Skip to content

Commit

Permalink
DEMO-195: Premium content feature does not work (ezsystems#100)
Browse files Browse the repository at this point in the history
DEMO-195: Premium content feature does not work

https://jira.ez.no/browse/DEMO-195
  • Loading branch information
damianz5 committed Sep 26, 2018
1 parent 45cab0b commit 89c0e7c
Show file tree
Hide file tree
Showing 20 changed files with 706 additions and 1 deletion.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -25,6 +25,7 @@ env:
- TEST_CMD="bin/behat -vv --profile=adminui --suite=adminui" COMPOSE_FILE="doc/docker/base-dev.yml:doc/docker/selenium.yml"
- TEST_CMD="bin/behat -vv --profile=adminui --suite=adminui --tags=~@EZP-29291-excluded" COMPOSE_FILE="doc/docker/base-dev.yml:doc/docker/varnish.yml:doc/docker/redis.yml:doc/docker/selenium.yml" WEB_HOST="varnish"
- TEST_CMD="bin/behat -vv --profile=repository-forms" COMPOSE_FILE="doc/docker/base-dev.yml:doc/docker/selenium.yml"
- TEST_CMD="bin/phpunit -v tests/"


# test only master (+ Pull requests)
Expand Down
23 changes: 22 additions & 1 deletion app/Resources/views/themes/tastefulplanet/full/article.html.twig
Expand Up @@ -32,9 +32,30 @@
</div>

<div class="field-body">
{{ ez_render_field(content, 'body') }}
{% set displayPremiumContentInfo = false %}

{% if not ez_is_field_empty(content, 'premium_content') %}
{% if ez_has_access_to_premium_content() %}
{{ ez_render_field(content, 'body') }}
{% else %}
{% set displayPremiumContentInfo = true %}
<div class="premium-article__preview">
{{ ez_render_field(content, 'body')|previewPremiumContent(3) }}
</div>
{% endif %}
{% else %}
{{ ez_render_field(content, 'body') }}
{% endif %}
</div>

{% if displayPremiumContentInfo %}
<div class="premium-article__info">
<span class="premium-article__image"><img src="{{ asset('assets/images/article/premium_content.png') }}" /></span>
<span class="premium-article__text">{{ 'To continue reading, without interruption, register and get unlimited access'|trans }}</span>
<span class="premium-article__register"><a href="{{ path('ez_user_register') }}" class="btn btn-primary">{{ 'Register Now'|trans }}</a></span>
</div>
{% endif %}

<div class="field-tags">
{{ ez_render_field(content, 'tags') }}
</div>
Expand Down
4 changes: 4 additions & 0 deletions app/config/default_parameters.yml
Expand Up @@ -119,6 +119,10 @@ parameters:
app.home.place_list_location_id: 219
app.home.tastes_location_id: 207

# Location Ids of allowed user groups for viewing Articles with premium content
# 12 - members, 13 administrator_users, 14 - editors
app.premium_content.allowed_user_groups.location_ids: [12, 13, 14]

app.migration_reference_file: '/src/AppBundle/MigrationVersions/References/references.yml'

app.migration.values_mapping:
Expand Down
12 changes: 12 additions & 0 deletions app/config/services.yml
Expand Up @@ -67,3 +67,15 @@ services:
AppBundle\Twig\YoutubeIdExtractorExtension:
tags:
- { name: twig.extension }


AppBundle\PremiumContent\HtmlRenderer: ~
AppBundle\User\UserGroups: ~

AppBundle\Twig\PremiumContentExtension:
arguments:
- '@AppBundle\PremiumContent\HtmlRenderer'
- '@AppBundle\User\UserGroups'
- '%app.premium_content.allowed_user_groups.location_ids%'
tags:
- { name: twig.extension }
61 changes: 61 additions & 0 deletions src/AppBundle/PremiumContent/HtmlRenderer.php
@@ -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);
}
}
106 changes: 106 additions & 0 deletions src/AppBundle/Twig/PremiumContentExtension.php
@@ -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);
}
}
79 changes: 79 additions & 0 deletions src/AppBundle/User/UserGroups.php
@@ -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);
}
);
}
}
44 changes: 44 additions & 0 deletions tests/AppBundle/PremiumContent/HtmlRendererTest.php
@@ -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');
}
}

0 comments on commit 89c0e7c

Please sign in to comment.