Zwischenstand

This commit is contained in:
Sven Wappler
2021-08-17 19:45:38 +02:00
parent fde2759722
commit ce6b9e38dc
71 changed files with 7886 additions and 809 deletions

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types = 1);
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Core\SingletonInterface;
/**
* An access control service
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class AbstractExtensionService implements SingletonInterface
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
*/
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Service\LoggingService
*/
protected $loggingService;
/**
* @var \TYPO3\CMS\Core\Log\Logger
*/
protected $logger;
/**
* Constructor
* @param \WapplerSystems\BookmarksLikesRatings\Service\LoggingService $loggingService
*/
public function __construct(LoggingService $loggingService)
{
$this->loggingService = $loggingService;
$this->logger = $loggingService->getLogger(get_class($this));
}
}

View File

@@ -0,0 +1,187 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
/** @noinspection PhpFullyQualifiedNameUsageInspection */
namespace WapplerSystems\BookmarksLikesRatings\Service;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter;
use WapplerSystems\BookmarksLikesRatings\Exception\FeUserNotFoundException;
use TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* An access control service
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class AccessControlService extends AbstractExtensionService
{
/**
* @var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository $frontendUserRepository
*/
protected $frontendUserRepository;
/**
* @param \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserRepository $frontendUserRepository
* @noinspection PhpUnused
*/
public function injectFrontendUserRepository(FrontendUserRepository $frontendUserRepository): void
{
$this->frontendUserRepository = $frontendUserRepository;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoterRepository $voterRepository
*/
protected $voterRepository;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoterRepository $voterRepository
*/
public function injectVoterRepository(\WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoterRepository $voterRepository): void
{
$this->voterRepository = $voterRepository;
}
/**
* @var \TYPO3\CMS\Core\Context\Context $context
*/
protected $context;
/**
* @param \TYPO3\CMS\Core\Context\Context $context
*/
public function injectContext(\TYPO3\CMS\Core\Context\Context $context): void
{
$this->context = $context;
}
/**
* Tests, if the given person is logged into the frontend
*
* @param \TYPO3\CMS\Extbase\Domain\Model\FrontendUser|null $person The person
* @return bool The result; true if the given person is logged in; otherwise false
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function isLoggedIn(\TYPO3\CMS\Extbase\Domain\Model\FrontendUser $person = null): bool
{
if (is_object($person)) {
if ($person->getUid() &&
($person->getUid() === $this->getFrontendUserUid())) {
return true; //treat anonymous user also as logged in
}
}
return false;
}
/**
* @return bool
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function backendAdminIsLoggedIn(): bool
{
return $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn');
}
/**
* @return bool
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function hasLoggedInFrontendUser(): bool
{
return $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn');
}
/**
* @return array
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function getFrontendUserGroups(): array
{
if ($this->hasLoggedInFrontendUser()) {
return $this->context->getPropertyFromAspect('frontend.user', 'groupIds');
}
return [];
}
/**
* @return int|null
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function getFrontendUserUid(): ?int
{
if ($this->hasLoggedInFrontendUser()) {
return $this->context->getPropertyFromAspect('frontend.user', 'id');
}
return null;
}
/**
* Loads objects from repositories
*
* @param mixed $voter
* @return \TYPO3\CMS\Extbase\Domain\Model\FrontendUser
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function getFrontendUser($voter = null): ?\TYPO3\CMS\Extbase\Domain\Model\FrontendUser
{
//set userobject
if (!$voter instanceof \TYPO3\CMS\Extbase\Domain\Model\FrontendUser) {
//TODO Errorhandling if no user is logged in
if ((int)$voter === 0) {
//get logged in fe-user
$voter = $this->frontendUserRepository->findByUid($this->getFrontendUserUid());
} else {
$voter = $this->frontendUserRepository->findByUid((int)$voter);
}
}
return $voter;
}
/**
* Loads objects from repositories
*
* @param int|null $voter
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter
* @throws FeUserNotFoundException
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
public function getFrontendVoter(?int $voter = 0): Voter
{
$exceptionMessageArray = [];
/** @var Voter $voterObject */
$voterObject = null;
//TODO Errorhandling if no user is logged in
if ((int)$voter === 0) {
//get logged in fe-user
$voterObject = $this->voterRepository->findByUid($this->getFrontendUserUid());
$exceptionMessageArray = [$this->getFrontendUserUid()];
$exceptionMessageType = 'feUser';
} else {
$voterObject = $this->voterRepository->findByUid((int)$voter);
$exceptionMessageArray = [(int)$voter];
$exceptionMessageType = 'anonymousUser';
}
if (empty($voterObject)) {
throw new FeUserNotFoundException(
LocalizationUtility::translate(
'flash.pluginConfiguration.missing.' . $exceptionMessageType,
'ThRating',
$exceptionMessageArray
),
1602095329
);
}
return $voterObject;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Extbase\Exception;
/**
* An access exception
*
* @version $ID: $
*/
class AccessException extends Exception
{
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Service for setting cookies like Typo3 does
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class CookieService extends AbstractExtensionService
{
/**
* Indicator for cookieProtection has been set
* @var bool
*/
protected $cookieProtection = false;
/**
* Gets the domain to be used on setting cookies.
* The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
* Protected function taken from t3lib_userAuth (t3 4.7.7)
*
* @return string The domain to be used on setting cookies
*/
protected function getCookieDomain()
{
$result = '';
$cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'];
// If a specific cookie domain is defined for a given TYPO3_MODE,
// use that domain
if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['cookieDomain'])) {
$cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['FE']['cookieDomain'];
}
if ($cookieDomain) {
if ($cookieDomain[0] === '/') {
$match = [];
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$matchCnt = @preg_match(
$cookieDomain,
GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'),
$match
);
if ($matchCnt === false) {
$this->logger->log(
LogLevel::ERROR,
'getCookieDomain: The regular expression for the cookie domain contains errors.' .
'The session is not shared across sub-domains.',
['cookieDomain' => $cookieDomain, 'errorCode' => 1399137882]
);
} elseif ($matchCnt) {
$result = $match[0];
}
} else {
$result = $cookieDomain;
}
}
return $result;
}
/**
* Sets the cookie
* Protected function taken from t3lib_userAuth (t3 4.7.7)
*
* @param string $cookieName identifier for the cookie
* @param string $cookieValue cookie value
* @param int $cookieExpire expire time for the cookie (UNIX timestamp)
*
* @throws Exception
*/
public function setVoteCookie($cookieName, $cookieValue, $cookieExpire = 0): void
{
// do not set session cookies
if (!empty($cookieExpire)) {
$settings = $GLOBALS['TYPO3_CONF_VARS']['SYS'];
// Get the domain to be used for the cookie (if any):
$cookieDomain = $this->getCookieDomain();
// If no cookie domain is set, use the base path:
$cookiePath = ($cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'));
// Use the secure option when the current request is served by a secure connection:
$cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
// Deliver cookies only via HTTP and prevent possible XSS by JavaScript:
$cookieHttpOnly = (bool)$settings['cookieHttpOnly'];
// Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
setcookie(
$cookieName,
$cookieValue,
(int)$cookieExpire,
$cookiePath,
$cookieDomain,
$cookieSecure,
$cookieHttpOnly
);
$this->cookieProtection = true;
$this->logger->log(
LogLevel::INFO,
'setVoteCookie: Cookie set',
[
'cookieName' => $cookieName,
'cookieValue' => $cookieValue,
'cookieExpire' => $cookieExpire,
'cookiePath' => $cookiePath,
'cookieDomain' => $cookieDomain,
'cookieSecure' => $cookieSecure,
'cookieHttpOnly' => $cookieHttpOnly,
]
);
} else {
throw new Exception(
"Cookie was not set since HTTPS was forced in \$GLOBALS['TYPO3_CONF_VARS'][SYS][cookieSecure].",
1254325546
);
}
}
}
/**
* Return if cookie protection has been set
*
* @return bool
*/
public function isProtected()
{
return $this->cookieProtection;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
/**
* A ViewHelper Exception
*
* @api
*/
class Exception extends \TYPO3Fluid\Fluid\Core\Exception
{
}

View File

@@ -0,0 +1,233 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use WapplerSystems\BookmarksLikesRatings\Exception\FeUserStoragePageException;
use WapplerSystems\BookmarksLikesRatings\Exception\InvalidStoragePageException;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Factory for model objects
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class ExtensionConfigurationService extends AbstractExtensionService
{
/**
* Contains configuration of the calling extension
*
* @var array
*/
protected $originalConfiguration;
/**
* Calling extension query settings
*
* @var \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface
*/
protected $originalTypo3QuerySettings;
/**
* @var int
*/
protected $cookieLifetime;
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @var QuerySettingsInterface
*/
private $extDefaultQuerySettings;
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
{
$this->configurationManager = $configurationManager;
}
/**
* Contains the settings of the current extension
*
* @var array
*/
protected $settings;
/**
* Contains configuration of th_rating
*
* @var array
*/
protected $thRatingConfiguration;
/**
* Contains configuration of the current extension
*
* @var array
*/
protected $frameworkConfiguration;
/**
* Constructor
*/
public function initializeObject(): void
{
// store calling extension configuration
$this->originalConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
);
$this->thRatingConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK,
'thrating',
'pi1'
);
if (!empty($this->thRatingConfiguration['ratings'])) {
//Merge extension ratingConfigurations with customer added ones
ArrayUtility::mergeRecursiveWithOverrule(
$this->thRatingConfiguration['settings']['ratingConfigurations'],
$this->thRatingConfiguration['ratings']
);
}
$this->frameworkConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT,
'thrating',
'pi1'
);
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->frameworkConfiguration,get_class($this).' frameworkConfiguration');
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->thRatingConfiguration,get_class($this).' thRatingConfiguration');
//\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->originalConfiguration,get_class($this).' originalConfiguration');
}
/**
* Get a logger instance
* The configuration of the logger is modified by extension typoscript config
*
* @param string|null $name the class name which this logger is for
* @return \TYPO3\CMS\Core\Log\Logger
*/
public function getLogger(string $name = null): Logger
{
if (empty($name)) {
return $this->loggingService->getLogger(__CLASS__);
}
return $this->loggingService->getLogger($name);
}
/**
* Set default query settings to those of th_rating
* (could be different if services are called from other extensions
* @throws FeUserStoragePageException
* @throws InvalidStoragePageException
*/
public function setExtDefaultQuerySettings(): void
{
$this->mergeStoragePids();
$this->extDefaultQuerySettings = $this->objectManager->get(QuerySettingsInterface::class);
$this->extDefaultQuerySettings->setStoragePageIds(
explode(',', $this->thRatingConfiguration['persistence']['storagePid'])
);
}
/**
* @return bool
*/
protected function getCookieProtection(): bool
{
$this->cookieLifetime = abs((int)$this->thRatingConfiguration['settings']['cookieLifetime']);
$this->logger->log(
LogLevel::DEBUG,
'Cookielifetime set to ' . $this->cookieLifetime . ' days',
['errorCode' => 1465728751]
);
return empty($this->cookieLifetime);
}
/**
* Checks storagePid settings of th_rating and tx_felogin_pi1 and
* concatenates them to the new storagePid setting
*
* @throws InvalidStoragePageException if plugin.tx_thrating.storagePid has not been set
* @throws FeUserStoragePageException if plugin.tx_felogin_pi1.storagePid has not been set
*/
private function mergeStoragePids(): void
{
$storagePids = GeneralUtility::intExplode(',', $this->thRatingConfiguration['storagePid'], true);
if (empty($storagePids[0])) {
throw new InvalidStoragePageException(
LocalizationUtility::translate('flash.vote.general.invalidStoragePid', 'ThRating'),
1403203519
);
}
$storagePids[] = $this->getFeUserStoragePage();
$this->thRatingConfiguration['persistence.']['storagePid'] = implode(',', $storagePids);
}
/**
* Check and return the first configured storage page for website users
* @return int
* @throws FeUserStoragePageException
*/
private function getFeUserStoragePage(): int
{
$feUserStoragePid = array_merge(
GeneralUtility::intExplode(
',',
$this->frameworkConfiguration['plugin.']['tx_felogin_pi1.']['storagePid'],
true
),
GeneralUtility::intExplode(',', $this->thRatingConfiguration['feUsersStoragePid'], true)
);
if (empty($feUserStoragePid[0])) {
throw new FeUserStoragePageException(
LocalizationUtility::translate('flash.pluginConfiguration.missing.feUserStoragePid', 'ThRating'),
1403190539
);
}
return $feUserStoragePid[0];
}
/**
* Change the current extbase configuration to the one of th_rating
*/
public function prepareExtensionConfiguration(): void
{
if ($this->originalConfiguration['extensionName'] !== 'ThRating') {
//Set default storage pids
$this->setExtDefaultQuerySettings();
}
$this->configurationManager->setConfiguration($this->thRatingConfiguration);
}
/**
* Change the current extbase configuration to the one of th_rating
*/
public function restoreCallingExtensionConfiguration(): void
{
if ($this->originalConfiguration['extensionName'] !== 'ThRating') {
$this->configurationManager->setConfiguration($this->originalConfiguration);
}
}
}

View File

@@ -0,0 +1,720 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
/** @noinspection PhpFullyQualifiedNameUsageInspection */
namespace WapplerSystems\BookmarksLikesRatings\Service;
use Psr\Http\Message\ServerRequestInterface;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\RatingImage;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote;
use WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingobjectRepository;
use WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingRepository;
use WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository;
use WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoteRepository;
use WapplerSystems\BookmarksLikesRatings\Domain\Validator\RatingobjectValidator;
use WapplerSystems\BookmarksLikesRatings\Domain\Validator\RatingValidator;
use WapplerSystems\BookmarksLikesRatings\Domain\Validator\StepconfValidator;
use WapplerSystems\BookmarksLikesRatings\Domain\Validator\VoteValidator;
use WapplerSystems\BookmarksLikesRatings\Evaluation\DynamicCssEvaluator;
use WapplerSystems\BookmarksLikesRatings\Exception\LanguageNotFoundException;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Factory for model objects
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class ExtensionHelperService extends AbstractExtensionService
{
protected const DYN_CSS_FILENAME = 'typo3temp/thratingDyn.css';
/**
* The current request.
*
* @var \TYPO3\CMS\Extbase\Mvc\Request
*/
protected $request;
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingobjectRepository
*/
protected $ratingobjectRepository;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingobjectRepository $ratingobjectRepository
*/
public function injectRatingobjectRepository(RatingobjectRepository $ratingobjectRepository): void
{
$this->ratingobjectRepository = $ratingobjectRepository;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingRepository
*/
protected $ratingRepository;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\RatingRepository $ratingRepository
*/
public function injectRatingRepository(RatingRepository $ratingRepository): void
{
$this->ratingRepository = $ratingRepository;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository
*/
protected $stepnameRepository;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository $stepnameRepository
*/
public function injectStepnameRepository(StepnameRepository $stepnameRepository): void
{
$this->stepnameRepository = $stepnameRepository;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoteRepository
*/
protected $voteRepository;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoteRepository $voteRepository
*/
public function injectVoteRepository(VoteRepository $voteRepository): void
{
$this->voteRepository = $voteRepository;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Service\AccessControlService
*/
protected $accessControllService;
/**
* @param AccessControlService $accessControllService
*/
public function injectAccessControlService(AccessControlService $accessControllService): void
{
$this->accessControllService = $accessControllService;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Validator\StepconfValidator
*/
protected $stepconfValidator;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Validator\StepconfValidator $stepconfValidator
*/
public function injectStepconfValidator(StepconfValidator $stepconfValidator): void
{
$this->stepconfValidator = $stepconfValidator;
}
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
{
$this->configurationManager = $configurationManager;
}
/**
* Contains the settings of the current extension
*
* @var array
*/
protected $settings;
/**
* @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\RatingImage
*/
protected $ratingImage;
/**
* @var \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface
*/
protected $extDefaultQuerySettings;
/**
* Constructor
*/
public function initializeObject(): void
{
$this->settings = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
'thrating',
'pi1'
);
$frameworkConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK,
'thrating',
'pi1'
);
if (!empty($frameworkConfiguration['ratings'])) {
//Merge extension ratingConfigurations with customer added ones
ArrayUtility::mergeRecursiveWithOverrule(
$this->settings['ratingConfigurations'],
$frameworkConfiguration['ratings']
);
}
}
/**
* @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
global $TSFE;
return $TSFE;
}
/**
* Returns the completed settings array
*
* @param array $settings
* @return array
*/
private function completeConfigurationSettings(array $settings): array
{
$cObj = $this->configurationManager->getContentObject();
if (!empty($cObj->currentRecord)) {
/* build array [0=>cObj tablename, 1=> cObj uid] - initialize with content information
(usage as normal content) */
$currentRecord = explode(':', $cObj->currentRecord);
} else {
//build array [0=>cObj tablename, 1=> cObj uid] - initialize with page info if used by typoscript
$currentRecord = ['pages', $GLOBALS['TSFE']->page['uid']];
}
if (empty($settings['ratetable'])) {
$settings['ratetable'] = $currentRecord[0];
}
if (empty($settings['ratefield'])) {
$settings['ratefield'] = 'uid';
}
if (empty($settings['ratedobjectuid'])) {
$settings['ratedobjectuid'] = $currentRecord[1];
}
return $settings;
}
/**
* Returns a new or existing ratingobject
*
* @param array $settings
* @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject
*/
public function getRatingobject(array $settings): Ratingobject
{
$ratingobject = null;
//check whether a dedicated ratingobject is configured
if (!empty($settings['ratingobject'])) {
$ratingobject = $this->ratingobjectRepository->findByUid($settings['ratingobject']);
} else {
if (empty($settings['ratetable']) || empty($settings['ratefield'])) {
//fallback to default configuration
$settings = $settings['defaultObject'] + $settings;
}
$settings = $this->completeConfigurationSettings($settings);
$ratingobject = $this->ratingobjectRepository->findMatchingTableAndField(
$settings['ratetable'],
$settings['ratefield'],
RatingobjectRepository::ADD_IF_NOT_FOUND
);
}
return $ratingobject;
}
/**
* Returns a new or existing ratingobject
*
* @param array $stepconfArray
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf
*/
public function createStepconf(array $stepconfArray): Stepconf
{
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf */
$stepconf = $this->objectManager->get(Stepconf::class);
$stepconf->setRatingobject($stepconfArray['ratingobject']);
$stepconf->setSteporder($stepconfArray['steporder']);
$stepconf->setStepweight($stepconfArray['stepweight']);
return $stepconf;
}
/**
* Returns a new or existing ratingobject
*
* @param Stepconf $stepconf
* @param array $stepnameArray
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname
* @throws LanguageNotFoundException
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
*/
public function createStepname(Stepconf $stepconf, array $stepnameArray): Stepname
{
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $stepname */
$stepname = $this->objectManager->get(Stepname::class);
$stepname->setStepconf($stepconf);
$stepname->setStepname($stepnameArray['stepname']);
$stepname->setPid($stepnameArray['pid']);
$stepname->setSysLanguageUid(
$this->getStaticLanguageByIsoCode(
$stepname->getPid(),
$stepnameArray['twoLetterIsoCode'] ?: null
)->getLanguageId()
);
$defaultStepname = $this->stepnameRepository->findDefaultStepname($stepname);
$l18nParent = is_null($defaultStepname) ? 0 : $defaultStepname->getUid();
$stepname->setL18nParent($l18nParent);
return $stepname;
}
/**
* Returns a new or existing rating
*
* @param array $settings
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject|null $ratingobject
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
* @throws \WapplerSystems\BookmarksLikesRatings\Service\Exception
*/
public function getRating(array $settings, ?Ratingobject $ratingobject): Rating
{
$settings = $this->completeConfigurationSettings($settings);
if (!empty($settings['rating'])) {
//fetch rating when it is configured
$rating = $this->ratingRepository->findByUid($settings['rating']);
} elseif ($settings['ratedobjectuid'] && !$this->objectManager->get(RatingobjectValidator::class)
->validate($ratingobject)->hasErrors()) {
//get rating according to given row
/** @noinspection NullPointerExceptionInspection */
$rating = $this->ratingRepository->findMatchingObjectAndUid(
$ratingobject,
$settings['ratedobjectuid'],
RatingRepository::ADD_IF_NOT_FOUND
);
} else {
throw new \WapplerSystems\BookmarksLikesRatings\Service\Exception(
'Incomplete configuration setting. Either \'rating\' or \'ratedobjectuid\' are missing.',
1398351336
);
}
return $rating;
}
/**
* Returns a new or existing vote
*
* @param string $prefixId
* @param array $settings
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating $rating
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote
* @throws \WapplerSystems\BookmarksLikesRatings\Exception\FeUserNotFoundException
*/
public function getVote(string $prefixId, array $settings, Rating $rating): Vote
{
// initialize variables
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote */
$vote = null;
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter $voter */
$voter = null;
//first fetch real voter or anonymous
/** @var int $frontendUserUid */
$frontendUserUid = $this->accessControllService->getFrontendUserUid();
if (!$frontendUserUid && !empty($settings['mapAnonymous'])) {
//set anonymous vote
$voter = $this->accessControllService->getFrontendVoter($settings['mapAnonymous']);
$anonymousRating = GeneralUtility::makeInstance(\WapplerSystems\BookmarksLikesRatings\Service\JsonService::class)
->decodeJsonToArray($_COOKIE[$prefixId . '_AnonymousRating_' . $rating->getUid()]);
if (!empty($anonymousRating['voteUid'])) {
$vote = $this->voteRepository->findByUid($anonymousRating['voteUid']);
}
} elseif ($frontendUserUid) {
//set FEUser if one is logged on
$voter = $this->accessControllService->getFrontendVoter($frontendUserUid);
if ($voter instanceof \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter) {
$vote = $this->voteRepository->findMatchingRatingAndVoter($rating, $voter);
}
}
//voting not found in database or anonymous vote? - create new one
$voteValidator = $this->objectManager->get(VoteValidator::class);
if ($voteValidator->validate($vote)->hasErrors()) {
$vote = $this->objectManager->get(Vote::class);
$ratingValidator = $this->objectManager->get(RatingValidator::class);
if (!$ratingValidator->validate($rating)->hasErrors()) {
$vote->setRating($rating);
}
if ($voter instanceof \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter) {
$vote->setVoter($voter);
}
}
return $vote;
}
/**
* Get a logger instance
* The configuration of the logger is modified by extension typoscript config
*
* @param string|null $name the class name which this logger is for
* @return \TYPO3\CMS\Core\Log\Logger
*/
public function getLogger(?string $name): Logger
{
if (empty($name)) {
return $this->loggingService->getLogger(__CLASS__);
}
return $this->loggingService->getLogger($name);
}
/**
* Update and persist attached objects to the repository
*
* @param string $repository
* @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $objectToPersist
*/
public function persistRepository(string $repository, AbstractEntity $objectToPersist): void
{
$objectUid = $objectToPersist->getUid();
if (!is_int($objectUid)) {
$this->objectManager->get($repository)->add($objectToPersist);
} else {
$this->objectManager->get($repository)->update($objectToPersist);
}
$this->objectManager->get(PersistenceManager::class)->persistAll();
}
/**
* Clear the dynamic CSS file for recreation
*/
public function clearDynamicCssFile(): void
{
$this->objectManager->get(DynamicCssEvaluator::class)->clearCachePostProc([]);
}
/**
* Render CSS-styles for ratings and ratingsteps
* Only called by singeltonAction to render styles once per page.
* The file self::DYN_CSS_FILENAME will be created if it doesn<73>t exist
*
* @return array
*/
public function renderDynCSS(): array
{
$messageArray = [];
$cssFile = '';
//create file if it does not exist
if (file_exists(Environment::getPublicPath() . '/' . self::DYN_CSS_FILENAME)) {
$fstat = stat(Environment::getPublicPath() . '/' . self::DYN_CSS_FILENAME);
//do not recreate file if it has greater than zero length
if ($fstat[7] !== 0) {
$this->logger->log(LogLevel::DEBUG, 'Dynamic CSS file exists - exiting');
return $messageArray;
}
}
//now walk through all ratingobjects to calculate stepwidths
$allRatingobjects = $this->ratingobjectRepository->findAll(true);
foreach ($allRatingobjects as $ratingobject) {
$ratingobjectUid = $ratingobject->getUid();
/** @var ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf> $stepconfObjects */
$stepconfObjects = $ratingobject->getStepconfs();
$stepcount = count($stepconfObjects);
if (!$stepcount) {
if ($this->settings['showMissingStepconfError']) {
//show error message in GUI
$messageArray[] = [
'messageText' => LocalizationUtility::translate(
'flash.renderCSS.noStepconf',
'ThRating',
[
1 => $ratingobject->getUid(),
2 => $ratingobject->getPid()
]
),
'messageTitle' => LocalizationUtility::translate(
'flash.configuration.error',
'ThRating'
),
'severity' => 'ERROR',
'additionalInfo' => [
'errorCode' => 1384705470,
'ratingobject UID' => $ratingobject->getUid(),
'ratingobject PID' => $ratingobject->getPid(),
],
];
} else {
//only log message
$this->logger->log(
LogLevel::ERROR,
LocalizationUtility::translate(
'flash.renderCSS.noStepconf',
'ThRating',
[
1 => $ratingobject->getUid(),
2 => $ratingobject->getPid()
]
),
[
'errorCode' => 1384705470,
'ratingobject UID' => $ratingobject->getUid(),
'ratingobject PID' => $ratingobject->getPid(),
]
);
}
}
$stepWeights = [];
$sumStepWeights = 0;
$stepconfs = $stepconfObjects->toArray();
foreach ($stepconfs as $stepconf) { //stepconfs are already sorted by steporder
//just do checks here that all steps are OK
if (!$this->stepconfValidator->validate($stepconf)->hasErrors()) {
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf */
$stepWeights[] = $stepconf->getStepweight();
$sumStepWeights += $stepconf->getStepweight();
} else {
foreach ($this->stepconfValidator->validate($stepconf)->getErrors() as $errorMessage) {
$messageArray[] = [
'messageText' => $errorMessage->getMessage(),
'messageTitle' => LocalizationUtility::translate('flash.configuration.error', 'ThRating'),
'severity' => 'ERROR',
'additionalInfo' => ['errorCode' => $errorMessage->getCode(),
'errorMessage' => $errorMessage->getMessage(), ], ];
}
}
}
$this->logger->log(
LogLevel::INFO,
'Ratingobject data',
[
'ratingobject UID' => $ratingobject->getUid(),
'ratingobject PID' => $ratingobject->getPid(),
'stepcount' => $stepcount,
'stepWeights' => $stepWeights,
'sumStepWeights' => $sumStepWeights,
'messageCount' => count($messageArray)
]
);
//generate CSS for all ratings out of TSConfig
foreach ($this->settings['ratingConfigurations'] as $ratingName => $ratingConfig) {
if ($ratingName === 'default') {
continue;
}
$subURI = substr(Environment::getPublicPath() . '/', strlen($_SERVER['DOCUMENT_ROOT']) + 1);
$basePath = $this->getTypoScriptFrontendController()->baseUrl ?: '//' .
$_SERVER['HTTP_HOST'] . '/' . $subURI;
$this->ratingImage = $this->objectManager->get(RatingImage::class);
$this->ratingImage->setConf($ratingConfig['imagefile']);
$filename = $this->ratingImage->getImageFile();
if (empty($filename)) {
$messageArray[] = [
'messageText' => LocalizationUtility::translate(
'flash.vote.renderCSS.defaultImage',
'ThRating'
),
'messageTitle' => LocalizationUtility::translate(
'flash.heading.warning',
'ThRating'
),
'severity' => 'WARNING',
'additionalInfo' => ['errorCode' => 1403192702,
'ratingName' => $ratingName,
'ratingConfig' => $ratingConfig, ], ];
$defaultRatingName = $this->settings['ratingConfigurations']['default'];
$ratingConfig = $this->settings['ratingConfigurations'][$defaultRatingName];
$this->ratingImage->setConf($ratingConfig['imagefile']);
$filename = $this->ratingImage->getImageFile();
}
$filenameUri = $basePath . '/' . $filename; //prepend host basepath if no URL is given
$imageDimensions = $this->ratingImage->getImageDimensions();
$height = $imageDimensions['height'];
$width = $imageDimensions['width'];
$mainId = '.thRating-RObj' . $ratingobjectUid . '-' . $ratingName;
$this->logger->log(
LogLevel::DEBUG,
'Main CSS info',
[
'mainId' => $mainId,
'filenameUri' => $filenameUri,
'image width' => $width,
'image height' => $height, ]
);
//calculate overall rating size depending on rating direction
if ($ratingConfig['tilt']) {
$width = round($width / 3, 1);
if (!$ratingConfig['barimage']) {
$height *= $sumStepWeights;
}
$cssFile .= $mainId . ' { width:' . $width . 'px; height:' . $height . 'px; }' . chr(10);
$cssFile .= $mainId . ', ' . $mainId . ' span:hover, ' . $mainId . ' span:active, ' . $mainId .
' span:focus, ' . $mainId . ' .current-rating { background:url(' . $filenameUri .
') right bottom repeat-y; }' . chr(10);
$cssFile .= $mainId . ' span, ' . $mainId . ' .current-rating { width:' . $width . 'px; }' .
chr(10);
} else {
$height = round($height / 3, 1);
if (!$ratingConfig['barimage']) {
$width *= $sumStepWeights;
}
$cssFile .= $mainId . ' { width:' . $width . 'px; height:' . $height . 'px; }' . chr(10);
$cssFile .= $mainId . ', ' . $mainId . ' span:hover, ' . $mainId . ' span:active, ' . $mainId .
' span:focus, ' . $mainId . ' .current-rating { background:url(' . $filenameUri .
') 0 0 repeat-x; }' . chr(10);
$cssFile .= $mainId . ' span, ' . $mainId . ' .current-rating { height:' . $height .
'px; line-height:' . $height . 'px; }' . chr(10);
//calculate widths/heights related to stepweights
}
$cssFile .= $mainId . ' .current-poll { background:url(' . $filenameUri . '); }' . chr(10);
}
//calculate widths/heights related to stepweights
$i = 1;
$stepPart = 0;
$sumWeights = 0;
foreach ($stepWeights as $stepWeight) {
$sumWeights += $stepWeight;
$zIndex = $stepcount - $i + 2; //add 2 to override .current-poll and .currentPollText
//configure rating and polling styles for steps
$oneStepPart = round($stepWeight * 100 / $sumStepWeights, 1); //calculate single width of ratingstep
$cssFile .= 'span.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-ratingpoll-normal { width:' .
$oneStepPart . '%; z-index:' . $zIndex . '; margin-left:' . $stepPart . '%;}' . chr(10);
$cssFile .= 'span.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-ratingpoll-tilt { height:' .
$oneStepPart . '%; z-index:' . $zIndex . '; margin-bottom:' . $stepPart . '%; }' . chr(10);
$cssFile .= 'li.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-currentpoll-normal { width:' .
$oneStepPart . '%; margin-left:' . $stepPart . '%; }' . chr(10);
$cssFile .= 'li.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-currentpoll-normal span { width:100%; }' .
chr(10);
$cssFile .= 'li.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-currentpoll-tilt { height:' .
$oneStepPart . '%; margin-bottom:' . $stepPart . '%; }' . chr(10);
$cssFile .= 'li.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-currentpoll-tilt span { height:100%; }' .
chr(10);
$stepPart = round($sumWeights * 100 / $sumStepWeights, 1); //calculate sum of widths to this ratingstep
$cssFile .= 'span.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-ratingstep-normal { width:' .
$stepPart . '%; z-index:' . $zIndex . '; }' . chr(10);
$cssFile .= 'span.RObj' . $ratingobjectUid . '-StpOdr' . $i . '-ratingstep-tilt { height:' .
$stepPart . '%; z-index:' . $zIndex . '; }' . chr(10);
$i++;
}
//reset variables for next iteration
unset($stepWeights, $sumWeights, $sumStepWeights);
$this->logger->log(LogLevel::DEBUG, 'CSS finished for ratingobject');
}
$this->logger->log(LogLevel::DEBUG, 'Saving CSS file', ['cssFile' => $cssFile]);
$fp = fopen(Environment::getPublicPath() . '/' . self::DYN_CSS_FILENAME, 'wb');
fwrite($fp, $cssFile);
fclose($fp);
return $messageArray;
}
/**
* Returns the language object
* If not ISO code is provided the default language is returned
*
* @param int $pid page id to which is part of the site
* @param string|null $twoLetterIsoCode iso-639-1 string (e.g. en, de, us)
* @return \TYPO3\CMS\Core\Site\Entity\SiteLanguage
* @throws LanguageNotFoundException
*/
public function getStaticLanguageByIsoCode(int $pid, string $twoLetterIsoCode = null): SiteLanguage
{
/** @var Site $site */
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pid);
if (!is_null($twoLetterIsoCode)) {
foreach ($site->getAllLanguages() as $language) {
if ($language->getTwoLetterIsoCode() === $twoLetterIsoCode) {
return $language;
}
}
throw new LanguageNotFoundException(LocalizationUtility::translate(
'flash.general.languageNotFound',
'ThRating'
), 1582980369);
}
return $site->getDefaultLanguage();
}
/**
* Returns the language object
* If not ISO code is provided the default language is returned
*
* @param int $pid page id to which is part of the site
* @param int|null $languageId iso-639-1 string (e.g. en, de, us)
* @return \TYPO3\CMS\Core\Site\Entity\SiteLanguage
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public function getStaticLanguageById(int $pid, int $languageId = null): ?SiteLanguage
{
/** @var Site $site */
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pid);
if ($languageId) {
return $site->getLanguageById($languageId);
}
return $site->getDefaultLanguage();
}
/**
* @return ServerRequestInterface
*/
public function getRequest(): ServerRequestInterface
{
return $GLOBALS['TYPO3_REQUEST'];
}
/**
* Sets the current request object
*
* @param \TYPO3\CMS\Extbase\Mvc\Request $request
*/
public function setRequest(\TYPO3\CMS\Extbase\Mvc\Request $request): void
{
$this->request = $request;
}
}

View File

@@ -0,0 +1,185 @@
<?php
declare(strict_types = 1);
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject;
use WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf;
use TYPO3\CMS\Core\Log\LogLevel;
/**
* Factory for model objects
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class ExtensionManagementService extends AbstractExtensionService
{
/**
* @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionConfigurationService
*/
protected $extensionConfigurationService;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionConfigurationService $extensionConfigurationService
*/
public function injectExtensionConfigurationService(ExtensionConfigurationService $extensionConfigurationService): void
{
$this->extensionConfigurationService = $extensionConfigurationService;
}
/**
* @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService
*/
protected $extensionHelperService;
/**
* @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService
*/
public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService)
{
$this->extensionHelperService = $extensionHelperService;
}
/**
* Prepares an object for ratings
*
* @api
* @param string $tablename
* @param string $fieldname
* @param int $stepcount
* @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
* @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException
*/
public function makeRatable(string $tablename, string $fieldname, int $stepcount): Ratingobject
{
$this->logger->log(
LogLevel::INFO,
'makeRatable called',
['tablename' => $tablename, 'fieldname' => $fieldname, 'stepcount' => $stepcount]
);
$this->extensionConfigurationService->prepareExtensionConfiguration();
$ratingobject = $this->extensionHelperService->getRatingobject(
['ratetable' => $tablename, 'ratefield' => $fieldname]
);
//create a new default stepconf having stepweight 1 for each step
for ($i = 1; $i <= $stepcount; $i++) {
$stepconfArray = ['ratingobject' => $ratingobject, 'steporder' => $i, 'stepweight' => 1];
$stepconf = $this->extensionHelperService->createStepconf($stepconfArray);
$ratingobject->addStepconf($stepconf);
}
//Full reload of newly created object
$ratingobject = $this->extensionHelperService->getRatingobject(
['ratetable' => $tablename, 'ratefield' => $fieldname]
);
// CREATE NEW DYNCSS FILE
$this->extensionHelperService->clearDynamicCssFile();
$this->extensionHelperService->renderDynCSS();
$this->extensionConfigurationService->restoreCallingExtensionConfiguration();
return $ratingobject;
}
/**
* Prepares an object for ratings
*
* @api
* @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf
* @param string $stepname
* @param string|null $twoLetterIsoCode
* @param bool $allStepconfs Take stepname for all steps and add steporder number at the end
* @return bool
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
* @throws \WapplerSystems\BookmarksLikesRatings\Exception\Exception
*/
public function setStepname(
Stepconf $stepconf,
string $stepname,
string $twoLetterIsoCode=null,
bool $allStepconfs = false
): bool {
$this->logger->log(
LogLevel::INFO,
'setStepname called',
[
'stepconf' => $stepconf->getUid(),
'steporder' => $stepconf->getSteporder(),
'stepname' => $stepname,
'twoLetterIsoCode' => $twoLetterIsoCode,
'allStepconfs' => $allStepconfs
]
);
$this->extensionConfigurationService->prepareExtensionConfiguration();
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $ratingobject */
$ratingobject = $stepconf->getRatingobject();
$success = true;
if (!$allStepconfs) {
//only add the one specific stepname
$stepnameArray = [
'stepname' => $stepname,
'twoLetterIsoCode' => $twoLetterIsoCode,
'pid' => $stepconf->getPid()
];
/** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $stepname */
$stepnameObject = $this->extensionHelperService->createStepname($stepconf, $stepnameArray);
if (!$stepconf->addStepname($stepnameObject)) {
$this->logger->log(
LogLevel::WARNING,
'Stepname entry for language already exists',
[
'stepconf' => $stepconf->getUid(),
'steporder' => $stepconf->getSteporder(),
'stepname' => $stepnameObject,
'twoLetterIsoCode' => $twoLetterIsoCode,
'errorCode' => 1398972827
]
);
$success = false;
}
} else {
//add stepnames to every stepconf
foreach ($ratingobject->getStepconfs() as $loopStepConf) {
$stepnameArray = [
'stepname' => $stepname . $loopStepConf->getSteporder(),
'twoLetterIsoCode' => $twoLetterIsoCode,
'pid' => $ratingobject->getPid()
];
$stepnameObject = $this->extensionHelperService->createStepname($loopStepConf, $stepnameArray);
if ($success && !$loopStepConf->addStepname($stepnameObject)) {
$this->logger->log(
LogLevel::WARNING,
'Stepname entry for language already exists',
[
'stepconf' => $stepconf->getUid(),
'steporder' => $stepconf->getSteporder(),
'stepname' => $stepname,
'twoLetterIsoCode' => $twoLetterIsoCode,
'errorCode' => 1398972331
]
);
$success = false;
}
}
}
$this->extensionConfigurationService->restoreCallingExtensionConfiguration();
return $success;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Service for setting cookies like Typo3 does
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class JsonService extends AbstractExtensionService
{
/**
* Encode a string to JSON
* Log a warning on error
*
* @param mixed $content
* @return string|false The domain to be used on setting cookies
*/
public function encodeToJson($content)
{
if (!empty($content)) {
try {
return json_encode($content, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$this->logger->log(
LogLevel::WARNING,
LocalizationUtility::translate('system.warning.json.encode', 'ThRating', [
1 => $content,
]),
[
'errorCode' => 1615051494,
'JSON' => $content,
'Exception' => $e,
]
);
}
}
return false;
}
/**
* Encode a string to JSON
* Log a warning on error
*
* @param string|null $content
* @return array|false The domain to be used on setting cookies
*/
public function decodeJsonToArray(string $content=null)
{
if (!empty($content)) {
try {
return json_decode($content, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$this->logger->log(
LogLevel::WARNING,
LocalizationUtility::translate('system.warning.json.encode', 'ThRating', [
1 => $content,
]),
[
'errorCode' => 1615051494,
'JSON' => $content,
'Exception' => $e,
]
);
}
}
return false;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Core\Log\Logger;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Log\Writer\DatabaseWriter;
use TYPO3\CMS\Core\Log\Writer\FileWriter;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
/**
* Factory for model objects
*
* @version $Id:$
* @license http://opensource.org/licenses/gpl-license.php GNU protected License, version 2
*/
class LoggingService
{
/**
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
*/
protected $configurationManager;
/**
* Constructor
* Must overrule the abstract class method to avoid self referencing
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager,
*/
public function __construct(
ObjectManagerInterface $objectManager,
ConfigurationManagerInterface $configurationManager
) {
$this->objectManager = $objectManager;
$this->configurationManager = $configurationManager;
}
/**
* Get a logger instance
* The configuration of the logger is modified by extension typoscript config
*
* @param string $name the class name which this logger is for
* @return \TYPO3\CMS\Core\Log\Logger
*/
public function getLogger(string $name): Logger
{
/** @var array $writerConfiguration */
$writerConfiguration = $GLOBALS['TYPO3_CONF_VARS']['LOG']['Thucke']['ThRating']['writerConfiguration'];
$settings = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS,
'thRating',
'pi1'
);
if (is_array($settings['logging'])) {
foreach ($settings['logging'] as $logLevel => $logConfig) {
$levelUppercase = strtoupper($logLevel);
if (!empty($logConfig['file'])) {
$writerConfiguration[constant('\TYPO3\CMS\Core\Log\LogLevel::' . $levelUppercase)][FileWriter::class] = ['logFile' => $logConfig['file']];
}
if (!empty($logConfig['database'])) {
$writerConfiguration[constant('\TYPO3\CMS\Core\Log\LogLevel::' . $levelUppercase)][DatabaseWriter::class] = ['table' => $logConfig['table']];
}
}
}
if (!empty($writerConfiguration)) {
$GLOBALS['TYPO3_CONF_VARS']['LOG']['Thucke']['ThRating']['writerConfiguration'] = $writerConfiguration;
}
return $this->objectManager->get(LogManager::class)->getLogger($name);
}
}

View File

@@ -0,0 +1,442 @@
<?php
/*
* This file is part of the package thucke/th-rating.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace WapplerSystems\BookmarksLikesRatings\Service;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Log\LogLevel;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* The voter
*/
class RichSnippetService extends AbstractExtensionService
{
/**
* Instances of AggregateRating may appear as properties of the following types
* This list derives from Google's information about supported types in aggregateRating (20 May 2021)
* (see https://developers.google.com/search/docs/data-types/review-snippet#aggregated-rating-type-definition)
* @const array
*/
protected const VALID_AGGREGATE_RATING_SCHEMA_TYPES = [
'Book',
'Audiobook',
'Course',
'CreativeWorkSeason',
'PodcastSeason',
'RadioSeason',
'TVSeason',
'CreativeWorkSeries',
'BookSeries',
'MovieSeries',
'Periodical',
'ComicSeries',
'Newspaper',
'PodcastSeries',
'RadioSeries',
'TVSeries',
'VideoGameSeries',
'Episode',
'PodcastEpisode',
'RadioEpisode',
'TVEpisode',
'Event',
'BusinessEvent',
'ChildrensEvent',
'ComedyEvent',
'CourseInstance',
'DanceEvent',
'DeliveryEvent',
'EducationEvent',
'EventSeries',
'ExhibitionEvent',
'Festival',
'FoodEvent',
'Hackathon',
'LiteraryEvent',
'MusicEvent',
'PublicationEvent',
'BroadcastEvent',
'OnDemandEvent',
'SaleEvent',
'ScreeningEvent',
'SocialEvent',
'SportsEvent',
'TheaterEvent',
'VisualArtsEvent',
'Game',
'VideoGame',
'HowTo',
'Recipe',
'AnimalShelter',
'ArchiveOrganization',
'AutomotiveBusiness',
'AutoBodyShop',
'AutoDealer',
'AutoRental',
'AutoRepair',
'AutoWash',
'GasStation',
'MotorcycleDealer',
'MotorcycleRepair',
'ChildCare',
'DryCleaningOrLaundry',
'EmergencyService',
'FireStation',
'Hospital',
'PoliceStation',
'EmploymentAgency',
'EntertainmentBusiness',
'AdultEntertainment',
'AmusementPark',
'ArtGallery',
'Casino',
'ComedyClub',
'MovieTheater',
'NightClub',
'FinancialService',
'AccountingService',
'AutomatedTeller',
'BankOrCreditUnion',
'InsuranceAgency',
'FoodEstablishment',
'Bakery',
'BarOrPub',
'Brewery',
'CafeOrCoffeeShop',
'Distillery',
'FastFoodRestaurant',
'IceCreamShop',
'Restaurant',
'Winery',
'GovernmentOffice',
'PostOffice',
'HealthAndBeautyBusiness',
'BeautySalon',
'DaySpa',
'HairSalon',
'NailSalon',
'TattooParlor',
'HomeAndConstructionBusiness',
'Electrician',
'GeneralContractor',
'HVACBusiness',
'HousePainter',
'Locksmith',
'MovingCompany',
'Plumber',
'RoofingContractor',
'InternetCafe',
'LegalService',
'Attorney',
'Notary',
'Library',
'LodgingBusiness',
'BedAndBreakfast',
'Campground',
'Hostel',
'Hotel',
'Motel',
'Resort',
'MedicalBusiness',
'CovidTestingFacility',
'Optician',
'Pharmacy',
'Physician',
'ProfessionalService',
'RadioStation',
'RealEstateAgent',
'RecyclingCenter',
'SelfStorage',
'ShoppingCenter',
'SportsActivityLocation',
'BowlingAlley',
'ExerciseGym',
'GolfCourse',
'HealthClub',
'PublicSwimmingPool',
'SkiResort',
'SportsClub',
'StadiumOrArena',
'TennisComplex',
'Store',
'AutoPartsStore',
'BikeStore',
'BookStore',
'ClothingStore',
'ComputerStore',
'ConvenienceStore',
'DepartmentStore',
'ElectronicsStore',
'Florist',
'FurnitureStore',
'GardenStore',
'GroceryStore',
'HardwareStore',
'HobbyShop',
'HomeGoodsStore',
'JewelryStore',
'LiquorStore',
'MensClothingStore',
'MobilePhoneStore',
'MovieRentalStore',
'MusicStore',
'OfficeEquipmentStore',
'OutletStore',
'PawnShop',
'PetStore',
'ShoeStore',
'SportingGoodsStore',
'TireShop',
'ToyStore',
'WholesaleStore',
'TelevisionStation',
'TouristInformationCenter',
'TravelAgency',
'MediaObject',
'3DModel',
'AudioObject',
'DataDownload',
'ImageObject',
'Barcode',
'LegislationObject',
'MusicVideoObject',
'VideoObject',
'Movie',
'MusicPlaylist',
'MusicAlbum',
'MusicRelease',
'MusicRecording',
'Organization',
'Airline',
'Consortium',
'Corporation',
'EducationalOrganization',
'CollegeOrUniversity',
'ElementarySchool',
'HighSchool',
'MiddleSchool',
'Preschool',
'School',
'FundingScheme',
'GovernmentOrganization',
'LibrarySystem',
'LocalBusiness',
'MedicalOrganization',
'NGO',
'NewsMediaOrganization',
'Dentist',
'DiagnosticLab',
'MedicalClinic',
'VeterinaryCare',
'PerformingGroup',
'DanceGroup',
'MusicGroup',
'TheaterGroup',
'Project',
'FundingAgency',
'ResearchProject',
'SportsOrganization',
'SportsTeam',
'WorkersUnion',
'Product',
'IndividualProduct',
'ProductCollection',
'ProductGroup',
'ProductModel',
'SomeProducts',
'Vehicle',
'BusOrCoach',
'Car',
'Motorcycle',
'MotorizedBicycle',
'SoftwareApplication',
'MobileApplication',
'WebApplication'
];
/**
* @var string
*/
protected $schema = 'Product';
/**
* @var string
*/
protected $anchor;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description;
/**
* @var array
*/
protected $richSnippetConfig;
/**
* @param array $settings
* @return bool
*/
public function setRichSnippetConfig(array $settings): bool
{
$this->logger->log(LogLevel::DEBUG, 'setRichSnippetConfig Entry point', $settings);
$this->richSnippetConfig['tablename'] = $settings['ratetable'];
$this->richSnippetConfig['richSnippetFields'] = $settings['richSnippetFields'];
if (is_array($this->richSnippetConfig['richSnippetFields'])) {
$this->logger->log(
LogLevel::DEBUG,
'setRichSnippetConfig Exit point',
$this->richSnippetConfig['richSnippetFields']
);
return true;
}
$this->logger->log(LogLevel::DEBUG, 'setRichSnippetConfig Exit point');
return false;
}
/**
* @return string|false
*/
public function getRichSnippetConfig()
{
return GeneralUtility::makeInstance(\WapplerSystems\BookmarksLikesRatings\Service\JsonService::class)
->encodeToJson($this->richSnippetConfig);
}
/**
* @return string
*/
public function getSchema(): string
{
return $this->schema;
}
/**
* @param string|null $schema
* @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException if parameter is invalid
*/
public function setSchema(?string $schema): void
{
if (!empty($schema)) {
if (in_array($schema, self::VALID_AGGREGATE_RATING_SCHEMA_TYPES, true)) {
$this->schema = $schema;
} else {
throw new \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException(
LocalizationUtility::translate(
'error.richSnippetConfiguration.AggregateRatingPropertySchema',
'ThRating'
),
1521487362
);
}
}
}
/**
* @return string
*/
public function getAnchor(): string
{
return $this->anchor;
}
/**
* @param string $anchor
*/
public function setAnchor(string $anchor): void
{
$this->anchor = $anchor;
}
/**
* @param string|null $name
*/
public function setName(?string $name): void
{
$this->name = $name;
}
/**
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* @return string|null
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @param string|null $description
*/
public function setDescription(?string $description): void
{
$this->description = $description;
}
/**
* @param int $uid
* @return RichSnippetService
*@throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException
*/
public function getRichSnippetObject(int $uid): self
{
$this->logger->log(LogLevel::DEBUG, 'getRichSnippetObject Entry point');
$this->setSchema($this->richSnippetConfig['richSnippetFields']['aggregateRatingSchemaType']);
if (empty($this->richSnippetConfig['richSnippetFields']['name'])) {
$this->logger->log(LogLevel::INFO, 'No name field defined - skipping database access');
unset($this->name, $this->description);
throw new \TYPO3\CMS\Core\Exception();
} else {
/** @var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($this->richSnippetConfig['tablename']);
//fetch whole row from database
/** @var array $row */
$row = $queryBuilder
->select('*')
->from($this->richSnippetConfig['tablename'])
->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid)))
->execute()
->fetch();
$this->logger->log(LogLevel::DEBUG, 'Data fetched', $row);
$this->setName($row[$this->richSnippetConfig['richSnippetFields']['name']]);
$contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$this->setDescription($row[$this->richSnippetConfig['richSnippetFields']['description']]);
//$this->setDescription($contentObjectRenderer->cObjGetSingle('TEXT', $this->richSnippetConfig['richSnippetFields']['description']));
//$this->setDescription($contentObjectRenderer->render($this->richSnippetConfig['richSnippetFields']['description']));
}
$this->logger->log(LogLevel::DEBUG, 'getRichSnippetObject Exit point', (array)$this);
return $this;
}
}