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�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; } }