forked from ezsystems/ezplatform-demo
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DEMO-195: Premium content feature does not work (ezsystems#100)
DEMO-195: Premium content feature does not work https://jira.ez.no/browse/DEMO-195
- Loading branch information
Showing
20 changed files
with
706 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
/** | ||
* @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
*/ | ||
namespace AppBundle\PremiumContent; | ||
|
||
use DOMDocument; | ||
use DOMElement; | ||
|
||
class HtmlRenderer | ||
{ | ||
/** | ||
* Allows to display certain number of elements. | ||
* | ||
* @param string $document | ||
* @param int $numberOfDisplayedElements | ||
* | ||
* @return string | ||
*/ | ||
public function renderElements(string $document, int $numberOfDisplayedElements = 2): string | ||
{ | ||
$doc = new DOMDocument(); | ||
libxml_use_internal_errors(true); | ||
$doc->loadHTML($document, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); | ||
|
||
$childNodes = $doc->documentElement->childNodes; | ||
$numberOfNodesToKeep = 0; | ||
$importantNodes = 0; | ||
|
||
/** @var DOMElement $node */ | ||
foreach ($childNodes as $node) { | ||
++$numberOfNodesToKeep; | ||
|
||
if ($node->nodeType === XML_ELEMENT_NODE) { | ||
++$importantNodes; | ||
} | ||
|
||
if ($importantNodes >= $numberOfDisplayedElements) { | ||
break; | ||
} | ||
} | ||
|
||
while ($childNodes->length > $numberOfNodesToKeep) { | ||
$lastNode = $childNodes->item($childNodes->length - 1); | ||
$lastNode->parentNode->removeChild($lastNode); | ||
} | ||
|
||
return $this->removeXmlHeader($doc->saveXML()); | ||
} | ||
|
||
/** | ||
* @param string $html | ||
* | ||
* @return string | ||
*/ | ||
private function removeXmlHeader(string $html): string | ||
{ | ||
return str_replace('<?xml version="1.0" standalone="yes"?>' . "\n", null, $html); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
/** | ||
* @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
*/ | ||
namespace AppBundle\Twig; | ||
|
||
use AppBundle\PremiumContent\HtmlRenderer; | ||
use AppBundle\User\UserGroups; | ||
use Twig_Extension; | ||
use Twig_SimpleFilter; | ||
use Twig_SimpleFunction; | ||
|
||
/** | ||
* Twig helper for premium content. | ||
*/ | ||
class PremiumContentExtension extends Twig_Extension | ||
{ | ||
/** @var \AppBundle\PremiumContent\HtmlRenderer */ | ||
private $htmlRenderer; | ||
|
||
/** @var \AppBundle\User\UserGroups */ | ||
private $userGroups; | ||
|
||
/** @var int[] */ | ||
private $allowedUserGroupsLocationIds; | ||
|
||
/** @var bool */ | ||
private $hasAccess; | ||
|
||
/** | ||
* @param \AppBundle\PremiumContent\HtmlRenderer $htmlRenderer | ||
* @param \AppBundle\User\UserGroups $userGroups | ||
* @param array $allowedUserGroupsLocationIds | ||
*/ | ||
public function __construct( | ||
HtmlRenderer $htmlRenderer, | ||
UserGroups $userGroups, | ||
array $allowedUserGroupsLocationIds | ||
) { | ||
$this->htmlRenderer = $htmlRenderer; | ||
$this->userGroups = $userGroups; | ||
$this->allowedUserGroupsLocationIds = $allowedUserGroupsLocationIds; | ||
} | ||
|
||
/** | ||
* Returns the name of the extension. | ||
* | ||
* @return string The extension name | ||
*/ | ||
public function getName() | ||
{ | ||
return 'premium_content_extension'; | ||
} | ||
|
||
/** | ||
* Returns a list of functions to add to the existing list. | ||
* | ||
* @return Twig_SimpleFunction[] | ||
*/ | ||
public function getFunctions() | ||
{ | ||
return [ | ||
new Twig_SimpleFunction('ez_has_access_to_premium_content', [$this, 'hasAccessToPremiumContent']), | ||
]; | ||
} | ||
|
||
/** | ||
* Returns a list of filters to add to the existing list. | ||
* | ||
* @return Twig_SimpleFilter[] | ||
*/ | ||
public function getFilters() | ||
{ | ||
return [ | ||
new Twig_SimpleFilter('previewPremiumContent', [$this, 'previewPremiumContent'], ['is_safe' => ['html']]), | ||
]; | ||
} | ||
|
||
/** | ||
* Allows to display certain number of paragraphs. | ||
* | ||
* @param string $document | ||
* @param int $numberOfDisplayedElements | ||
* | ||
* @return string | ||
*/ | ||
public function previewPremiumContent(string $document, int $numberOfDisplayedElements = 2): string | ||
{ | ||
return $this->htmlRenderer->renderElements($document, $numberOfDisplayedElements); | ||
} | ||
|
||
/** | ||
* Checks if user has access to premium content. | ||
* | ||
* @return bool | ||
*/ | ||
public function hasAccessToPremiumContent(): bool | ||
{ | ||
if (null !== $this->hasAccess) { | ||
return $this->hasAccess; | ||
} | ||
|
||
return $this->hasAccess = $this->userGroups->isCurrentUserInOneOfTheGroups($this->allowedUserGroupsLocationIds); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
/** | ||
* @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
*/ | ||
namespace AppBundle\User; | ||
|
||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
use eZ\Publish\API\Repository\Repository as RepositoryInterface; | ||
use eZ\Publish\API\Repository\Values\User\User as ApiUser; | ||
|
||
class UserGroups | ||
{ | ||
/** @var \eZ\Publish\API\Repository\Repository */ | ||
private $repository; | ||
|
||
/** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface */ | ||
private $tokenStorage; | ||
|
||
/** | ||
* @param \eZ\Publish\API\Repository\Repository $repository | ||
* @param \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface $tokenStorage | ||
*/ | ||
public function __construct( | ||
RepositoryInterface $repository, | ||
TokenStorageInterface $tokenStorage | ||
) { | ||
$this->repository = $repository; | ||
$this->tokenStorage = $tokenStorage; | ||
} | ||
|
||
/** | ||
* Checks if current user's groups exists in one of passed user groups location Ids. | ||
* | ||
* @param array $userGroupsLocationIds | ||
* | ||
* @return bool | ||
*/ | ||
public function isCurrentUserInOneOfTheGroups(array $userGroupsLocationIds): bool | ||
{ | ||
return 0 !== \count(array_intersect($this->getCurrentUserGroupsIds(), $userGroupsLocationIds)); | ||
} | ||
|
||
/** | ||
* Returns User Groups Location Ids based on current user.. | ||
* | ||
* @return int[] | ||
*/ | ||
private function getCurrentUserGroupsIds(): array | ||
{ | ||
$token = $this->tokenStorage->getToken(); | ||
|
||
if (!$token || !\is_object($token->getUser())) { | ||
return []; | ||
} | ||
|
||
$userGroups = $this->loadUserGroups($token->getUser()->getAPIUser()); | ||
|
||
return array_map(function($userGroup) { | ||
return $userGroup->contentInfo->mainLocationId; | ||
}, $userGroups); | ||
} | ||
|
||
/** | ||
* Loads User Groups of User, regardless to user limitations. | ||
* | ||
* @param \eZ\Publish\API\Repository\Values\User\User $apiUser | ||
* | ||
* @return \eZ\Publish\API\Repository\Values\User\UserGroup[] | ||
*/ | ||
private function loadUserGroups(ApiUser $apiUser): array | ||
{ | ||
return $this->repository->sudo( | ||
function (RepositoryInterface $repository) use ($apiUser) { | ||
return $repository->getUserService()->loadUserGroupsOfUser($apiUser); | ||
} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
namespace Tests\AppBundle\PremiumContent; | ||
|
||
use AppBundle\PremiumContent\HtmlRenderer; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class HtmlRendererTest extends TestCase | ||
{ | ||
/** | ||
* @param string $inputDocument | ||
* @param int $numberOfDisplayedElements | ||
* @param string $expectedResult | ||
* @dataProvider htmlRendererDataProvider | ||
*/ | ||
public function testRenderElements(string $inputDocument, int $numberOfDisplayedElements, string $expectedResult) | ||
{ | ||
$subject = new HtmlRenderer(); | ||
|
||
$result = $subject->renderElements($inputDocument, $numberOfDisplayedElements); | ||
|
||
$this->assertEquals($expectedResult, $result); | ||
} | ||
|
||
public function htmlRendererDataProvider(): array | ||
{ | ||
$input = $this->loadFixture('testRenderElementsInput'); | ||
|
||
return [ | ||
[$input, 1, $this->loadFixture('testRenderElementsResult_1')], | ||
[$input, 2, $this->loadFixture('testRenderElementsResult_2')], | ||
[$input, 3, $this->loadFixture('testRenderElementsResult_3')], | ||
[$input, 4, $this->loadFixture('testRenderElementsResult_4')], | ||
[$input, 5, $this->loadFixture('testRenderElementsResult_5')], | ||
[$input, 6, $this->loadFixture('testRenderElementsResult_6')], | ||
[$input, 7, $this->loadFixture('testRenderElementsResult_7')], | ||
]; | ||
} | ||
|
||
private function loadFixture($name): string | ||
{ | ||
return file_get_contents(__DIR__ . '../../../fixtures/' . $name . '.xml'); | ||
} | ||
} |
Oops, something went wrong.