From ce6b9e38dc1a15cb35271696608c85619fca2135 Mon Sep 17 00:00:00 2001 From: Sven Wappler Date: Tue, 17 Aug 2021 19:45:38 +0200 Subject: [PATCH] Zwischenstand --- .editorconfig | 51 + Classes/Controller/BookmarksController.php | 6 +- Classes/Controller/VoteController.php | 1316 +++++++++++++++++ Classes/{ => Domain}/Model/Bookmark.php | 6 +- Classes/{ => Domain}/Model/Bookmarks.php | 2 +- Classes/Domain/Model/Rating.php | 410 +++++ Classes/Domain/Model/RatingImage.php | 196 +++ Classes/Domain/Model/Ratingobject.php | 257 ++++ Classes/Domain/Model/Stepconf.php | 271 ++++ Classes/Domain/Model/Stepname.php | 158 ++ Classes/Domain/Model/Vote.php | 242 +++ Classes/Domain/Model/Voter.php | 17 + .../Domain/Repository/RatingRepository.php | 89 ++ .../Repository/RatingobjectRepository.php | 114 ++ .../Domain/Repository/StepconfRepository.php | 78 + .../Domain/Repository/StepnameRepository.php | 255 ++++ Classes/Domain/Repository/VoteRepository.php | 105 ++ Classes/Domain/Repository/VoterRepository.php | 31 + Classes/Domain/Validator/RatingValidator.php | 60 + .../Validator/RatingobjectValidator.php | 54 + .../Domain/Validator/StepconfValidator.php | 185 +++ .../Domain/Validator/StepnameValidator.php | 72 + Classes/Domain/Validator/VoteValidator.php | 79 + Classes/Service/AbstractExtensionService.php | 53 + Classes/Service/AccessControlService.php | 187 +++ Classes/Service/AccessException.php | 21 + Classes/Service/CookieService.php | 139 ++ Classes/Service/Exception.php | 19 + .../Service/ExtensionConfigurationService.php | 233 +++ Classes/Service/ExtensionHelperService.php | 720 +++++++++ .../Service/ExtensionManagementService.php | 185 +++ Classes/Service/JsonService.php | 81 + Classes/Service/LoggingService.php | 83 ++ Classes/Service/RichSnippetService.php | 442 ++++++ Classes/ViewHelpers/BookmarkViewHelper.php | 245 +++ Classes/ViewHelpers/LikeViewHelper.php | 245 +++ Classes/ViewHelpers/RatingViewHelper.php | 257 ++++ .../Widgets/Provider/TopLikesDataProvider.php | 38 + Classes/Widgets/TopLikesWidget.php | 70 + .../Backend/DashboardWidgetGroups.php | 7 + Configuration/Backend/DashboardWidgets.yaml | 16 + Configuration/FlexForms/Bookmarks.xml | 12 +- Configuration/Services.yaml | 12 + Configuration/TCA/Overrides/sys_template.php | 2 +- Configuration/TCA/Overrides/tt_content.php | 41 +- ...kmarkslikesratings_domain_model_rating.php | 100 ++ ...likesratings_domain_model_ratingobject.php | 111 ++ ...arkslikesratings_domain_model_stepconf.php | 142 ++ ...arkslikesratings_domain_model_stepname.php | 131 ++ ...ookmarkslikesratings_domain_model_vote.php | 108 ++ Configuration/TypoScript/constants.typoscript | 6 +- Documentation/AdministratorManual/Index.rst | 28 - Documentation/Changelog/2.0.0.rst | 39 - Documentation/Configuration/Index.rst | 97 -- Documentation/Images/screenshot-fe.png | Bin 16333 -> 0 bytes Documentation/Images/screenshot-styled-fe.png | Bin 13366 -> 0 bytes Documentation/Includes.txt | 14 - Documentation/Index.rst | 57 - Documentation/Introduction/Index.rst | 33 - Documentation/Settings.cfg | 17 - Documentation/Sitemap/Index.rst | 12 - Documentation/Support/Index.rst | 28 - LICENSE.txt | 339 ----- README.md | 28 - Resources/Private/Language/locallang.xlf | 28 +- Resources/Private/Layouts/Default.html | 2 +- .../Private/Templates/Bookmarks/Index.html | 3 +- composer.json | 9 +- ext_emconf.php | 22 +- ext_localconf.php | 40 +- ext_tables.sql | 139 +- 71 files changed, 7886 insertions(+), 809 deletions(-) create mode 100644 .editorconfig create mode 100644 Classes/Controller/VoteController.php rename Classes/{ => Domain}/Model/Bookmark.php (95%) rename Classes/{ => Domain}/Model/Bookmarks.php (99%) create mode 100644 Classes/Domain/Model/Rating.php create mode 100644 Classes/Domain/Model/RatingImage.php create mode 100644 Classes/Domain/Model/Ratingobject.php create mode 100644 Classes/Domain/Model/Stepconf.php create mode 100644 Classes/Domain/Model/Stepname.php create mode 100644 Classes/Domain/Model/Vote.php create mode 100644 Classes/Domain/Model/Voter.php create mode 100644 Classes/Domain/Repository/RatingRepository.php create mode 100644 Classes/Domain/Repository/RatingobjectRepository.php create mode 100644 Classes/Domain/Repository/StepconfRepository.php create mode 100644 Classes/Domain/Repository/StepnameRepository.php create mode 100644 Classes/Domain/Repository/VoteRepository.php create mode 100644 Classes/Domain/Repository/VoterRepository.php create mode 100644 Classes/Domain/Validator/RatingValidator.php create mode 100644 Classes/Domain/Validator/RatingobjectValidator.php create mode 100644 Classes/Domain/Validator/StepconfValidator.php create mode 100644 Classes/Domain/Validator/StepnameValidator.php create mode 100644 Classes/Domain/Validator/VoteValidator.php create mode 100644 Classes/Service/AbstractExtensionService.php create mode 100644 Classes/Service/AccessControlService.php create mode 100644 Classes/Service/AccessException.php create mode 100644 Classes/Service/CookieService.php create mode 100644 Classes/Service/Exception.php create mode 100644 Classes/Service/ExtensionConfigurationService.php create mode 100644 Classes/Service/ExtensionHelperService.php create mode 100644 Classes/Service/ExtensionManagementService.php create mode 100644 Classes/Service/JsonService.php create mode 100644 Classes/Service/LoggingService.php create mode 100644 Classes/Service/RichSnippetService.php create mode 100644 Classes/ViewHelpers/BookmarkViewHelper.php create mode 100644 Classes/ViewHelpers/LikeViewHelper.php create mode 100644 Classes/ViewHelpers/RatingViewHelper.php create mode 100644 Classes/Widgets/Provider/TopLikesDataProvider.php create mode 100644 Classes/Widgets/TopLikesWidget.php create mode 100644 Configuration/Backend/DashboardWidgetGroups.php create mode 100644 Configuration/Backend/DashboardWidgets.yaml create mode 100644 Configuration/Services.yaml create mode 100644 Configuration/TCA/tx_bookmarkslikesratings_domain_model_rating.php create mode 100644 Configuration/TCA/tx_bookmarkslikesratings_domain_model_ratingobject.php create mode 100644 Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepconf.php create mode 100644 Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepname.php create mode 100644 Configuration/TCA/tx_bookmarkslikesratings_domain_model_vote.php delete mode 100644 Documentation/AdministratorManual/Index.rst delete mode 100644 Documentation/Changelog/2.0.0.rst delete mode 100644 Documentation/Configuration/Index.rst delete mode 100644 Documentation/Images/screenshot-fe.png delete mode 100644 Documentation/Images/screenshot-styled-fe.png delete mode 100644 Documentation/Includes.txt delete mode 100644 Documentation/Index.rst delete mode 100644 Documentation/Introduction/Index.rst delete mode 100644 Documentation/Settings.cfg delete mode 100644 Documentation/Sitemap/Index.rst delete mode 100644 Documentation/Support/Index.rst delete mode 100644 LICENSE.txt delete mode 100644 README.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..70e4fec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,51 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# TS/JS-Files +[*.{ts,js}] +indent_size = 2 + +# JSON-Files +[*.json] +indent_style = tab + +# ReST-Files +[*.rst] +indent_size = 3 +max_line_length = 80 + +# YAML-Files +[*.{yaml,yml}] +indent_size = 2 + +# package.json +[package.json] +indent_size = 2 + +# TypoScript +[*.{typoscript,tsconfig}] +indent_size = 2 + +# XLF-Files +[*.xlf] +indent_style = tab + +# SQL-Files +[*.sql] +indent_style = tab +indent_size = 2 + +# .htaccess +[{_.htaccess,.htaccess}] +indent_style = tab diff --git a/Classes/Controller/BookmarksController.php b/Classes/Controller/BookmarksController.php index 759b3ab..e7795cc 100644 --- a/Classes/Controller/BookmarksController.php +++ b/Classes/Controller/BookmarksController.php @@ -7,10 +7,10 @@ * LICENSE file that was distributed with this source code. */ -namespace WapplerSystems\BookmarkPages\Controller; +namespace WapplerSystems\BookmarksLikesRatings\Controller; -use WapplerSystems\BookmarkPages\Model\Bookmark; -use WapplerSystems\BookmarkPages\Model\Bookmarks; +use WapplerSystems\BookmarksLikesRatings\Model\Bookmark; +use WapplerSystems\BookmarksLikesRatings\Model\Bookmarks; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; diff --git a/Classes/Controller/VoteController.php b/Classes/Controller/VoteController.php new file mode 100644 index 0000000..c8cbd44 --- /dev/null +++ b/Classes/Controller/VoteController.php @@ -0,0 +1,1316 @@ +accessControlService = $accessControlService; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\JsonService + */ + protected $jsonService; + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\JsonService $jsonService + */ + public function injectJsonService(\WapplerSystems\BookmarksLikesRatings\Service\JsonService $jsonService) + { + $this->jsonService = $jsonService; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\RichSnippetService + */ + protected $richSnippetService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\RichSnippetService $richSnippetService + */ + public function injectRichSnippetService(RichSnippetService $richSnippetService): void + { + $this->richSnippetService = $richSnippetService; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\CookieService + */ + protected $cookieService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\CookieService $cookieService + */ + public function injectCookieService(CookieService $cookieService): void + { + $this->cookieService = $cookieService; + } + + /** + * @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\Domain\Validator\VoteValidator + */ + protected $voteValidator; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Validator\VoteValidator $voteValidator + */ + public function injectVoteValidator(VoteValidator $voteValidator): void + { + $this->voteValidator = $voteValidator; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Domain\Validator\RatingValidator + */ + protected $ratingValidator; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Validator\RatingValidator $ratingValidator + */ + public function injectRatingValidator(RatingValidator $ratingValidator): void + { + $this->ratingValidator = $ratingValidator; + } + + /** + * @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\StepconfRepository + */ + protected $stepconfRepository; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepconfRepository $stepconfRepository + */ + public function injectStepconfRepository(StepconfRepository $stepconfRepository): void + { + $this->stepconfRepository = $stepconfRepository; + } + + /** + * @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 \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService + */ + public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService): void + { + $this->extensionHelperService = $extensionHelperService; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionConfigurationService + */ + protected $extensionConfigurationService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionConfigurationService $extensionConfigurationService + */ + public function injectExtensionConfigurationService(ExtensionConfigurationService $extensionConfigurationService): void + { + $this->extensionConfigurationService = $extensionConfigurationService; + } + + /** + * Lifecycle-Event + * wird nach der Initialisierung des Objekts und nach dem Auflösen der Dependencies aufgerufen. + */ + public function initializeObject(): void + { + } + + /** + * Initializes the current action + * + * @throws FeUserStoragePageException + * @throws InvalidStoragePageException + * @throws \Exception + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + */ + /** @noinspection PhpMissingParentCallCommonInspection */ + protected function initializeAction(): void + { + //instantiate the logger + $this->logger = GeneralUtility::makeInstance(ObjectManager::class) + ->get(ExtensionHelperService::class)->getLogger(__CLASS__); + + $this->logger->log(LogLevel::DEBUG, 'Entry point'); + + $this->prefixId = + strtolower("tx_{$this->request->getControllerExtensionName()}_{$this->request->getPluginName()}"); + + //Set default storage pids + $this->extensionConfigurationService->setExtDefaultQuerySettings(); + + //make current request object avaiable to other classes + $this->extensionHelperService->setRequest($this->request); + + $frameworkConfiguration = $this->configurationManager->getConfiguration( + ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK + ); + + if ($this->request->hasArgument(self::AJAX_REFERENCE_ID)) { + //switch to JSON respone on AJAX request + $this->request->setFormat('json'); + $this->defaultViewObjectName = JsonView::class; + //read unique AJAX identification on AJAX request + $this->ajaxSelections['ajaxRef'] = $this->request->getArgument(self::AJAX_REFERENCE_ID); + /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ + $this->settings = $this->jsonService->decodeJsonToArray($this->request->getArgument('settings')); + $frameworkConfiguration['settings'] = $this->settings; + $this->logger->log( + LogLevel::INFO, + 'AJAX request detected - set new frameworkConfiguration', + $frameworkConfiguration + ); + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->settings,get_class($this).' initializeAction'); + } else { + //set unique AJAX identification + $this->ajaxSelections['ajaxRef'] = $this->prefixId . '_' . $this->getRandomId(); + $this->logger->log(LogLevel::DEBUG, 'Set id for AJAX requests', $this->ajaxSelections); + } + + if (!is_array($frameworkConfiguration['ratings'])) { + $frameworkConfiguration['ratings'] = []; + } + ArrayUtility::mergeRecursiveWithOverrule( + $this->settings['ratingConfigurations'], + $frameworkConfiguration['ratings'] + ) + ; + $this->setCookieProtection(); + } + + /** + * Index action for this controller. + */ + public function indexAction(): void + { + // @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $ratingobject + //update foreign table for each rating + foreach ($this->ratingobjectRepository->findAll() as $ratingobject) { + foreach ($ratingobject->getRatings() as $rating) { + $this->setForeignRatingValues($rating); + } + } + $this->view->assign('ratingobjects', $this->ratingobjectRepository->findAll()); + + //initialize ratingobject and autocreate four ratingsteps + $ratingobject = $this->objectManager + ->get(ExtensionManagementService::class) + ->makeRatable('TestTable', 'TestField', 4); + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf */ + $stepconf = $ratingobject->getStepconfs()->current(); + + //add descriptions in default language to each stepconf + $this->objectManager->get(ExtensionManagementService::class)->setStepname( + $stepconf, + 'Automatic generated entry ', + null, + true + ); + //add descriptions in german language to each stepconf + $this->objectManager->get(ExtensionManagementService::class)->setStepname( + $stepconf, + 'Automatischer Eintrag ', + 'de', + true + ); + } + + /** + * Includes the hidden form to handle AJAX requests + * + * @noinspection PhpUnused + */ + public function singletonAction(): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry singletonAction'); + + $messageArray = $this->extensionHelperService->renderDynCSS(); + //generate dynamic CSS file and add messages to flashMessageQueue + foreach ($messageArray as $message) { + $this->logFlashMessage( + $message['messageText'], + $message['messageTitle'], + $message['severity'], + $message['additionalInfo'] + ); + } + $this->controllerContext->getFlashMessageQueue()->clear(); + $this->logger->log(LogLevel::DEBUG, 'Exit singletonAction'); + } + + /** + * Displays the vote of the current user + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote + * @Extbase\IgnoreValidation("vote") + * @throws \TYPO3\CMS\Core\Exception + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + * @noinspection PhpUnused + */ + public function showAction(Vote $vote = null): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry showAction'); + //is_object($vote) && \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($vote->getUid(),'showAction'); + $this->initVoting($vote); //just to set all properties + + $this->fillSummaryView(); + if (!$this->voteValidator->validate($this->vote)->hasErrors()) { + if ($this->accessControlService->isLoggedIn($vote->getVoter()) || $vote->isAnonymous()) { + } else { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.noPermission', 'ThRating'), + LocalizationUtility::translate('flash.heading.error', 'ThRating'), + 'ERROR', + ['errorCode' => 1403201246] + ); + } + } + $this->view->assign('actionMethodName', $this->actionMethodName); + $this->logger->log(LogLevel::DEBUG, 'Exit showAction'); + } + + /** + * Creates a new vote + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote + * @throws \TYPO3\CMS\Core\Exception + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException + * @noinspection PhpUnused + */ + public function createAction(\WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote): void + { + //http://localhost:8503/index.php?id=71&tx_thrating_pi1[controller]=Vote&tx_thrating_pi1[action]=create& + //tx_thrating_pi1[vote][rating]=1&tx_thrating_pi1[vote][voter]=1&tx_thrating_pi1[vote][vote]=1 + $this->logger->log(LogLevel::DEBUG, 'Entry createAction', ['errorCode' => 1404934047]); + if ($this->accessControlService->isLoggedIn($vote->getVoter()) || $vote->isAnonymous()) { + $this->logger->log(LogLevel::DEBUG, 'Start processing', ['errorCode' => 1404934054]); + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $matchVote */ + $matchVote = null; + + //if not anonymous check if vote is already done + if (!$vote->isAnonymous()) { + $this->logger->log( + LogLevel::DEBUG, + 'FE user is logged in - looking for existing vote', + ['errorCode' => 1404933999] + ); + $matchVote = $this->voteRepository->findMatchingRatingAndVoter($vote->getRating(), $vote->getVoter()); + } + //add new or anonymous vote + if ($this->voteValidator->validate($matchVote)->hasErrors() || $vote->isAnonymous()) { + $this->logger->log(LogLevel::DEBUG, 'New vote could be added', ['errorCode' => 1404934012]); + $vote->getRating()->addVote($vote); + if ($this->cookieProtection && $vote->isAnonymous() && !$vote->hasAnonymousVote($this->prefixId)) { + $this->logger->log( + LogLevel::DEBUG, + 'Anonymous rating; preparing cookie potection', + ['errorCode' => 1404934021] + ); + $anonymousRating['ratingtime'] = time(); + $anonymousRating['voteUid'] = $vote->getUid(); + //TODO: switch to session store according to https://t3terminal.com/blog/de/typo3-cookie/ + if (!empty($this->cookieLifetime)) { + $expireTime = (new \DateTime('NOW'))->add(\DateInterval::createFromDateString($this->cookieLifetime . ' days'))->getTimestamp(); + + //set cookie to prevent multiple anonymous ratings + $this->cookieService->setVoteCookie( + $this->prefixId . '_AnonymousRating_' . $vote->getRating()->getUid(), + $this->jsonService->encodeToJson($anonymousRating), + $expireTime + ); + } + } + $setResult = $this->setForeignRatingValues($vote->getRating()); + if (!$setResult) { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.foreignUpdateFailed', 'ThRating'), + LocalizationUtility::translate('flash.heading.warning', 'ThRating'), + 'WARNING', + [ + 'errorCode' => 1403201551, + 'ratingobject' => $vote->getRating()->getRatingobject()->getUid(), + 'ratetable' => $vote->getRating()->getRatingobject()->getRatetable(), + 'ratefield' => $vote->getRating()->getRatingobject()->getRatefield() + ] + ); + } + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.newCreated', 'ThRating'), + LocalizationUtility::translate('flash.heading.ok', 'ThRating'), + 'DEBUG', + [ + 'ratingobject' => $vote->getRating()->getRatingobject()->getUid(), + 'ratetable' => $vote->getRating()->getRatingobject()->getRatetable(), + 'ratefield' => $vote->getRating()->getRatingobject()->getRatefield(), + 'voter' => $vote->getVoter()->getUsername(), + 'vote' => (string)$vote->getVote() + ] + ); + } elseif (!empty($this->settings['enableReVote']) && + !$this->voteValidator->validate($matchVote)->hasErrors()) { + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $matchVoteStepconf */ + $matchVoteStepconf = $matchVote->getVote(); + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $newVoteStepconf */ + $newVoteStepconf = $vote->getVote(); + if ($matchVoteStepconf !== $newVoteStepconf) { + //do update of existing vote + $this->logFlashMessage( + LocalizationUtility::translate( + 'flash.vote.create.updateExistingVote', + 'ThRating', + [$matchVoteStepconf->getSteporder(), (string)$matchVoteStepconf] + ), + LocalizationUtility::translate('flash.heading.ok', 'ThRating'), + 'DEBUG', + [ + 'voter UID' => $vote->getVoter()->getUid(), + 'ratingobject UID' => $vote->getRating()->getRatingobject()->getUid(), + 'rating' => $vote->getRating()->getUid(), + 'vote UID' => $vote->getUid(), + 'new vote' => (string)$vote->getVote(), + 'old vote' => (string)$matchVoteStepconf + ] + ); + $vote->getRating()->updateVote($matchVote, $vote); + } else { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.noUpdateSameVote', 'ThRating'), + LocalizationUtility::translate('flash.heading.warning', 'ThRating'), + 'WARNING', + [ + 'voter UID' => $vote->getVoter()->getUid(), + 'ratingobject UID' => $vote->getRating()->getRatingobject()->getUid(), + 'rating' => $vote->getRating()->getUid(), + 'vote UID' => $vote->getUid(), + 'new vote' => (string)$newVoteStepconf, + 'old vote' => (string)$matchVoteStepconf + ] + ); + } + } else { + //display message that rating has been already done + $vote = $matchVote; + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.alreadyRated', 'ThRating'), + LocalizationUtility::translate('flash.heading.notice', 'ThRating'), + 'NOTICE', + [ + 'errorCode' => 1403202280, + 'voter UID' => $vote->getVoter()->getUid(), + 'ratingobject UID' => $vote->getRating()->getRatingobject()->getUid(), + 'rating' => $vote->getRating()->getUid(), + 'vote UID' => $vote->getUid() + ] + ); + } + $this->vote = $vote; + } else { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.create.noPermission', 'ThRating'), + LocalizationUtility::translate('flash.heading.error', 'ThRating'), + 'ERROR', + ['errorCode' => 1403203210] + ); + } + + $referrer = $this->request->getInternalArgument('__referrer'); + $newArguments = $this->request->getArguments(); + //replace vote argument with correct vote if user has already rated + $newArguments['vote']['vote'] = $this->vote->getVote(); + unset($newArguments['action'], $newArguments['controller']); + + //Send signal to connected slots + $this->initSignalSlotDispatcher('afterCreateAction'); + $newArguments = ['signalSlotHandlerContent' => $this->signalSlotHandlerContent] + $newArguments; + + $this->logger->log(LogLevel::DEBUG, 'Exit createAction - forwarding request', [ + 'action' => $referrer['@action'], /** @phpstan-ignore-line */ + 'controller' => $referrer['@controller'], /** @phpstan-ignore-line */ + 'extension' => $referrer['@extension'], /** @phpstan-ignore-line */ + 'arguments' => $newArguments + ]); + $this->controllerContext->getFlashMessageQueue()->clear(); + /** @var array $referrer */ + $this->forward($referrer['@action'], $referrer['@controller'], $referrer['@extension'], $newArguments); + } + + /** + * FE user gives a new vote by SELECT form + * A classic SELECT input form will be provided to AJAX-submit the vote + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote|null $vote The new vote (used on callback from createAction) + * @Extbase\IgnoreValidation("vote") + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException + * @throws \TYPO3\CMS\Core\Exception + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + * @noinspection PhpUnused + */ + public function newAction(Vote $vote = null): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry newAction'); + //find vote using additional information + $this->initSettings(); + $this->initVoting($vote); + $this->view->assign('actionMethodName', $this->actionMethodName); + if (!$this->vote->hasRated() || + (!$this->accessControlService->isLoggedIn($this->vote->getVoter()) && $this->vote->isAnonymous())) { + $this->view->assign('ajaxSelections', $this->ajaxSelections['json']); + } else { + $this->logger->log(LogLevel::INFO, 'New rating is not possible; forwarding to showAction'); + } + $this->fillSummaryView(); + if ($this->view instanceof JsonView) { + $this->view->assign('flashMessages', $this->view->getFlashMessages()); + } + $this->logger->log(LogLevel::DEBUG, 'Exit newAction'); + } + + /** + * FE user gives a new vote by using a starrating obejct + * A graphic starrating object containing links will be provided to AJAX-submit the vote + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote The new vote + * @Extbase\IgnoreValidation("vote") + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException* + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + * @throws \TYPO3\CMS\Core\Exception + */ + //http://localhost:8503/index.php?id=71&tx_thrating_pi1[controller]=Vote&tx_thrating_pi1[action]=ratinglinks + public function ratinglinksAction(Vote $vote = null): void + { + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->view,get_class($this).' ratinglinksAction'); + $this->logger->log(LogLevel::DEBUG, 'Entry ratinglinksAction'); + $this->settings['ratingConfigurations']['default'] = + $this->settings['defaultRatingConfiguration']['ratinglinks']; + $this->graphicActionHelper($vote); + $this->initSignalSlotDispatcher('afterRatinglinkAction'); + $this->logger->log(LogLevel::DEBUG, 'Exit ratinglinksAction'); + } + + /** + * Handle graphic pollings + * Graphic bars containing links will be provided to AJAX-submit the polling + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote The new vote + * @Extbase\IgnoreValidation("vote") + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException* + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + * @throws \TYPO3\CMS\Core\Exception + * @noinspection PhpUnused + */ + public function pollingAction(Vote $vote = null): void + { + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this->view,get_class($this).' pollingAction'); + $this->logger->log(LogLevel::DEBUG, 'Entry pollingAction'); + $this->settings['ratingConfigurations']['default'] = + $this->settings['defaultRatingConfiguration']['polling']; + + $this->graphicActionHelper($vote); + $this->initSignalSlotDispatcher('afterPollingAction'); + $this->logger->log(LogLevel::DEBUG, 'Exit pollingAction'); + } + + /** + * Handle mark action + * An icon containing for the mark action will be provided for AJAX-submission + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote The new vote + * @Extbase\IgnoreValidation("vote") + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException* + * @throws \TYPO3\CMS\Core\Exception + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + */ + public function markAction(\WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote = null): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry markAction'); + $this->settings['ratingConfigurations']['default'] = $this->settings['defaultRatingConfiguration']['mark']; + + $this->graphicActionHelper($vote); + + $this->initSignalSlotDispatcher('afterMarkAction'); + $this->logger->log(LogLevel::DEBUG, 'Exit markAction'); + } + + /** + * FE user gives a new vote by using a starrating obejct + * A graphic starrating object containing links will be provided to AJAX-submit the vote + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote|null $vote The new vote + * @Extbase\IgnoreValidation("vote") + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException* + * @throws \TYPO3\CMS\Core\Exception + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + */ + //http://localhost:8503/index.php?id=71&tx_thrating_pi1[controller]=Vote&tx_thrating_pi1[action]=ratinglinks + public function graphicActionHelper(Vote $vote = null): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry graphicActionHelper'); + $this->initSettings(); + $this->initVoting($vote); + $this->view->assign('actionMethodName', $this->actionMethodName); + + $rating = $this->vote->getRating(); + if (!$this->ratingValidator->validate($rating)->hasErrors()) { + $this->ratingImage = $this->objectManager->get(\WapplerSystems\BookmarksLikesRatings\Domain\Model\RatingImage::class); + $this->ratingImage->setConf($this->settings['ratingConfigurations'][$this->ratingName]['imagefile']); + //read dimensions of the image + $imageDimensions = $this->ratingImage->getImageDimensions(); + $height = $imageDimensions['height']; + $width = $imageDimensions['width']; + + //calculate concrete values for polling display + $currentRates = $rating->getCurrentrates(); + $currentPollDimensions = $currentRates['currentPollDimensions']; + + foreach ($currentPollDimensions as $step => $currentPollDimension) { + $currentPollDimensions[$step]['steporder'] = $step; + $currentPollDimensions[$step]['backgroundPos'] = round( + $height / 3 * (($currentPollDimension['pctValue'] / 100) - 2), + 1 + ); + $currentPollDimensions[$step]['backgroundPosTilt'] = round( + $width / 3 * (($currentPollDimension['pctValue'] / 100) - 2), + 1 + ); + } + + $this->logger->log( + LogLevel::DEBUG, + 'Current polling dimensions', + ['currentPollDimensions' => $currentPollDimensions] + ); + $this->view->assign('currentPollDimensions', $currentPollDimensions); + } + $this->view->assign('ratingName', $this->ratingName); + $this->view->assign('ratingClass', $this->settings['ratingClass']); + if ((!$this->vote->isAnonymous() && + $this->accessControlService->isLoggedIn($this->vote->getVoter()) && + (!$this->vote->hasRated() || !empty($this->settings['enableReVote']))) || + ( + ($this->vote->isAnonymous() && + !$this->accessControlService->isLoggedIn($this->vote->getVoter())) && + ( + (!$this->vote->hasAnonymousVote($this->prefixId) && + $this->cookieProtection && + !$this->request->hasArgument('settings')) || + !$this->cookieProtection + ) + ) + ) { + //if user hasn�t voted yet then include ratinglinks + $this->view->assign('ajaxSelections', $this->ajaxSelections['steporder']); + $this->logger->log( + LogLevel::INFO, + 'Set ratinglink information', + ['errorCode' => 1404933850, 'ajaxSelections[steporder]' => $this->ajaxSelections['steporder']] + ); + } + $this->fillSummaryView(); + if ($this->view instanceof JsonView) { + $this->view->assign( + 'flashMessages', + $this->view->getFlashMessages() + ); + } + $this->logger->log(LogLevel::DEBUG, 'Exit graphicActionHelper'); + } + + /** + * Initialize signalSlotHandler for given action + * Registered slots are being called with two parameters + * 1. signalSlotMessage: an array consisting of + * 'tablename' - the tablename of the rated object + * 'fieldname' - the fieldname of the rated object + * 'uid' - the uid of the rated object + * 'currentRates' - an array constising of the actual rating statistics + * 'currentrate' - the calculated overall rating + * 'weightedVotes' - an array giving the voting counts for every ratingstep + * 'sumWeightedVotes' an array giving the voting counts for every ratingstep multiplied by their weights + * 'anonymousVotes' - count of anonymous votings + * if the user has voted anonymous or non-anonymous: + * 'voter' - the uid of the frontenduser that has voted + * 'votingStep' - the ratingstep that has been choosen + * 'votingName' - the name of the ratingstep + * 'anonymousVote' - boolean info if it was an anonymous rating + * + * @param string $slotName the slotname + * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException + * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException + */ + protected function initSignalSlotDispatcher(string $slotName): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry initSignalSlotDispatcher', ['slotName' => $slotName]); + if ($this->request->hasArgument('signalSlotHandlerContent')) { + //set orginal handlerContent if action has been forwarded + $this->signalSlotHandlerContent = $this->request->getArgument('signalSlotHandlerContent'); + $this->logger->log( + LogLevel::INFO, + 'Fetch static SignalSlotHandlerContent', + ['signalSlotHandlerContent' => $this->signalSlotHandlerContent] + ); + } else { + $signalSlotMessage = []; + $signalSlotMessage['tablename'] = $this->vote->getRating()->getRatingobject()->getRatetable(); + $signalSlotMessage['fieldname'] = $this->vote->getRating()->getRatingobject()->getRatefield(); + $signalSlotMessage['uid'] = (int)$this->vote->getRating()->getRatedobjectuid(); + $signalSlotMessage['currentRates'] = $this->vote->getRating()->getCurrentrates(); + if (!$this->voteValidator->validate($this->vote)->hasErrors()) { + $signalSlotMessage['voter'] = $this->vote->getVoter()->getUid(); + $signalSlotMessage['votingStep'] = $this->vote->getVote()->getSteporder(); + $signalSlotMessage['votingName'] = (string)$this->vote->getVote()->getStepname(); + $signalSlotMessage['anonymousVote'] = $this->vote->isAnonymous(); + } + $this->logger->log( + LogLevel::INFO, + 'Going to process signalSlot', + ['signalSlotMessage' => $signalSlotMessage] + ); + + //clear signalSlotHandlerArray for sure + $this->signalSlotHandlerContent = []; + $this->signalSlotDispatcher->dispatch( + __CLASS__, + $slotName, + [$signalSlotMessage, &$this->signalSlotHandlerContent] + ); + $this->logger->log( + LogLevel::INFO, + 'New signalSlotHandlerContent', + ['signalSlotHandlerContent' => $this->signalSlotHandlerContent] + ); + } + $this->view->assign('staticPreContent', $this->signalSlotHandlerContent['staticPreContent']); + $this->view->assign('staticPostContent', $this->signalSlotHandlerContent['staticPostContent']); + unset( + $this->signalSlotHandlerContent['staticPreContent'], + $this->signalSlotHandlerContent['staticPostContent'] + ); + $this->view->assign('preContent', $this->signalSlotHandlerContent['preContent']); + $this->view->assign('postContent', $this->signalSlotHandlerContent['postContent']); + $this->logger->log(LogLevel::DEBUG, 'Exit initSignalSlotDispatcher'); + } + + /** + * Check preconditions for rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote the vote this selection is for + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\RecordNotFoundException + * @throws \TYPO3\CMS\Core\Exception + */ + protected function initVoting(Vote $vote = null): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry initVoting'); + + $logVoterUid = 0; + + if ($this->voteValidator->isObjSet($vote) && !$this->voteValidator->validate($vote)->hasErrors()) { + $this->vote = $vote; + $this->logger->log(LogLevel::DEBUG, 'Using valid vote'); + } else { + $this->logger->log(LogLevel::DEBUG, 'initVoting ELSE'); + //first initialize parent objects for vote object + $ratingobject = $this->extensionHelperService->getRatingobject($this->settings); + $rating = $this->extensionHelperService->getRating($this->settings, $ratingobject); + $this->vote = $this->extensionHelperService->getVote($this->prefixId, $this->settings, $rating); + + $countSteps = count($ratingobject->getStepconfs()); + if (empty($countSteps)) { + $this->logger->log(LogLevel::DEBUG, 'No ratingsteps configured', ['errorCode' => 1403201012]); + $this->logFlashMessage( + LocalizationUtility::translate('flash.ratingobject.noRatingsteps', 'ThRating'), + LocalizationUtility::translate('flash.heading.error', 'ThRating'), + 'ERROR', + ['errorCode' => 1403201012] + ); + } + + if (!$this->vote->getVoter() instanceof \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter) { + if (!empty($this->settings['showNoFEUser'])) { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.noFEuser', 'ThRating'), + LocalizationUtility::translate('flash.heading.notice', 'ThRating'), + 'NOTICE', + ['errorCode' => 1403201096] + ); + } + } else { + $logVoterUid = $this->vote->getVoter()->getUid(); + } + } + $this->logger->log(LogLevel::INFO, 'Using vote', [ + 'ratingobject' => $this->vote->getRating()->getRatingobject()->getUid(), + 'rating' => $this->vote->getRating()->getUid(), + 'voter' => $logVoterUid + ]); + //set array to create voting information + $this->setAjaxSelections($this->vote); + $this->logger->log(LogLevel::DEBUG, 'Exit initVoting'); + } + + /** + * Check preconditions for settings + */ + protected function initSettings(): void + { + $this->logger->log(LogLevel::DEBUG, 'Entry initSettings'); + + //switch display mode to correct config if nothing is set + if (empty($this->settings['display'])) { + $this->settings['display'] = $this->settings['ratingConfigurations']['default']; + } + + //set display configuration + if (!empty($this->settings['display'])) { + if (isset($this->settings['ratingConfigurations'][$this->settings['display']])) { + $this->ratingName = $this->settings['display']; + } else { + //switch back to default if given display configuration does not exist + $this->ratingName = $this->settings['ratingConfigurations']['default']; + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.ratinglinks.wrongDisplayConfig', 'ThRating'), + LocalizationUtility::translate('flash.heading.error', 'ThRating'), + 'WARNING', + [ + 'errorCode' => 1403203414, + 'settings display' => $this->settings['display'], + 'avaiable ratingConfigurations' => $this->settings['ratingConfigurations'] + ] + ); + } + } else { + //choose default ratingConfiguration if nothing is defined + $this->ratingName = $this->settings['ratingConfigurations']['default']; + $this->logger->log( + LogLevel::WARNING, + 'Display name not set - using configured default', + ['default display' => $this->ratingName] + ); + } + $ratingConfiguration = $this->settings['ratingConfigurations'][$this->ratingName]; + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump( + //$this->settings,get_class($this).' settings '.$this->ratingName); + + //override extension settings with rating configuration settings + if (is_array($ratingConfiguration['settings'])) { + unset( + $ratingConfiguration['settings']['defaultObject'], + $ratingConfiguration['settings']['ratingConfigurations'] + ); + if (!is_array($ratingConfiguration['ratings'])) { + $ratingConfiguration['ratings'] = []; + } + ArrayUtility::mergeRecursiveWithOverrule($this->settings, $ratingConfiguration['settings']); + $this->logger->log( + LogLevel::DEBUG, + 'Override extension settings with rating configuration settings', + ['Original setting' => $this->settings, 'Overruling settings' => $ratingConfiguration['settings']] + ); + } + //override fluid settings with rating fluid settings + if (is_array($ratingConfiguration['fluid'])) { + ArrayUtility::mergeRecursiveWithOverrule($this->settings['fluid'], $ratingConfiguration['fluid']); + $this->logger->log(LogLevel::DEBUG, 'Override fluid settings with rating fluid settings'); + } + $this->logger->log(LogLevel::INFO, 'Final extension configuration', ['settings' => $this->settings]); + + //distinguish between bar and no-bar rating + $this->view->assign('barimage', 'noratingbar'); + if ($ratingConfiguration['barimage']) { + $this->view->assign('barimage', 'ratingbar'); + $this->logger->log(LogLevel::DEBUG, 'Set ratingbar config'); + } + + //set tilt or normal rating direction + $this->settings['ratingClass'] = 'normal'; + if ($ratingConfiguration['tilt']) { + $this->logger->log(LogLevel::DEBUG, 'Tilt rating class configuration'); + $this->settings['ratingClass'] = 'tilt'; + } + + $frameworkConfiguration = + $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK); + $frameworkConfiguration['settings'] = $this->settings; + + $this->configurationManager->setConfiguration($frameworkConfiguration); + $this->setCookieProtection(); + + $this->logger->log(LogLevel::DEBUG, 'Exit initSettings'); + } + + /** + * Build array of possible AJAX selection configuration + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote the vote this selection is for + */ + protected function setAjaxSelections(Vote $vote): void + { + if (empty($this->settings['displayOnly']) && $vote->getVoter() instanceof Voter) { + //cleanup settings to reduce data size in POST form + $tmpDisplayConfig = $this->settings['ratingConfigurations'][$this->settings['display']]; + unset($this->settings['defaultObject'], $this->settings['ratingConfigurations']); + $this->settings['ratingConfigurations'][$this->settings['display']] = $tmpDisplayConfig; + //TODO: ?? $currentRates = $vote->getRating()->getCurrentrates(); + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepConf */ + foreach ($vote->getRating()->getRatingobject()->getStepconfs() as $i => $stepConf) { + /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ + $key = utf8_encode( + $this->jsonService->encodeToJson([ + 'value' => $stepConf->getUid(), + 'voter' => $vote->getVoter()->getUid(), + 'rating' => $vote->getRating()->getUid(), + 'ratingName' => $this->ratingName, + 'settings' => $this->jsonService->encodeToJson($this->settings), + 'actionName' => strtolower($this->request->getControllerActionName()), + self::AJAX_REFERENCE_ID => $this->ajaxSelections['ajaxRef'] + ]) + ); + $this->ajaxSelections['json'][$key] = (string)$stepConf; + $this->ajaxSelections['steporder'][$stepConf->getSteporder()]['step'] = $stepConf; + $this->ajaxSelections['steporder'][$stepConf->getSteporder()]['ajaxvalue'] = $key; + } + $this->logger->log( + LogLevel::DEBUG, + 'Finalized ajaxSelections', + ['ajaxSelections' => $this->ajaxSelections] + ); + } + } + + /** + * Fill all variables for FLUID + * + * @throws \WapplerSystems\BookmarksLikesRatings\Exception\InvalidAggregateRatingSchemaTypeException + */ + protected function fillSummaryView() + { + $this->view->assign('settings', $this->settings); + $this->view->assign('ajaxRef', $this->ajaxSelections['ajaxRef']); + $this->view->assign('rating', $this->vote->getRating()); + $this->view->assign('voter', $this->vote->getVoter()); + + if ($this->richSnippetService->setRichSnippetConfig($this->settings)) { + /** @var \WapplerSystems\BookmarksLikesRatings\Service\RichSnippetService $richSnippetObject */ + $richSnippetObject = + $this->richSnippetService->getRichSnippetObject($this->vote->getRating()->getRatedobjectuid()); + $richSnippetObject->setAnchor('RatingAX_' . $this->vote->getRating()->getRatingobject()->getUid() . + '_' . $this->vote->getRating()->getRatedobjectuid()); + if (empty($richSnippetObject->getName())) { + $richSnippetObject->setName('Rating AX ' . $this->vote->getRating()->getRatingobject()->getUid() . + '_' . $this->vote->getRating()->getRatedobjectuid()); + } + $this->view->assign('richSnippetObject', $richSnippetObject); + } + + $currentrate = $this->vote->getRating()->getCurrentrates(); + $this->view->assign('stepCount', count($currentrate['weightedVotes'])); + $this->view->assign('anonymousVotes', $currentrate['anonymousVotes']); + $this->view->assign( + 'anonymousVoting', + !empty($this->settings['mapAnonymous']) && !$this->accessControlService->getFrontendUserUid() + ); + if ($this->settings['showNotRated'] && empty($currentrate['currentrate'])) { + $this->logFlashMessage( + LocalizationUtility::translate('flash.vote.show.notRated', 'ThRating'), + LocalizationUtility::translate('flash.heading.info', 'ThRating'), + 'INFO', + ['errorCode' => 1403203414] + ); + } + if (!$this->voteValidator->validate($this->vote)->hasErrors()) { + /** @noinspection NotOptimalIfConditionsInspection */ + if (( + !$this->vote->isAnonymous() && + $this->vote->getVoter()->getUid() === $this->accessControlService->getFrontendUserUid() + ) || ($this->vote->isAnonymous() && ($this->vote->hasAnonymousVote($this->prefixId) || + $this->cookieProtection || $this->cookieService->isProtected()))) { + $this->view->assign('protected', $this->cookieService->isProtected()); + $this->view->assign('voting', $this->vote); + $this->view->assign( + 'usersRate', + $this->vote->getVote()->getSteporder() * 100 / count($currentrate['weightedVotes']) . '%' + ); + } + } + //$this->view->assign('LANG', \WapplerSystems\BookmarksLikesRatings\Utility\LocalizationUtility::getLangArray('ThRating')); + } + + /** + * Override getErrorFlashMessage to present + * nice flash error messages. + * + * @return string + */ + protected function getErrorFlashMessage() + { + switch ($this->actionMethodName) { + case 'createAction': + return 'Could not create the new vote:'; + case 'showAction': + return 'Could not show vote!'; + default: + return parent::getErrorFlashMessage(); + } + } + + /** + * Generates a random number + * used as the unique iddentifier for AJAX objects + * + * @return int + * @throws \Exception + */ + protected function getRandomId() + { + mt_srand((int)microtime() * 1000000); + return random_int(1000000, 9999999); + } + + protected function setCookieProtection(): void + { + $this->cookieLifetime = abs((int)$this->settings['cookieLifetime']); + $this->logger->log( + LogLevel::DEBUG, + 'Cookielifetime set to ' . $this->cookieLifetime . ' days', + ['errorCode' => 1465728751] + ); + if (empty($this->cookieLifetime)) { + $this->cookieProtection = false; + } else { + $this->cookieProtection = true; + } + } + + /** + * Sends log information to flashMessage and logging framework + * + * @param string $messageText + * @param string $messageTitle + * @param string $severity + * @param array $additionalInfo + */ + private function logFlashMessage( + string $messageText, + string $messageTitle, + string $severity, + array $additionalInfo + ) { + $additionalInfo = ['messageTitle' => $messageTitle] + $additionalInfo; + $severity = strtoupper($severity); + switch ($severity) { + case 'DEBUG': + $flashSeverity = 'OK'; + break; + case 'INFO': + $flashSeverity = 'INFO'; + break; + case 'NOTICE': + $flashSeverity = 'NOTICE'; + break; + case 'WARNING': + $flashSeverity = 'WARNING'; + break; + default: + $flashSeverity = 'ERROR'; + } + if ((int)$additionalInfo['errorCode']) { + $messageText .= ' (' . $additionalInfo['errorCode'] . ')'; + } + + //TODO: locally enqueue flashmessages of setStoragePids when controllerContext has not been set yet + if (is_object($this->controllerContext)) { + $this->addFlashMessage( + $messageText, + $messageTitle, + constant('\TYPO3\CMS\Core\Messaging\AbstractMessage::' . $flashSeverity) + ); + } + $this->logger->log(constant('\TYPO3\CMS\Core\Log\LogLevel::' . $severity), $messageText, $additionalInfo); + } + + /** + * Sets the rating values in the foreign table + * Recommended field type is VARCHAR(255) + * + * @param Rating $rating The rating + * @return bool + */ + protected function setForeignRatingValues(Rating $rating) + { + $table = $rating->getRatingobject()->getRatetable(); + $lockedFieldnames = $this->getLockedfieldnames($table); + $rateField = $rating->getRatingobject()->getRatefield(); + if (!empty($GLOBALS['TCA'][$table]['columns'][$rateField]) && !in_array($rateField, $lockedFieldnames, false)) { + $rateUid = $rating->getRatedobjectuid(); + $currentRatesArray = $rating->getCurrentrates(); + if (empty($this->settings['foreignFieldArrayUpdate'])) { + //do update using DOUBLE value + $currentRates = round($currentRatesArray['currentrate'], 2); + } else { + //do update using whole currentrates JSON array + $currentRates = $this->jsonService->encodeToJson($currentRatesArray); + } + + /** @var \TYPO3\CMS\Core\Database\Query\QueryBuilder $queryBuilder */ + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable($table); + + //do update foreign table + $queryResult = $queryBuilder + ->update($table) + ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($rateUid))) + ->set($rateField, $currentRates) + ->execute(); + + return !empty($queryResult); + } + + $this->logger->log( + LogLevel::NOTICE, + 'Foreign ratefield does not exist in ratetable or is locked for rating updates', + [ + 'ratingobject UID' => $rating->getRatingobject()->getUid(), + 'ratetable' => $rating->getRatingobject()->getRatetable(), + 'ratefield' => $rating->getRatingobject()->getRatefield() + ] + ); + + return true; + } + + /** + * Create a list of fieldnamed that must not be updated with ratingvalues + * + * @param string $table tablename looking for system fields + * @return array + */ + protected function getLockedfieldnames($table) + { + $TCA = &$GLOBALS['TCA'][$table]['ctrl']; // Set private TCA var + + /** @var array $lockedFields */ + $lockedFields = GeneralUtility::intExplode(',', $TCA['label_alt'], true); + array_push( + $lockedFields, + 'pid', + 'uid', + 'pages', + 'pages_language_overlay', + $TCA['label'], + $TCA['tstamp'], + $TCA['crdate'], + $TCA['cruser_id'], + $TCA['delete'], + $TCA['enablecolumns']['disabled'], + $TCA['enablecolumns']['starttime'], + $TCA['enablecolumns']['endtime'], + $TCA['enablecolumns']['fe_group'], + $TCA['selicon_field'], + $TCA['sortby'], + $TCA['editlock'], + $TCA['origUid'], + $TCA['fe_cruser_id'], + $TCA['fe_crgroup_id'], + $TCA['fe_admin_lock'], + $TCA['languageField'], + $TCA['transOrigPointerField'], + $TCA['transOrigDiffSourceField'] + ); + + return $lockedFields; + } + + /** + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected function getTypoScriptFrontendController() + { + /** @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $TSFE */ + global $TSFE; + + return $TSFE; + } + + /** + * Demo slotHandler for slot 'afterRatinglinkAction' + * + * @param array $signalSlotMessage array containing signal information + * @param array $customContent array by reference to return pre and post content + */ + public function afterRatinglinkActionHandler( + /** @noinspection PhpUnusedParameterInspection */ + $signalSlotMessage, + &$customContent + ) { + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($signalSlotMessage,'signalSlotMessage'); + $customContent['preContent'] = 'This ist my preContent'; + $customContent['staticPreContent'] = 'This ist my staticPreContent'; + $customContent['postContent'] = 'This ist my postContent'; + $customContent['staticPostContent'] = 'This ist my stticPostContent'; + } + + /** + * Demo slotHandler for slot 'afterCreateAction' + * + * @param array $signalSlotMessage array containing signal information + * @param array $customContent array by reference to return pre and post content + */ + /** @noinspection PhpUnusedParameterInspection */ + public function afterCreateActionHandler( + /** @noinspection PhpUnusedParameterInspection */ + $signalSlotMessage, + &$customContent + ) { + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($signalSlotMessage,'signalSlotMessage'); + $customContent['preContent'] = 'This ist my preContent after afterCreateActionHandler'; + $customContent['staticPreContent'] = 'This ist my staticPreContent after afterCreateActionHandler'; + $customContent['postContent'] = 'This ist my postContent after afterCreateActionHandler'; + } +} diff --git a/Classes/Model/Bookmark.php b/Classes/Domain/Model/Bookmark.php similarity index 95% rename from Classes/Model/Bookmark.php rename to Classes/Domain/Model/Bookmark.php index bc49897..55a670b 100644 --- a/Classes/Model/Bookmark.php +++ b/Classes/Domain/Model/Bookmark.php @@ -7,7 +7,7 @@ * LICENSE file that was distributed with this source code. */ -namespace WapplerSystems\BookmarkPages\Model; +namespace WapplerSystems\BookmarksLikesRatings\Domain\Model; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -47,7 +47,7 @@ class Bookmark * @param null $pid page id * @param null $parameter */ - public function __construct($url, $title=null, $pid=null, $parameter=null) + public function __construct($url, $title = null, $pid = null, $parameter = null) { if (is_array($url)) { $this->id = $url['id']; @@ -223,7 +223,7 @@ class Bookmark */ protected static function getCurrentPageTitle() { - return self::getFrontend()->altPageTitle? self::getFrontend()->altPageTitle : self::getFrontend()->page['title']; + return self::getFrontend()->altPageTitle ? self::getFrontend()->altPageTitle : self::getFrontend()->page['title']; } /** diff --git a/Classes/Model/Bookmarks.php b/Classes/Domain/Model/Bookmarks.php similarity index 99% rename from Classes/Model/Bookmarks.php rename to Classes/Domain/Model/Bookmarks.php index d21e23b..4c873fc 100644 --- a/Classes/Model/Bookmarks.php +++ b/Classes/Domain/Model/Bookmarks.php @@ -7,7 +7,7 @@ * LICENSE file that was distributed with this source code. */ -namespace WapplerSystems\BookmarkPages\Model; +namespace WapplerSystems\BookmarksLikesRatings\Domain\Model; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; diff --git a/Classes/Domain/Model/Rating.php b/Classes/Domain/Model/Rating.php new file mode 100644 index 0000000..f5f7546 --- /dev/null +++ b/Classes/Domain/Model/Rating.php @@ -0,0 +1,410 @@ + + */ + protected $votes; + + /** + * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface + */ + protected $objectManager; + /** + * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager + */ + /** @noinspection PhpUnused */ + public function injectObjectManager(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\JsonService + */ + protected $jsonService; + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\JsonService $jsonService + */ + public function injectJsonService(JsonService $jsonService) + { + $this->jsonService = $jsonService; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoteRepository + */ + protected $voteRepository; + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\VoteRepository $voteRepository + */ + public function injectVoteRepository(VoteRepository $voteRepository) + { + $this->voteRepository = $voteRepository; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService + */ + /** @noinspection PhpUnused */ + public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService) + { + $this->extensionHelperService = $extensionHelperService; + } + + /** + * The current calculated rates + * + * Redundant information to enhance performance in displaying calculated information + * This is a JSON encoded string with the following keys + * - votecounts(1...n) vote counts of the specific ratingstep + * It be updated every time a vote is created, changed or deleted. + * Specific handling must be defined when ratingsteps are added or removed or stepweights are changed + * Calculation of ratings: + * currentrate = ( sum of all ( stepweight(n) * votecounts(n) ) ) / number of all votes + * currentwidth = round (currentrate * 100 / number of ratingsteps, 1) + * + * @var string + */ + protected $currentrates; + + /** + * @var array + */ + protected $settings; + + /** + * Constructs a new rating object + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject|null $ratingobject + * @param int|null $ratedobjectuid + * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException + */ + public function __construct(Ratingobject $ratingobject = null, $ratedobjectuid = null) + { + if ($ratingobject) { + $this->setRatingobject($ratingobject); + } + if ($ratedobjectuid) { + $this->setRatedobjectuid($ratedobjectuid); + } + $this->initializeObject(); + } + + /** + * Initializes a new rating object + * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException + */ + public function initializeObject() + { + if (empty($this->objectManager)) { + $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class); + } + $this->settings = $this->objectManager + ->get(ConfigurationManager::class) + ->getConfiguration('Settings', 'thRating', 'pi1'); + + //Initialize vote storage if rating is new + if (!is_object($this->votes)) { + $this->votes = new ObjectStorage(); + } + } + + /** + * Sets the ratingobject this rating is part of + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $ratingobject The Rating + */ + public function setRatingobject(Ratingobject $ratingobject) + { + $this->ratingobject = $ratingobject; + $this->setPid($ratingobject->getPid()); + } + + /** + * Returns the ratingobject this rating is part of + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject The ratingobject this rating is part of + */ + public function getRatingobject(): Ratingobject + { + return $this->ratingobject; + } + + /** + * Sets the rating object uid + * + * @param int $ratedobjectuid + */ + public function setRatedobjectuid($ratedobjectuid) + { + $this->ratedobjectuid = $ratedobjectuid; + } + + /** + * Gets the rating object uid + * + * @return int Rating object row uid field value + */ + public function getRatedobjectuid() + { + return $this->ratedobjectuid; + } + + /** + * Adds a vote to this rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote + */ + public function addVote(Vote $vote) + { + $this->votes->attach($vote); + $this->addCurrentrate($vote); + $this->extensionHelperService->persistRepository(VoteRepository::class, $vote); + } + + /** + * Updates an existing vote to this rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $existingVote + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $newVote + */ + public function updateVote(Vote $existingVote, Vote $newVote) + { + $this->removeCurrentrate($existingVote); + $existingVote->setVote($newVote->getVote()); + $this->addCurrentrate($existingVote); + $this->extensionHelperService->persistRepository(VoteRepository::class, $existingVote); + } + + /** + * Remove a vote from this rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $voteToRemove The vote to be removed + */ + /** @noinspection PhpUnused */ + public function removeVote(Vote $voteToRemove) + { + $this->removeCurrentrate($voteToRemove); + $this->votes->detach($voteToRemove); + } + + /** + * Remove all votes from this rating + */ + /** @noinspection PhpUnused */ + public function removeAllVotes() + { + $this->votes = new ObjectStorage(); + unset($this->currentrates); + } + + /** + * Returns all votes in this rating + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote> + */ + public function getVotes() + { + return clone $this->votes; + } + + /** + * Checks all votes of this rating and sets currentrates accordingly + * + * This method is used for maintenance to assure consistency + */ + public function checkCurrentrates() + { + $currentratesDecoded['weightedVotes'] = []; + $currentratesDecoded['sumWeightedVotes'] = []; + $numAllVotes = 0; + $numAllAnonymousVotes = 0; + foreach ($this->getRatingobject()->getStepconfs() as $stepConf) { + $stepOrder = $stepConf->getSteporder(); + $voteCount = $this->voteRepository->countByMatchingRatingAndVote($this, $stepConf); + $anonymousCount = $this->voteRepository->countAnonymousByMatchingRatingAndVote( + $this, + $stepConf, + $this->settings['mapAnonymous'] + ); + $currentratesDecoded['weightedVotes'][$stepOrder] = $voteCount * $stepConf->getStepweight(); + $currentratesDecoded['sumWeightedVotes'][$stepOrder] = + $currentratesDecoded['weightedVotes'][$stepOrder] * $stepOrder; + $numAllVotes += $voteCount; + $numAllAnonymousVotes += $anonymousCount; + } + $currentratesDecoded['numAllVotes'] = $numAllVotes; + $currentratesDecoded['anonymousVotes'] = $numAllAnonymousVotes; + $this->currentrates = $this->jsonService->encodeToJson($currentratesDecoded); + } + + /** + * Adds a vote to the calculations of this rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $voting The vote to be added + */ + public function addCurrentrate(Vote $voting) + { + if (empty($this->currentrates)) { + $this->checkCurrentrates(); //initialize entry + } + $currentratesDecoded = $this->jsonService->decodeJsonToArray($this->currentrates); + $currentratesDecoded['numAllVotes']++; + if ($voting->isAnonymous()) { + $currentratesDecoded['anonymousVotes']++; + } + $votingStep = $voting->getVote(); + /** @noinspection NullPointerExceptionInspection */ + $votingSteporder = $votingStep->getSteporder(); + /** @noinspection NullPointerExceptionInspection */ + $votingStepweight = $votingStep->getStepweight(); + $currentratesDecoded['weightedVotes'][$votingSteporder] += $votingStepweight; + $currentratesDecoded['sumWeightedVotes'][$votingSteporder] += $votingStepweight * $votingSteporder; + $this->currentrates = $this->jsonService->encodeToJson($currentratesDecoded); + } + + /** + * Adds a vote to the calculations of this rating + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $voting The vote to be removed + */ + public function removeCurrentrate(Vote $voting) + { + if (empty($this->currentrates)) { + $this->checkCurrentrates(); //initialize entry + } + $currentratesDecoded = $this->jsonService->decodeJsonToArray($this->currentrates); + $currentratesDecoded['numAllVotes']--; + if ($voting->isAnonymous()) { + $currentratesDecoded['anonymousVotes']--; + } + $votingStep = $voting->getVote(); + /** @noinspection NullPointerExceptionInspection */ + $votingSteporder = $votingStep->getSteporder(); + /** @noinspection NullPointerExceptionInspection */ + $votingStepweight = $votingStep->getStepweight(); + $currentratesDecoded['weightedVotes'][$votingSteporder] -= $votingStepweight; + $currentratesDecoded['sumWeightedVotes'][$votingSteporder] -= $votingStepweight * $votingSteporder; + $this->currentrates = $this->jsonService->encodeToJson($currentratesDecoded); + } + + /** + * Returns the calculated rating + * + * @return array + */ + public function getCurrentrates(): array + { + $currentratesDecoded = $this->jsonService->decodeJsonToArray($this->currentrates); + if (empty($currentratesDecoded['numAllVotes'])) { + $this->checkCurrentrates(); + $currentratesDecoded = $this->jsonService->decodeJsonToArray($this->currentrates); + } + $numAllVotes = $currentratesDecoded['numAllVotes']; + if (!empty($numAllVotes)) { + $currentrate = array_sum($currentratesDecoded['sumWeightedVotes']) / $numAllVotes; + } else { + $currentrate = 0; + $numAllVotes = 0; + } + + $sumAllWeightedVotes = array_sum($currentratesDecoded['weightedVotes']); + + //initialize array to handle missing stepconfs correctly + $currentPollDimensions = []; + + foreach ($this->getRatingobject()->getStepconfs() as $stepConf) { + if (empty($sumAllWeightedVotes)) { + //set current polling styles to zero percent and prevent division by zero error in lower formula + $currentPollDimensions[$stepConf->getStepOrder()]['pctValue'] = 0; + } else { + /* calculate current polling styles -> holds a percent value for usage in CSS + to display polling relations */ + $currentPollDimensions[$stepConf->getStepOrder()]['pctValue'] = + round( + ($currentratesDecoded['weightedVotes'][$stepConf->getStepOrder()] * 100) / + $sumAllWeightedVotes, + 1 + ); + } + } + + return ['currentrate' => $currentrate, + 'weightedVotes' => $currentratesDecoded['weightedVotes'], + 'sumWeightedVotes' => $currentratesDecoded['sumWeightedVotes'], + 'anonymousVotes' => $currentratesDecoded['anonymousVotes'], + 'currentPollDimensions' => $currentPollDimensions, + 'numAllVotes' => $numAllVotes, ]; + } + + /** + * Returns the calculated rating in percent + * + * @return int + */ + public function getCalculatedRate(): int + { + $currentrate = $this->getCurrentrates(); + if (!empty($currentrate['weightedVotes'])) { + $calculatedRate = round(($currentrate['currentrate'] * 100) / count($currentrate['weightedVotes']), 1); + } else { + $calculatedRate = 0; + } + return $calculatedRate; + } +} diff --git a/Classes/Domain/Model/RatingImage.php b/Classes/Domain/Model/RatingImage.php new file mode 100644 index 0000000..1dfcbea --- /dev/null +++ b/Classes/Domain/Model/RatingImage.php @@ -0,0 +1,196 @@ +gifBuilder = $gifBuilder; + } + + /** + * Constructs a new image object + * + * @param mixed $conf either an array consisting of GIFBUILDER typoscript or a plain string having the filename + */ + public function __construct($conf = null) + { + $this->initializeObject(); + if (!empty($conf)) { + $this->setConf($conf); + } + } + + /** + * Initializes the new vote object + */ + public function initializeObject() + { + if (empty($this->gifBuilder)) { + $this->injectGifBuilder(GeneralUtility::makeInstance(GifBuilder::class)); + } + } + + /** + * Sets the typoscript configuration for the GIFBUILDER object + * + * @param mixed $conf either an array consisting of GIFBUILDER typoscript or a plain string having the filename + */ + public function setConf($conf) + { + switch (gettype($conf)) { + case 'string': + $this->setImageFile($conf); + break; + case 'array': + $this->conf = $conf; + $this->generateImage(); + break; + default: + //TODO: Error message + } + } + + /** + * Returns the current typoscript configuration of the GIFBUILDER object + * + * @return array + */ + public function getConf(): array + { + if (empty($this->conf)) { + return []; + } + + return $this->conf; + } + + /** + * Sets the filename of the image + * + * @param string $imageFile + */ + public function setImageFile($imageFile) + { + $fullImagePath = Environment::getPublicPath() . '/' . $imageFile; + if (file_exists($fullImagePath)) { + $this->imageFile = $imageFile; + $this->isBuilderObject = false; + } else { + //clear path if given file is invalid + unset($this->imageFile, $this->isBuilderObject); + //TODO: error handling + } + } + + /** + * Returns the filename of the image + * + * @param mixed $fullPath + * @return string + */ + public function getImageFile($fullPath = false) + { + $checkedFile = $this->gifBuilder->checkFile($this->imageFile); + if (empty($checkedFile)) { + //clear image if file doe not exist + $this->setImageFile('xxx'); + } + return $fullPath ? Environment::getPublicPath() . '/' . $this->imageFile : $this->imageFile; + } + + /** + * Generates the image using the given typoscript + * + * @return bool The result; true if the given the image has been created successfully; otherwise false + */ + public function generateImage() + { + if (!empty($this->conf)) { + $this->gifBuilder->start($this->getConf(), []); + $genImageFile = $this->gifBuilder->gifBuild(); + if (!file_exists($genImageFile)) { + //TODO: error handling + return false; + } + $this->setImageFile($genImageFile); + $this->isBuilderObject = true; + + return true; + } + + return false; + } + + /** + * Returns the filename of the image + * + * @var bool switch if absolute path should be returned + * @return array('width','height') + */ + public function getImageDimensions() + { + if ($this->isBuilderObject) { + [$width, $height] = $this->gifBuilder->getImageDimensions($this->imageFile); + } else { + [$width, $height] = getimagesize($this->getImageFile(true)); + } + + return ['width' => $width, 'height' => $height, 'builderObject' => $this->isBuilderObject]; + } + + /** + * Method to use Object as plain string + * + * @return string + */ + public function __toString() + { + return $this->imageFile; + } +} diff --git a/Classes/Domain/Model/Ratingobject.php b/Classes/Domain/Model/Ratingobject.php new file mode 100644 index 0000000..42018e5 --- /dev/null +++ b/Classes/Domain/Model/Ratingobject.php @@ -0,0 +1,257 @@ + + */ + protected $stepconfs; + + /** + * The ratings of this object + * + * @Extbase\ORM\Lazy + * @Extbase\ORM\Cascade("remove") + * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating> + */ + protected $ratings; + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepconfRepository + */ + protected $stepconfRepository; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepconfRepository $stepconfRepository + */ + public function injectStepconfRepository(StepconfRepository $stepconfRepository) + { + $this->stepconfRepository = $stepconfRepository; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService + */ + /** @noinspection PhpUnused */ + public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService) + { + $this->extensionHelperService = $extensionHelperService; + } + + /** + * Constructs a new rating object + * @param string $ratetable The rating objects table name + * @param string $ratefield The rating objects field name + * @Extbase\Validate("StringLength", options={"minimum": 3}, param="ratetable") + * @Extbase\Validate("StringLength", options={"maximum": 60}, param="ratetable") + * @Extbase\Validate("StringLength", options={"minimum": 3}, param="ratefield") + * @Extbase\Validate("StringLength", options={"maximum": 60}, param="ratefield") + */ + public function __construct($ratetable = null, $ratefield = null) + { + if ($ratetable) { + $this->setRatetable($ratetable); + } + if ($ratefield) { + $this->setRatefield($ratefield); + } + $this->initializeObject(); + } + + /** + * Initializes a new ratingobject + */ + public function initializeObject() + { + //Initialize rating storage if ratingobject is new + if (!is_object($this->ratings)) { + $this->ratings = new ObjectStorage(); + } + //Initialize stepconf storage if ratingobject is new + if (!is_object($this->stepconfs)) { + $this->stepconfs = new ObjectStorage(); + } + } + + /** + * Sets the rating table name + * + * @param string $ratetable + */ + public function setRatetable($ratetable) + { + $this->ratetable = $ratetable; + } + + /** + * Gets the rating table name + * + * @return string Rating object table name + */ + public function getRatetable() + { + return $this->ratetable; + } + + /** + * Sets the rating field name + * + * @param string $ratefield + */ + public function setRatefield($ratefield) + { + $this->ratefield = $ratefield; + } + + /** + * Sets the rating field name + * + * @return string Rating object field name + */ + public function getRatefield() + { + return $this->ratefield; + } + + /** + * Adds a rating to this object + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating $rating + */ + /** @noinspection PhpUnused */ + public function addRating(Rating $rating) + { + $this->ratings->attach($rating); + $this->extensionHelperService->persistRepository(RatingRepository::class, $rating); + $this->extensionHelperService->clearDynamicCssFile(); + } + + /** + * Remove a rating from this object + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating $rating The rating to be removed + */ + public function removeRating(Rating $rating): void + { + $this->ratings->detach($rating); + } + + /** + * Remove all ratings from this object + */ + public function removeAllRatings(): void + { + $this->ratings = new ObjectStorage(); + } + + /** + * Adds a stepconf to this object + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf + */ + public function addStepconf(Stepconf $stepconf): void + { + if (!$this->stepconfRepository->existStepconf($stepconf)) { + $this->stepconfs->attach($stepconf); + $this->extensionHelperService->persistRepository(StepconfRepository::class, $stepconf); + $this->extensionHelperService->clearDynamicCssFile(); + } + } + + /** + * Sets all ratings of this ratingobject + * + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf> $stepconfs + * The step configurations for this ratingobject + */ + public function setStepconfs(ObjectStorage $stepconfs) + { + $this->stepconfs = $stepconfs; + } + + /** + * Returns all ratings in this object + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf> + */ + public function getStepconfs() + { + return clone $this->stepconfs; + } + + /** + * Sets all ratings of this ratingobject + * + * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating> $ratings + * The ratings of the organization + */ + public function setRatings(ObjectStorage $ratings) + { + $this->ratings = $ratings; + } + + /** + * Returns all ratings in this object + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating> + */ + public function getRatings() + { + return clone $this->ratings; + } +} diff --git a/Classes/Domain/Model/Stepconf.php b/Classes/Domain/Model/Stepconf.php new file mode 100644 index 0000000..d84e3da --- /dev/null +++ b/Classes/Domain/Model/Stepconf.php @@ -0,0 +1,271 @@ + + */ + protected $votes; + + /** + * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager + */ + protected $objectManager; + + /** + * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager + */ + public function injectObjectManager(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository $stepnameRepository + */ + protected $stepnameRepository; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository $stepnameRepository + */ + public function injectStepnameRepository(StepnameRepository $stepnameRepository) + { + $this->stepnameRepository = $stepnameRepository; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService + */ + public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService) + { + $this->extensionHelperService = $extensionHelperService; + } + + /** + * Constructs a new stepconfig object + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject|null $ratingobject + * @param int|null $steporder + */ + public function __construct(Ratingobject $ratingobject = null, $steporder = null) + { + if ($ratingobject) { + $this->setRatingobject($ratingobject); + } + if ($steporder) { + $this->setSteporder($steporder); + } + $this->initializeObject(); + } + + /** + * Initializes a new stepconf object + */ + public function initializeObject() + { + //Initialize vote storage if rating is new + if (!is_object($this->votes)) { + $this->votes = new ObjectStorage(); + } + } + + /** + * Sets the ratingobject this rating is part of + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $ratingobject The Rating + */ + public function setRatingobject(Ratingobject $ratingobject) + { + $this->ratingobject = $ratingobject; + $this->setPid($ratingobject->getPid()); + } + + /** + * Returns the ratingobject this rating is part of + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject|null + */ + public function getRatingobject() + { + return $this->ratingobject; + } + + /** + * Sets the stepconfig order + * + * @param int $steporder + */ + public function setSteporder($steporder) + { + $this->steporder = $steporder; + } + + /** + * Gets the stepconfig order + * + * @return int stepconfig position + */ + public function getSteporder(): int + { + return $this->steporder; + } + + /** + * Sets the stepconfig value + * + * @param int $stepweight + */ + public function setStepweight($stepweight) + { + $this->stepweight = $stepweight; + } + + /** + * Gets the stepconfig value + * If not set steporder is copied + * + * @return int Stepconfig value + */ + public function getStepweight() + { + empty($this->stepweight) && $this->stepweight = $this->steporder; + return $this->stepweight; + } + + /** + * Adds a localized stepname to this stepconf + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $stepname + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + * @return bool + */ + public function addStepname(Stepname $stepname): bool + { + $success = true; + $stepname->setStepconf($this); + if (!$this->stepnameRepository->existStepname($stepname)) { + $defaultLanguageObject = $this->stepnameRepository->findDefaultStepname($stepname); + if (is_object($defaultLanguageObject)) { + //handle localization if an entry for the default language exists + $stepname->setL18nParent($defaultLanguageObject->getUid()); + } + $this->stepnameRepository->add($stepname); + $this->extensionHelperService->persistRepository(StepnameRepository::class, $stepname); + $stepname->getStepconf()->getStepname(); + $this->extensionHelperService->persistRepository(StepconfRepository::class, $this); + $this->extensionHelperService->clearDynamicCssFile(); + $this->stepname = $stepname; + } else { + //warning - existing stepname entry for a language will not be overwritten + $success = false; + } + + return $success; + } + + /** + * Returns the localized stepname object of this stepconf + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname|null + */ + public function getStepname(): ?Stepname + { + /* @phpstan-ignore-next-line */ + if ($this->stepname instanceof LazyLoadingProxy) { + $this->stepname = $this->stepname->_loadRealInstance(); + } + return $this->stepname; + } + + /** + * Returns all votes in this rating + * + * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote> + */ + public function getVotes() + { + return clone $this->votes; + } + + /** + * Method to use Object as plain string + * + * @return string + */ + public function __toString() + { + $stepnameText = $this->getStepname(); + if (!$stepnameText) { + $stepnameText = $this->getSteporder(); + } + return (string)$stepnameText; + } +} diff --git a/Classes/Domain/Model/Stepname.php b/Classes/Domain/Model/Stepname.php new file mode 100644 index 0000000..ad32048 --- /dev/null +++ b/Classes/Domain/Model/Stepname.php @@ -0,0 +1,158 @@ +stepconf = $stepconf; + $this->setPid($stepconf->getPid()); + } + + /** + * Returns the stepconf this rating is part of + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf The stepconf this rating is part of + */ + public function getStepconf(): Stepconf + { + return $this->stepconf; + } + + /** + * Sets the stepconfig name + * + * @param string $stepname + */ + public function setStepname($stepname): void + { + $this->stepname = $stepname; + } + + /** + * Gets the stepconfig name + * If not set stepweight is copied + * + * @return string Stepconfig name + */ + public function getStepname(): string + { + $value = $this->stepname; + if (stripos($value, 'LLL:') === 0) { + $value = 'stepnames.' . substr($value, 4); + $value = LocalizationUtility::translate($value, 'ThRating'); + } + if (empty($value)) { + $value = (string)$this->getStepconf()->getSteporder(); + } + + return $value; + } + + /** + * @return int + */ + /** @noinspection PhpUnused */ + public function getL18nParent(): int + { + return $this->l18nParent; + } + + /** + * @param int $l18nParent + */ + public function setL18nParent($l18nParent): void + { + $this->l18nParent = $l18nParent; + } + + /** + * Get sys language + * + * @return int + */ + public function getSysLanguageUid(): int + { + return $this->_languageUid; + } + + /** + * Set sys language + * + * @param int $sysLanguageUid language uid + */ + public function setSysLanguageUid($sysLanguageUid): void + { + $this->_languageUid = $sysLanguageUid; + } + + /** + * @return bool + */ + public function isValid(): bool + { + return !empty($this->stepconf); + } + + /** + * Method to use Object as plain string + * + * @return string + */ + public function __toString(): string + { + return $this->getStepname(); + } +} diff --git a/Classes/Domain/Model/Vote.php b/Classes/Domain/Model/Vote.php new file mode 100644 index 0000000..bbc47ce --- /dev/null +++ b/Classes/Domain/Model/Vote.php @@ -0,0 +1,242 @@ +objectManager = $objectManager; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + /** + * @param \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService $extensionHelperService + */ + public function injectExtensionHelperService(ExtensionHelperService $extensionHelperService): void + { + $this->extensionHelperService = $extensionHelperService; + } + + /** + * Constructs a new rating object + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating|null $rating + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter|null $voter + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf|null $vote + * @throws InvalidConfigurationTypeException + */ + /** @noinspection PhpUnused */ + public function __construct( + Rating $rating = null, + Voter $voter = null, + Stepconf $vote = null + ) { + if ($rating) { + $this->setRating($rating); + } + if ($voter) { + $this->setVoter($voter); + } + if ($vote) { + $this->setVote($vote); + } + $this->initializeObject(); + } + + /** + * Initializes the new vote object + * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException + */ + public function initializeObject() + { + if (empty($this->objectManager)) { + $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class); + } + if (empty($this->extensionHelperService)) { + $this->extensionHelperService = GeneralUtility::makeInstance(ExtensionHelperService::class); + } + $this->logger = $this->extensionHelperService->getLogger(__CLASS__); + $this->settings = $this->objectManager->get(ConfigurationManager::class)->getConfiguration( + 'Settings', + 'thRating', + 'pi1' + ); + //\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($this,get_class($this).' initializeObject'); + } + + /** + * Sets the rating this vote is part of + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating $rating The Rating + */ + public function setRating(Rating $rating) + { + $this->rating = $rating; + $this->setPid($rating->getPid()); + } + + /** + * Returns the rating this vote is part of + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating The rating this vote is part of + */ + public function getRating(): Rating + { + return $this->rating; + } + + /** + * Sets the frontenduser of this vote + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter $voter The frontenduser + */ + public function setVoter(Voter $voter) + { + $this->voter = $voter; + } + + /** + * Returns the frontenduser of this vote + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Voter|null + */ + public function getVoter(): ?Voter + { + return $this->voter; + } + + /** + * Sets the choosen stepconfig + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $vote + */ + public function setVote($vote) + { + $this->vote = $vote; + } + + /** + * Gets the rating object uid + * + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf|null Reference to selected stepconfig + */ + public function getVote() + { + return $this->vote; + } + + /** + * Sets the rating this vote is part of + * + * @return bool + */ + /** @noinspection PhpUnused */ + public function hasRated() + { + return $this->getVote() !== null && ($this->getVote() instanceof Stepconf); + } + + /** + * Checks if vote is done by anonymous user + * + * @return bool + */ + public function isAnonymous(): bool + { + return !empty($this->settings['mapAnonymous']) && !empty($this->getVoter()) && + $this->getVoter()->getUid() === (int)$this->settings['mapAnonymous']; + } + + /** + * Checks cookie if anonymous vote is already done + * always false if cookie checks is deactivated + * + * @param string $prefixId Extension prefix to identify cookie + * @return bool + */ + public function hasAnonymousVote($prefixId = 'DummyPrefix'): bool + { + $anonymousRating = GeneralUtility::makeInstance(\WapplerSystems\BookmarksLikesRatings\Service\JsonService::class) + ->decodeJsonToArray($_COOKIE[$prefixId . '_AnonymousRating_' . $this->getRating()->getUid()]); + return !empty($anonymousRating['voteUid']); + } + + /** + * Method to use Object as plain string + * + * @return string + */ + public function __toString(): string + { + return (string)$this->getVote(); + } +} diff --git a/Classes/Domain/Model/Voter.php b/Classes/Domain/Model/Voter.php new file mode 100644 index 0000000..adabbb3 --- /dev/null +++ b/Classes/Domain/Model/Voter.php @@ -0,0 +1,17 @@ +extensionHelperService = $extensionHelperService; + } + + /** + * Finds the specific rating by giving the object and row uid + * + * @Extbase\Validate("\WapplerSystems\BookmarksLikesRatings\Domain\Validator\RatingobjectValidator", param="ratingobject") + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $ratingobject The concerned ratingobject + * @Extbase\Validate("NumberRange", options={"minimum": 1}, param="ratedobjectuid") + * @param int $ratedobjectuid The Uid of the rated row + * @param bool $addIfNotFound Set to true if new objects should instantly be added + * @return Rating + * @throws IllegalObjectTypeException + */ + public function findMatchingObjectAndUid( + Ratingobject $ratingobject, + int $ratedobjectuid, + $addIfNotFound = false + ): Rating { + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Rating $foundRow */ + $foundRow = $this->objectManager->get(Rating::class); + + $query = $this->createQuery(); + $query->matching($query->logicalAnd( + [ + $query->equals('ratingobject', $ratingobject->getUid()), + $query->equals('ratedobjectuid', $ratedobjectuid) + ] + ))->setLimit(1); + /*$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL(), get_class($this).' SQL'); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters(), get_class($this).' SQL Parameter');*/ + + $queryResult = $query->execute(); + if ($queryResult->count() > 0) { + $foundRow = $queryResult->getFirst(); + } elseif ($addIfNotFound) { + $foundRow->setRatingobject($ratingobject); + $foundRow->setRatedobjectuid($ratedobjectuid); + $validator = $this->objectManager->get(RatingValidator::class); + if (!$validator->validate($foundRow)->hasErrors()) { + $this->add($foundRow); + } + $this->extensionHelperService->persistRepository(__CLASS__, $foundRow); + $foundRow = $this->findMatchingObjectAndUid($ratingobject, $ratedobjectuid); + } + + return $foundRow; + } +} diff --git a/Classes/Domain/Repository/RatingobjectRepository.php b/Classes/Domain/Repository/RatingobjectRepository.php new file mode 100644 index 0000000..c74d4bd --- /dev/null +++ b/Classes/Domain/Repository/RatingobjectRepository.php @@ -0,0 +1,114 @@ +extensionHelperService = $extensionHelperService; + } + + /** + * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface + */ + protected $configurationManager; + /** + * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager + */ + public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void + { + $this->configurationManager = $configurationManager; + } + + /** + * Finds the specific ratingobject by giving table and fieldname + * + * @param string $ratetable The tablename of the ratingobject + * @param string $ratefield The fieldname of the ratingobject + * @param bool $addIfNotFound Set to true if new objects should instantly be added + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject The ratingobject + * @throws RecordNotFoundException + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException + */ + public function findMatchingTableAndField(string $ratetable, string $ratefield, bool $addIfNotFound = false): Ratingobject + { + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Ratingobject $foundRow */ + $foundRow = $this->objectManager->get(Ratingobject::class); + + $query = $this->createQuery(); + + $query->matching($query->logicalAnd([ + $query->equals('ratetable', $ratetable), + $query->equals('ratefield', $ratefield), + ]))->setLimit(1); + + /*$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL(), get_class($this).' SQL'); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters(), get_class($this).' SQL Parameter');*/ + + /** @var \TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult */ + $queryResult = $query->execute(); + + if ($queryResult->count() > 0) { + $foundRow = $queryResult->getFirst(); + } elseif ($addIfNotFound) { + $foundRow->setRatetable($ratetable); + $foundRow->setRatefield($ratefield); + + if (!$this->objectManager->get(RatingobjectValidator::class)->validate($foundRow)->hasErrors()) { + $this->add($foundRow); + } + $this->extensionHelperService->persistRepository(self::class, $foundRow); + $foundRow = $this->findMatchingTableAndField($ratetable, $ratefield); + } else { + throw new RecordNotFoundException(LocalizationUtility::translate('recordNotFound', 'ThRating'), 1567962473); + } + return $foundRow; + } + + /** + * Finds the specific ratingobject by giving table and fieldname + * + * @param bool Switch to fetch ALL entries regardless of their pid + * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array All ratingobjects of the site + */ + /** @noinspection PhpMissingParentCallCommonInspection */ + public function findAll($ignoreStoragePage = false) + { + $query = $this->createQuery(); + $query->getQuerySettings()->setRespectStoragePage(!$ignoreStoragePage); + return $query->execute(); + } +} diff --git a/Classes/Domain/Repository/StepconfRepository.php b/Classes/Domain/Repository/StepconfRepository.php new file mode 100644 index 0000000..9df9c18 --- /dev/null +++ b/Classes/Domain/Repository/StepconfRepository.php @@ -0,0 +1,78 @@ + QueryInterface::ORDER_ASCENDING]; + + /** + * Initialize this repository + */ + public function initializeObject(): void + { + //disable RespectStoragePage as pid is always bound to parent objects pid + + /** @var \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $defaultQuerySettings */ + $defaultQuerySettings = $this->objectManager->get(QuerySettingsInterface::class); + $defaultQuerySettings->setRespectStoragePage(false); + $this->setDefaultQuerySettings($defaultQuerySettings); + } + + /** + * Finds the given stepconf object in the repository + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf The ratingobject to look for + * @return \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf + */ + public function findStepconfObject(Stepconf $stepconf): Stepconf + { + $query = $this->createQuery(); + /** @noinspection NullPointerExceptionInspection */ + $query->matching($query->logicalAnd([ + $query->equals('ratingobject', $stepconf->getRatingobject()->getUid()), + $query->equals('steporder', $stepconf->getSteporder()), + ]))->setLimit(1); + $queryResult = $query->execute(); + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $foundRow */ + $foundRow = $this->objectManager->get(Stepconf::class); + + if (count($queryResult) !== 0) { + $foundRow = $queryResult->getFirst(); + } + return $foundRow; + } + + /** + * Finds the ratingstep entry by giving ratingobjectUid + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf The uid of the ratingobject + * @return bool true if stepconf object exists in repository + */ + public function existStepconf(Stepconf $stepconf): bool + { + $foundRow = $this->findStepconfObject($stepconf); + $stepconfValidator = $this->objectManager->get(StepconfValidator::class); + + return !$stepconfValidator->validate($foundRow)->hasErrors(); + } +} diff --git a/Classes/Domain/Repository/StepnameRepository.php b/Classes/Domain/Repository/StepnameRepository.php new file mode 100644 index 0000000..58ccaeb --- /dev/null +++ b/Classes/Domain/Repository/StepnameRepository.php @@ -0,0 +1,255 @@ +syslangUidLiteral = $GLOBALS['TCA'][self::TABLE_NAME]['ctrl']['languageField']; + $this->defaultOrderings = [ $this->syslangUidLiteral => QueryInterface::ORDER_ASCENDING]; + } + + /** + * Checks if stepname got a valid language code + * + * @param Stepname $stepname The stepname object + * @return bool + */ + public function checkStepnameLanguage(Stepname $stepname): bool + { + $stepnameLang = $stepname->getSysLanguageUid(); + if ($stepnameLang > 0) { + //check if given language exist + + try { + // only get language and do not assign the result to check if it exists + $this->objectManager + ->get(ExtensionHelperService::class) + ->getStaticLanguageById($stepnameLang); + } catch (InvalidArgumentException $exception) { + //invalid language code -> NOK + return false; + } + } + //language code found -> OK + return true; + } + + /** + * Finds the given stepconf object in the repository + * + * @param Stepname $stepname The ratingname to look for + * @return Stepname|null + */ + public function findStepnameObject(Stepname $stepname): ?Stepname + { + $query = $this->createQuery(); + $query->getQuerySettings()->setRespectSysLanguage(false); + $query->getQuerySettings()->setLanguageOverlayMode(false); + $query->matching( + $query->logicalAnd( + [ + $query->equals(self::STEPCONF_NAME, $stepname->getStepconf()), + $query->equals($this->syslangUidLiteral, $stepname->getSysLanguageUid()), + ] + ) + )->setLimit(1); + + /** @var \TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult */ + $queryResult = $query->execute(); + + /* + $queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL(), get_class($this).' SQL'); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters(), get_class($this).' SQL Parameter'); + */ + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $foundRow */ + $foundRow = null; + + if ($queryResult->count() > 0) { + $foundRow = $queryResult->getFirst(); + } + return $foundRow; + } + + /** + * Finds the given stepname object in the repository + * + * @param int $uid + * @return Stepname|null + */ + public function findStrictByUid(int $uid): ?Stepname + { + $query = $this->createQuery(); + $query->getQuerySettings()->setRespectSysLanguage(false); + $query->getQuerySettings()->setRespectStoragePage(false); + $query->getQuerySettings()->setLanguageOverlayMode(false); + $query->matching( + $query->logicalAnd( + [$query->equals('uid', $uid)] + ) + )->setLimit(1); + $queryResult = $query->execute(); + + /** @var Stepname $foundRow */ + $foundRow = null; + if ($queryResult->count() > 0) { + $foundRow = $queryResult->getFirst(); + } + return $foundRow; + } + + /** + * Check on double language entries + * + * @param Stepname $stepname The ratingname to look for + * @return array return values false says OK + */ + public function checkConsistency(Stepname $stepname): array + { + $query = $this->createQuery(); + $query ->getQuerySettings()->setRespectSysLanguage(false); + $query ->matching( + $query->equals(self::STEPCONF_NAME, $stepname->getStepconf()->getUid()) + ); + $queryResult = $query + ->execute(true); + $checkConsistency = []; + if (count($queryResult) > 1) { + $websiteLanguagesArray = []; + $allWebsiteLanguages = $this->getCurrentSite()->getAllLanguages(); + + /** @var \TYPO3\CMS\Core\Site\Entity\SiteLanguage $language */ + foreach (array_values($allWebsiteLanguages) as $language) { + $websiteLanguagesArray[] = $language->getLanguageId(); + } + $languageCounter = []; + foreach (array_values($queryResult) as $value) { + $languageUid = $value[$this->syslangUidLiteral]; + $languageCounter[$languageUid]++; + if ($languageCounter[$languageUid] > 1) { + $checkConsistency['doubleLang'] = true; + } + + //check if language flag exists in current website + if (($languageUid > 0) && in_array($languageUid, $websiteLanguagesArray, true)) { + $checkConsistency['existLang'] = true; + } + } + } + + return $checkConsistency; + } + + /** + * Finds the default language stepconf by giving ratingobject and steporder + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $stepname The ratingname to look for + * @throws InvalidQueryException + * @return Stepname|null The stepname in default language + * @var Stepname $foundRow + */ + public function findDefaultStepname(Stepname $stepname): ?Stepname + { + $foundRow = $this->objectManager->get(Stepname::class); + + $query = $this->createQuery(); + $query->getQuerySettings()->setRespectSysLanguage(false); + $query->getQuerySettings()->setLanguageOverlayMode(false); + $query->matching( + $query->logicalAnd( + [ + $query->equals(self::STEPCONF_NAME, $stepname->getStepconf()), + $query->in($this->syslangUidLiteral, [0, -1]) + ] + ) + )->setLimit(1); + + /** @var QueryResultInterface $queryResult */ + $queryResult = $query->execute(); + + /* + $queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL(), get_class($this).' SQL'); + \TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters(), get_class($this).' SQL Parameter'); + */ + + /** @var Stepname $foundRow */ + $foundRow = null; + if ($queryResult->count() > 0) { + $foundRow = $queryResult->getFirst(); + } + return $foundRow; + } + + /** + * Finds the localized ratingstep entry by giving ratingobjectUid + * + * @param Stepname $stepname The ratingname to look for + * @return bool true if stepconf having same steporder and _languageUid exists + */ + public function existStepname(Stepname $stepname): bool + { + $lookForStepname = $this->findStepnameObject($stepname); + return !is_null($lookForStepname); + } + + /** + * Set default query settings to find ALL records + */ + public function clearQuerySettings(): void + { + $querySettings = $this->createQuery()->getQuerySettings(); + $querySettings->setRespectSysLanguage(true); + $querySettings->setIgnoreEnableFields(true); + $querySettings->setLanguageOverlayMode(false); + $this->setDefaultQuerySettings($querySettings); + } + + protected function getCurrentSite(): ?Site + { + if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface + && $GLOBALS['TYPO3_REQUEST']->getAttribute('site') instanceof Site) { + return $GLOBALS['TYPO3_REQUEST']->getAttribute('site'); + } + return null; + } +} diff --git a/Classes/Domain/Repository/VoteRepository.php b/Classes/Domain/Repository/VoteRepository.php new file mode 100644 index 0000000..99637af --- /dev/null +++ b/Classes/Domain/Repository/VoteRepository.php @@ -0,0 +1,105 @@ +createQuery(); + + return $query->matching( + $query->logicalAnd( + [ + $query->equals('rating', $rating), + $query->equals('voter', $voter) + ] + ) + )->execute()->getFirst(); + } + + /** + * Counts all votings by giving the rating and ratingstep + * + * @param Rating $rating The concerned ratingobject + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf The stepconf object + * @return int + */ + public function countByMatchingRatingAndVote($rating = null, $stepconf = null): int + { + $query = $this->createQuery(); + $query->matching($query->logicalAnd( + [ + $query->equals('rating', $rating->getUid()), + $query->equals('vote', $stepconf->getUid()) + ] + )); + + return count($query->execute()); + } + + /** + * Counts all anonymous votings by giving the rating and ratingstep + * + * @param Rating $rating The concerned ratingobject + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf The stepconf object + * @param int $anonymousVoter UID of the anonymous account + * @return int + */ + public function countAnonymousByMatchingRatingAndVote($rating = null, $stepconf = null, $anonymousVoter = null): int + { + /** @var int $count */ + $count = 0; + + if ($anonymousVoter !== null) { + $query = $this->createQuery(); + $query->matching( + $query->logicalAnd([ + $query->equals('rating', $rating->getUid()), + $query->equals('vote', $stepconf->getUid()), + $query->equals('voter', $anonymousVoter), + ]) + ); + $count = count($query->execute()); + } + + return $count; + } +} diff --git a/Classes/Domain/Repository/VoterRepository.php b/Classes/Domain/Repository/VoterRepository.php new file mode 100644 index 0000000..da28f81 --- /dev/null +++ b/Classes/Domain/Repository/VoterRepository.php @@ -0,0 +1,31 @@ +objectManager->get(QuerySettingsInterface::class); + $querySettings->setIgnoreEnableFields(true); + $this->setDefaultQuerySettings($querySettings); + } +} diff --git a/Classes/Domain/Validator/RatingValidator.php b/Classes/Domain/Validator/RatingValidator.php new file mode 100644 index 0000000..99a5b7f --- /dev/null +++ b/Classes/Domain/Validator/RatingValidator.php @@ -0,0 +1,60 @@ +isEmpty($rating) && $rating instanceof Rating) { + $ratedobjectuid = $rating->getRatedobjectuid(); + if (empty($ratedobjectuid)) { + $this->addError( + LocalizationUtility::translate('error.validator.rating.ratedobjectuid', 'ThRating'), + 1283536994 + ); + } + if (!$rating->getRatingobject() instanceof Ratingobject) { + $this->addError( + LocalizationUtility::translate('error.validator.rating.ratingobject', 'ThRating'), + 1283538549 + ); + } + } else { + $this->addError(LocalizationUtility::translate('error.validator.rating.empty', 'ThRating'), 1568138421); + } + } +} diff --git a/Classes/Domain/Validator/RatingobjectValidator.php b/Classes/Domain/Validator/RatingobjectValidator.php new file mode 100644 index 0000000..d97608b --- /dev/null +++ b/Classes/Domain/Validator/RatingobjectValidator.php @@ -0,0 +1,54 @@ +getRatetable(); + /** @var string $ratefield */ + $ratefield = $ratingobject->getRatefield(); + + if (empty($ratetable)) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.ratingobject_table_extbase', + 'ThRating' + ), + 1283528638 + ); + } + if (empty($ratefield)) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.ratingobject_field_extbase', + 'ThRating' + ), + 1283536038 + ); + } + } +} diff --git a/Classes/Domain/Validator/StepconfValidator.php b/Classes/Domain/Validator/StepconfValidator.php new file mode 100644 index 0000000..c908a96 --- /dev/null +++ b/Classes/Domain/Validator/StepconfValidator.php @@ -0,0 +1,185 @@ +stepconfRepository = $stepconfRepository; + } + + /** + * @var \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository + */ + protected $stepnameRepository; + + /** + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Repository\StepnameRepository $stepnameRepository + */ + public function injectStepnameRepository(StepnameRepository $stepnameRepository) + { + $this->stepnameRepository = $stepnameRepository; + } + + /** + * If the given step is valid + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepconf $stepconf + * @throws InvalidQueryException + */ + protected function isValid($stepconf): void + { + /** @noinspection NotOptimalIfConditionsInspection */ + if (!$this->isEmpty($stepconf) && $stepconf instanceof Stepconf) { + $this->checkRatingobject($stepconf); + if (!$this->result->hasErrors()) { + $this->checkSteporder($stepconf); + } + if (!$this->result->hasErrors()) { + $this->validateStepnames($stepconf); + } + } else { + $this->addError(LocalizationUtility::translate('error.validator.stepconf.empty', 'ThRating'), 1568139528); + } + } + + /** + * A stepconf object must have a ratingobject + * @param Stepconf $stepconf + */ + protected function checkRatingobject(Stepconf $stepconf): void + { + if (!$stepconf->getRatingobject() instanceof Ratingobject) { + $this->addError( + LocalizationUtility::translate('error.validator.stepconf.ratingobject', 'ThRating'), + 1284700846 + ); + } + } + + /** + * At least a steporder value must be set and a positive integer ( >0 ) and valid regaing existing values + * @param Stepconf $stepconf + */ + protected function checkSteporder(Stepconf $stepconf): void + { + $steporder = $stepconf->getSteporder(); + if (empty($steporder)) { + $this->addError( + LocalizationUtility::translate('error.validator.stepconf.steps', 'ThRating'), + 1284700903 + ); + return; + } + + if (!is_int($stepconf->getSteporder()) || $stepconf->getSteporder() < 1) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.stepconf.invalidSteporderNumber', + 'ThRating' + ), + 1368123953 + ); + } + + //check if given steporder is valid (integer, maximum +1) + /** @var object $maxSteporderStepconfobject */ + $maxSteporderStepconfobject = $this->stepconfRepository->findByRatingobject($stepconf->getRatingobject()); + $maxSteporder = $maxSteporderStepconfobject[$maxSteporderStepconfobject->count() - 1]->getSteporder(); + if ($stepconf->getSteporder() > $maxSteporder + 1) { + $this->addError( + LocalizationUtility::translate('error.validator.stepconf.maxSteporder', 'ThRating'), + 1368123970 + ); + } + } + + /** + * If the given step is valid + * + * @param Stepconf $stepconf + * @throws InvalidQueryException + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + protected function validateStepnames($stepconf): void + { + //check if a stepname is given that at least has the default language definition + //TODO move to query on stepname repository + $stepname = $stepconf->getStepname(); + $countNames = 0; + if ($stepname instanceof ObjectStorage) { + $countNames = $stepname->count(); + } + if ($countNames != 0) { + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $firstStepname */ + $firstStepname = $stepname->current(); + + /** @var \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname|object $defaultName */ + $defaultName = $this->stepnameRepository->findDefaultStepname($firstStepname); + if (!$defaultName->isValid()) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.stepconf.defaultStepname', + 'ThRating', + [$firstStepname->getStepconf()->getUid()] + ), + 1384374165 + ); + } else { + //Finally check on language consistency + $checkConsistency = $this->stepnameRepository->checkConsistency($firstStepname); + if ($checkConsistency['doubleLang']) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.stepconf.doubleLangEntry', + 'ThRating', + [$firstStepname->getStepconf()->getUid()] + ), + 1384374589 + ); + } elseif ($checkConsistency['existLang']) { + $this->addError( + LocalizationUtility::translate( + 'error.validator.stepconf.notExistingLanguage', + 'ThRating', + [$firstStepname->getUid()] + ), + 1384374589 + ); + } + } + } + } +} diff --git a/Classes/Domain/Validator/StepnameValidator.php b/Classes/Domain/Validator/StepnameValidator.php new file mode 100644 index 0000000..9bdb038 --- /dev/null +++ b/Classes/Domain/Validator/StepnameValidator.php @@ -0,0 +1,72 @@ +stepnameRepository = $stepnameRepository; + } + + /** + * If the given step is valid + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Stepname $stepname + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + protected function isValid($stepname): void + { + //a stepname object must have a stepconf + if (!$stepname->getStepconf() instanceof Stepconf) { + $this->addError( + LocalizationUtility::translate('error.validator.stepname.stepconf', 'ThRating'), + 1382895072 + ); + } + + //check if given languagecode exists in website + if (!$this->stepnameRepository->checkStepnameLanguage($stepname)) { + $this->addError(LocalizationUtility::translate('error.validator.stepname.sysLang', 'ThRating'), 1382895089); + } + + //now check if entry for default language exists + $langUid = $stepname->getSysLanguageUid(); + if (!empty($langUid)) { + $defaultStepname = $this->stepnameRepository->findDefaultStepname($stepname); + if (get_class($defaultStepname) !== Stepname::class || $this->validate($defaultStepname)->hasErrors()) { + $this->addError( + LocalizationUtility::translate('error.validator.stepname.defaultLang', 'ThRating'), + 1382895097 + ); + } + } + } +} diff --git a/Classes/Domain/Validator/VoteValidator.php b/Classes/Domain/Validator/VoteValidator.php new file mode 100644 index 0000000..b8b22db --- /dev/null +++ b/Classes/Domain/Validator/VoteValidator.php @@ -0,0 +1,79 @@ +isEmpty($vote) && $vote instanceof Vote) { + //a vote object must have a vote + if (!$vote->getVote() instanceof Stepconf) { + $this->addError(LocalizationUtility::translate('error.validator.vote.vote', 'ThRating'), 1283537235); + } else { + //a vote must have a valid voter + if (!$vote->getVoter() instanceof Voter) { + $this->addError( + LocalizationUtility::translate('error.validator.vote.voter', 'ThRating'), + 1283540684 + ); + } + //check if the given vote is a valid step for this ratingobject + if (!$vote->getRating()->getRatingobject()->getStepconfs()->contains($vote->getVote())) { + $this->addError( + LocalizationUtility::translate('error.validator.vote.stepconf', 'ThRating'), + 1283612492 + ); + } + } + } else { + $this->addError(LocalizationUtility::translate('error.validator.vote.empty', 'ThRating'), 1568141014); + } + } + + /** + * If the given Vote is set + * + * @param \WapplerSystems\BookmarksLikesRatings\Domain\Model\Vote $vote The vote + * @return bool + */ + public function isObjSet($vote) + { + $result = !$this->isEmpty($vote) && $vote instanceof Vote; + + return $result; + } +} diff --git a/Classes/Service/AbstractExtensionService.php b/Classes/Service/AbstractExtensionService.php new file mode 100644 index 0000000..9fda21e --- /dev/null +++ b/Classes/Service/AbstractExtensionService.php @@ -0,0 +1,53 @@ +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)); + } +} diff --git a/Classes/Service/AccessControlService.php b/Classes/Service/AccessControlService.php new file mode 100644 index 0000000..2e819d9 --- /dev/null +++ b/Classes/Service/AccessControlService.php @@ -0,0 +1,187 @@ +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; + } +} diff --git a/Classes/Service/AccessException.php b/Classes/Service/AccessException.php new file mode 100644 index 0000000..b9c68f5 --- /dev/null +++ b/Classes/Service/AccessException.php @@ -0,0 +1,21 @@ +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; + } +} diff --git a/Classes/Service/Exception.php b/Classes/Service/Exception.php new file mode 100644 index 0000000..e9767a4 --- /dev/null +++ b/Classes/Service/Exception.php @@ -0,0 +1,19 @@ +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); + } + } +} diff --git a/Classes/Service/ExtensionHelperService.php b/Classes/Service/ExtensionHelperService.php new file mode 100644 index 0000000..ae81e8a --- /dev/null +++ b/Classes/Service/ExtensionHelperService.php @@ -0,0 +1,720 @@ +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; + } +} diff --git a/Classes/Service/ExtensionManagementService.php b/Classes/Service/ExtensionManagementService.php new file mode 100644 index 0000000..661e6db --- /dev/null +++ b/Classes/Service/ExtensionManagementService.php @@ -0,0 +1,185 @@ +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; + } +} diff --git a/Classes/Service/JsonService.php b/Classes/Service/JsonService.php new file mode 100644 index 0000000..85e89f2 --- /dev/null +++ b/Classes/Service/JsonService.php @@ -0,0 +1,81 @@ +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; + } +} diff --git a/Classes/Service/LoggingService.php b/Classes/Service/LoggingService.php new file mode 100644 index 0000000..b041e8e --- /dev/null +++ b/Classes/Service/LoggingService.php @@ -0,0 +1,83 @@ +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); + } +} diff --git a/Classes/Service/RichSnippetService.php b/Classes/Service/RichSnippetService.php new file mode 100644 index 0000000..5157e95 --- /dev/null +++ b/Classes/Service/RichSnippetService.php @@ -0,0 +1,442 @@ +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; + } +} diff --git a/Classes/ViewHelpers/BookmarkViewHelper.php b/Classes/ViewHelpers/BookmarkViewHelper.php new file mode 100644 index 0000000..e1e927b --- /dev/null +++ b/Classes/ViewHelpers/BookmarkViewHelper.php @@ -0,0 +1,245 @@ +registerArgument('action', 'string', 'The rating action'); + $this->registerArgument('ratetable', 'string', 'The rating tablename'); + $this->registerArgument('ratefield', 'string', 'The rating fieldname'); + $this->registerArgument('ratedobjectuid', 'integer', 'The ratingobject uid', true); + $this->registerArgument('ratingobject', 'integer', 'The ratingobject'); + $this->registerArgument('display', 'string', 'The display configuration'); + } + + /** + * Renders the ratingView + * + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception + * @noinspection PhpFullyQualifiedNameUsageInspection + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $typoscriptObjectPath = 'plugin.tx_thrating'; + $ratedobjectuid = $arguments['ratedobjectuid']; + $action = $arguments['action']; + $ratingobject = $arguments['ratingobject']; + $ratetable = $arguments['ratetable']; + $ratefield = $arguments['ratefield']; + $display = $arguments['display']; + $extensionHelperService = static::getExtensionHelperService(); + $contentObjectRenderer = static::getContentObjectRenderer(); + + //instantiate the logger + $logger = $extensionHelperService->getLogger(__CLASS__); + $logger->log( + LogLevel::DEBUG, + 'Entry point', + [ + 'Viewhelper parameters' => [ + 'action' => $action, + 'ratingobject' => $ratingobject, + 'ratetable' => $ratetable, + 'ratefield' => $ratefield, + 'ratedobjectuid' => $ratedobjectuid, + 'display' => $display, ], + 'typoscript' => static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT), + ] + ); + + if (TYPO3_MODE === 'BE') { + static::simulateFrontendEnvironment(); + } + $contentObjectRenderer->start([]); + + $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath); + $lastSegment = array_pop($pathSegments); + $setup = static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + foreach ($pathSegments as $segment) { + if (!array_key_exists($segment . '.', $setup)) { + $logger->log( + LogLevel::CRITICAL, + 'TypoScript object path does not exist', + [ + 'Typoscript object path' => htmlspecialchars($typoscriptObjectPath), + 'Setup' => $setup, + 'errorCode' => 1253191023, ] + ); + + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist', + 1549388144 + ); + } + $setup = $setup[$segment . '.']; + } + + if (!isset($setup[$lastSegment])) { + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"', + 1549388123 + ); + } + + if (!empty($action)) { + $setup[$lastSegment . '.']['action'] = $action; + $setup[$lastSegment . '.']['switchableControllerActions.']['Vote.']['1'] = $action; + } + if (!empty($ratingobject)) { + $setup[$lastSegment . '.']['settings.']['ratingobject'] = $ratingobject; + } elseif (!empty($ratetable) && !empty($ratefield)) { + $setup[$lastSegment . '.']['settings.']['ratetable'] = $ratetable; + $setup[$lastSegment . '.']['settings.']['ratefield'] = $ratefield; + } else { + $logger->log( + LogLevel::CRITICAL, + 'ratingobject not specified or ratetable/ratfield not set', + ['errorCode' => 1399727698] + ); + throw new Exception('ratingobject not specified or ratetable/ratfield not set', 1399727698); + } + if (!empty($ratedobjectuid)) { + $setup[$lastSegment . '.']['settings.']['ratedobjectuid'] = $ratedobjectuid; + } else { + $logger->log(LogLevel::CRITICAL, 'ratedobjectuid not set', ['errorCode' => 1304624408]); + throw new Exception('ratedobjectuid not set', 1304624408); + } + if (!empty($display)) { + $setup[$lastSegment . '.']['settings.']['display'] = $display; + } + + $logger->log( + LogLevel::DEBUG, + 'Single contentObjectRenderer to get', + [ + 'contentObjectRenderer type' => $setup[$lastSegment], + 'cOjb config' => $setup[$lastSegment . '.'], ] + ); + + $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? []); + if (TYPO3_MODE === 'BE') { + static::resetFrontendEnvironment(); + } + + $logger->log(LogLevel::INFO, 'Generated content', ['content' => $content]); + $logger->log(LogLevel::DEBUG, 'Exit point'); + + return $content; + } + + /** + * @return \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected static function getExtensionHelperService(): ExtensionHelperService + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ExtensionHelperService::class); + } + + /** + * @return object|\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface + */ + protected static function getConfigurationManager() + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManagerInterface::class); + } + + /** + * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + */ + protected static function getContentObjectRenderer(): ContentObjectRenderer + { + return GeneralUtility::makeInstance( + ContentObjectRenderer::class, + $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0) + ); + } + + /** + * Sets the $TSFE->cObjectDepthCounter in Backend mode + * This somewhat hacky work around is currently needed because the cObjGetSingle() function + * of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting + */ + protected static function simulateFrontendEnvironment(): void + { + static::$tsfeBackup = $GLOBALS['TSFE'] ?? null; + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + $GLOBALS['TSFE'] = new \stdClass(); + $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $GLOBALS['TSFE']->cObjectDepthCounter = 100; + } + + /** + * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment() + * + * @see simulateFrontendEnvironment() + */ + protected static function resetFrontendEnvironment(): void + { + $GLOBALS['TSFE'] = static::$tsfeBackup; + } +} diff --git a/Classes/ViewHelpers/LikeViewHelper.php b/Classes/ViewHelpers/LikeViewHelper.php new file mode 100644 index 0000000..b9ca4c1 --- /dev/null +++ b/Classes/ViewHelpers/LikeViewHelper.php @@ -0,0 +1,245 @@ +registerArgument('action', 'string', 'The rating action'); + $this->registerArgument('ratetable', 'string', 'The rating tablename'); + $this->registerArgument('ratefield', 'string', 'The rating fieldname'); + $this->registerArgument('ratedobjectuid', 'integer', 'The ratingobject uid', true); + $this->registerArgument('ratingobject', 'integer', 'The ratingobject'); + $this->registerArgument('display', 'string', 'The display configuration'); + } + + /** + * Renders the ratingView + * + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception + * @noinspection PhpFullyQualifiedNameUsageInspection + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $typoscriptObjectPath = 'plugin.tx_thrating'; + $ratedobjectuid = $arguments['ratedobjectuid']; + $action = $arguments['action']; + $ratingobject = $arguments['ratingobject']; + $ratetable = $arguments['ratetable']; + $ratefield = $arguments['ratefield']; + $display = $arguments['display']; + $extensionHelperService = static::getExtensionHelperService(); + $contentObjectRenderer = static::getContentObjectRenderer(); + + //instantiate the logger + $logger = $extensionHelperService->getLogger(__CLASS__); + $logger->log( + LogLevel::DEBUG, + 'Entry point', + [ + 'Viewhelper parameters' => [ + 'action' => $action, + 'ratingobject' => $ratingobject, + 'ratetable' => $ratetable, + 'ratefield' => $ratefield, + 'ratedobjectuid' => $ratedobjectuid, + 'display' => $display, ], + 'typoscript' => static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT), + ] + ); + + if (TYPO3_MODE === 'BE') { + static::simulateFrontendEnvironment(); + } + $contentObjectRenderer->start([]); + + $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath); + $lastSegment = array_pop($pathSegments); + $setup = static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + foreach ($pathSegments as $segment) { + if (!array_key_exists($segment . '.', $setup)) { + $logger->log( + LogLevel::CRITICAL, + 'TypoScript object path does not exist', + [ + 'Typoscript object path' => htmlspecialchars($typoscriptObjectPath), + 'Setup' => $setup, + 'errorCode' => 1253191023, ] + ); + + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist', + 1549388144 + ); + } + $setup = $setup[$segment . '.']; + } + + if (!isset($setup[$lastSegment])) { + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"', + 1549388123 + ); + } + + if (!empty($action)) { + $setup[$lastSegment . '.']['action'] = $action; + $setup[$lastSegment . '.']['switchableControllerActions.']['Vote.']['1'] = $action; + } + if (!empty($ratingobject)) { + $setup[$lastSegment . '.']['settings.']['ratingobject'] = $ratingobject; + } elseif (!empty($ratetable) && !empty($ratefield)) { + $setup[$lastSegment . '.']['settings.']['ratetable'] = $ratetable; + $setup[$lastSegment . '.']['settings.']['ratefield'] = $ratefield; + } else { + $logger->log( + LogLevel::CRITICAL, + 'ratingobject not specified or ratetable/ratfield not set', + ['errorCode' => 1399727698] + ); + throw new Exception('ratingobject not specified or ratetable/ratfield not set', 1399727698); + } + if (!empty($ratedobjectuid)) { + $setup[$lastSegment . '.']['settings.']['ratedobjectuid'] = $ratedobjectuid; + } else { + $logger->log(LogLevel::CRITICAL, 'ratedobjectuid not set', ['errorCode' => 1304624408]); + throw new Exception('ratedobjectuid not set', 1304624408); + } + if (!empty($display)) { + $setup[$lastSegment . '.']['settings.']['display'] = $display; + } + + $logger->log( + LogLevel::DEBUG, + 'Single contentObjectRenderer to get', + [ + 'contentObjectRenderer type' => $setup[$lastSegment], + 'cOjb config' => $setup[$lastSegment . '.'], ] + ); + + $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? []); + if (TYPO3_MODE === 'BE') { + static::resetFrontendEnvironment(); + } + + $logger->log(LogLevel::INFO, 'Generated content', ['content' => $content]); + $logger->log(LogLevel::DEBUG, 'Exit point'); + + return $content; + } + + /** + * @return \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected static function getExtensionHelperService(): ExtensionHelperService + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ExtensionHelperService::class); + } + + /** + * @return object|\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface + */ + protected static function getConfigurationManager() + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManagerInterface::class); + } + + /** + * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + */ + protected static function getContentObjectRenderer(): ContentObjectRenderer + { + return GeneralUtility::makeInstance( + ContentObjectRenderer::class, + $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0) + ); + } + + /** + * Sets the $TSFE->cObjectDepthCounter in Backend mode + * This somewhat hacky work around is currently needed because the cObjGetSingle() function + * of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting + */ + protected static function simulateFrontendEnvironment(): void + { + static::$tsfeBackup = $GLOBALS['TSFE'] ?? null; + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + $GLOBALS['TSFE'] = new \stdClass(); + $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $GLOBALS['TSFE']->cObjectDepthCounter = 100; + } + + /** + * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment() + * + * @see simulateFrontendEnvironment() + */ + protected static function resetFrontendEnvironment(): void + { + $GLOBALS['TSFE'] = static::$tsfeBackup; + } +} diff --git a/Classes/ViewHelpers/RatingViewHelper.php b/Classes/ViewHelpers/RatingViewHelper.php new file mode 100644 index 0000000..68949d4 --- /dev/null +++ b/Classes/ViewHelpers/RatingViewHelper.php @@ -0,0 +1,257 @@ + + * + * + * + * rendered rating + * + */ +class RatingViewHelper extends AbstractViewHelper +{ + use CompileWithRenderStatic; + + /** + * Disable escaping of this node's output + * + * @var bool + */ + protected $escapeOutput = false; + + /** + * @noinspection PhpUnnecessaryFullyQualifiedNameInspection + * @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + * contains a backup of the current['TSFE'] if used in BE mode + */ + protected static $tsfeBackup; + + /** + * @noinspection PhpFullyQualifiedNameUsageInspection + * @var \TYPO3\CMS\Core\Log\Logger $logger + */ + protected $logger; + + /** + * @var array + */ + protected $typoScriptSetup; + + /** + * @noinspection PhpUnnecessaryFullyQualifiedNameInspection + * @var \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected $extensionHelperService; + + public function initializeArguments(): void + { + $this->registerArgument('action', 'string', 'The rating action'); + $this->registerArgument('ratetable', 'string', 'The rating tablename'); + $this->registerArgument('ratefield', 'string', 'The rating fieldname'); + $this->registerArgument('ratedobjectuid', 'integer', 'The ratingobject uid', true); + $this->registerArgument('ratingobject', 'integer', 'The ratingobject'); + $this->registerArgument('display', 'string', 'The display configuration'); + } + + /** + * Renders the ratingView + * + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return mixed + * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception + * @noinspection PhpFullyQualifiedNameUsageInspection + */ + public static function renderStatic( + array $arguments, + \Closure $renderChildrenClosure, + RenderingContextInterface $renderingContext + ) { + $typoscriptObjectPath = 'plugin.tx_thrating'; + $ratedobjectuid = $arguments['ratedobjectuid']; + $action = $arguments['action']; + $ratingobject = $arguments['ratingobject']; + $ratetable = $arguments['ratetable']; + $ratefield = $arguments['ratefield']; + $display = $arguments['display']; + $extensionHelperService = static::getExtensionHelperService(); + $contentObjectRenderer = static::getContentObjectRenderer(); + + //instantiate the logger + $logger = $extensionHelperService->getLogger(__CLASS__); + $logger->log( + LogLevel::DEBUG, + 'Entry point', + [ + 'Viewhelper parameters' => [ + 'action' => $action, + 'ratingobject' => $ratingobject, + 'ratetable' => $ratetable, + 'ratefield' => $ratefield, + 'ratedobjectuid' => $ratedobjectuid, + 'display' => $display, ], + 'typoscript' => static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT), + ] + ); + + if (TYPO3_MODE === 'BE') { + static::simulateFrontendEnvironment(); + } + $contentObjectRenderer->start([]); + + $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath); + $lastSegment = array_pop($pathSegments); + $setup = static::getConfigurationManager() + ->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + foreach ($pathSegments as $segment) { + if (!array_key_exists($segment . '.', $setup)) { + $logger->log( + LogLevel::CRITICAL, + 'TypoScript object path does not exist', + [ + 'Typoscript object path' => htmlspecialchars($typoscriptObjectPath), + 'Setup' => $setup, + 'errorCode' => 1253191023, ] + ); + + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'TypoScript object path "' . $typoscriptObjectPath . '" does not exist', + 1549388144 + ); + } + $setup = $setup[$segment . '.']; + } + + if (!isset($setup[$lastSegment])) { + throw new \TYPO3Fluid\Fluid\Core\ViewHelper\Exception( + 'No Content Object definition found at TypoScript object path "' . $typoscriptObjectPath . '"', + 1549388123 + ); + } + + if (!empty($action)) { + $setup[$lastSegment . '.']['action'] = $action; + $setup[$lastSegment . '.']['switchableControllerActions.']['Vote.']['1'] = $action; + } + if (!empty($ratingobject)) { + $setup[$lastSegment . '.']['settings.']['ratingobject'] = $ratingobject; + } elseif (!empty($ratetable) && !empty($ratefield)) { + $setup[$lastSegment . '.']['settings.']['ratetable'] = $ratetable; + $setup[$lastSegment . '.']['settings.']['ratefield'] = $ratefield; + } else { + $logger->log( + LogLevel::CRITICAL, + 'ratingobject not specified or ratetable/ratfield not set', + ['errorCode' => 1399727698] + ); + throw new Exception('ratingobject not specified or ratetable/ratfield not set', 1399727698); + } + if (!empty($ratedobjectuid)) { + $setup[$lastSegment . '.']['settings.']['ratedobjectuid'] = $ratedobjectuid; + } else { + $logger->log(LogLevel::CRITICAL, 'ratedobjectuid not set', ['errorCode' => 1304624408]); + throw new Exception('ratedobjectuid not set', 1304624408); + } + if (!empty($display)) { + $setup[$lastSegment . '.']['settings.']['display'] = $display; + } + + $logger->log( + LogLevel::DEBUG, + 'Single contentObjectRenderer to get', + [ + 'contentObjectRenderer type' => $setup[$lastSegment], + 'cOjb config' => $setup[$lastSegment . '.'], ] + ); + + $content = $contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'] ?? []); + if (TYPO3_MODE === 'BE') { + static::resetFrontendEnvironment(); + } + + $logger->log(LogLevel::INFO, 'Generated content', ['content' => $content]); + $logger->log(LogLevel::DEBUG, 'Exit point'); + + return $content; + } + + /** + * @return \WapplerSystems\BookmarksLikesRatings\Service\ExtensionHelperService + */ + protected static function getExtensionHelperService(): ExtensionHelperService + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ExtensionHelperService::class); + } + + /** + * @return object|\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface + */ + protected static function getConfigurationManager() + { + return GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationManagerInterface::class); + } + + /** + * @return \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + */ + protected static function getContentObjectRenderer(): ContentObjectRenderer + { + return GeneralUtility::makeInstance( + ContentObjectRenderer::class, + $GLOBALS['TSFE'] ?? GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0) + ); + } + + /** + * Sets the $TSFE->cObjectDepthCounter in Backend mode + * This somewhat hacky work around is currently needed because the cObjGetSingle() function + * of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting + */ + protected static function simulateFrontendEnvironment(): void + { + static::$tsfeBackup = $GLOBALS['TSFE'] ?? null; + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + $GLOBALS['TSFE'] = new \stdClass(); + $GLOBALS['TSFE']->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $GLOBALS['TSFE']->cObjectDepthCounter = 100; + } + + /** + * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment() + * + * @see simulateFrontendEnvironment() + */ + protected static function resetFrontendEnvironment(): void + { + $GLOBALS['TSFE'] = static::$tsfeBackup; + } +} diff --git a/Classes/Widgets/Provider/TopLikesDataProvider.php b/Classes/Widgets/Provider/TopLikesDataProvider.php new file mode 100644 index 0000000..8885d65 --- /dev/null +++ b/Classes/Widgets/Provider/TopLikesDataProvider.php @@ -0,0 +1,38 @@ +getQueryBuilderForTable('be_users'); + return $queryBuilder + ->count('*') + ->from('be_users') + ->where( + $queryBuilder->expr()->eq( + 'admin', + $queryBuilder->createNamedParameter(0, Connection::PARAM_INT) + ) + ) + ->execute(); + } +} diff --git a/Classes/Widgets/TopLikesWidget.php b/Classes/Widgets/TopLikesWidget.php new file mode 100644 index 0000000..8b7bcb7 --- /dev/null +++ b/Classes/Widgets/TopLikesWidget.php @@ -0,0 +1,70 @@ +configuration = $configuration; + $this->view = $view; + $this->options = $options; + $this->buttonProvider = $buttonProvider; + $this->dataProvider = $dataProvider; + } + + public function renderWidgetContent(): string + { + $this->view->setTemplate('Widget/ListWidget'); + $this->view->assignMultiple([ + 'items' => $this->getItems(), + 'options' => $this->options, + 'button' => $this->buttonProvider, + 'configuration' => $this->configuration, + ]); + return $this->view->render(); + } + + protected function getItems(): array + { + return $this->dataProvider->getTopLikes(); + } +} diff --git a/Configuration/Backend/DashboardWidgetGroups.php b/Configuration/Backend/DashboardWidgetGroups.php new file mode 100644 index 0000000..232a767 --- /dev/null +++ b/Configuration/Backend/DashboardWidgetGroups.php @@ -0,0 +1,7 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:widget_group.ratings', + ], +]; diff --git a/Configuration/Backend/DashboardWidgets.yaml b/Configuration/Backend/DashboardWidgets.yaml new file mode 100644 index 0000000..ce13bdc --- /dev/null +++ b/Configuration/Backend/DashboardWidgets.yaml @@ -0,0 +1,16 @@ + +services: + dashboard.widget.bookmarkslikesratingsToplikes: + class: 'WapplerSystems\BookmarksLikesRatings\Widgets\TopLikesWidget' + arguments: + $view: '@dashboard.views.widget' + $dataProvider: '@WapplerSystems\BookmarksLikesRatings\Widgets\Provider\TopLikesDataProvider' + tags: + - name: dashboard.widget + identifier: 'bookmarks_likes_ratings' + groupNames: 'ratings' + title: 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:widgets.bookmarks_likes_ratings.topLikes.title' + description: 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:widgets.bookmarks_likes_ratings.topLikes.description' + iconIdentifier: 'content-widget-ratings' + height: 'large' + width: 'medium' diff --git a/Configuration/FlexForms/Bookmarks.xml b/Configuration/FlexForms/Bookmarks.xml index a578307..878b866 100644 --- a/Configuration/FlexForms/Bookmarks.xml +++ b/Configuration/FlexForms/Bookmarks.xml @@ -13,18 +13,18 @@ - + select selectSingle default - LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:default + LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:default default - LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:alternative + LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:alternative alternative @@ -33,14 +33,14 @@ - - LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:ff.isComplementary.description + + LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:ff.isComplementary.description check 0 - LLL:EXT:ws_bookmark_pages/Resources/Private/Language/locallang.xlf:yes + LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:yes diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..efaf8a8 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,12 @@ +imports: + - { resource: Backend/DashboardWidgets.yaml } + +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + WapplerSystems\BookmarksLikesRatings\: + resource: '../Classes/*' + diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php index 1511f1f..ca4b678 100644 --- a/Configuration/TCA/Overrides/sys_template.php +++ b/Configuration/TCA/Overrides/sys_template.php @@ -12,4 +12,4 @@ defined('TYPO3') || die('Access denied.'); /** * add TypoScript to template record */ -\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('ws_bookmark_pages', 'Configuration/TypoScript/', 'Bookmark Pages'); +\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile('bookmarks_likes_ratings', 'Configuration/TypoScript/', 'Bookmark Pages'); diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index 25ade44..829c028 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -10,36 +10,19 @@ defined('TYPO3') || die('Access denied.'); (function () { - $version = \TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(); - $version = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($version); - /** - * Register Plugin - */ - if ($version < 10000000) { - // For TYPO3 < V10 - // @extensionScannerIgnoreLine - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( - 'WapplerSystems.BookmarkPages', - 'Bookmarks', - 'Bookmark Pages' - ); - } else { - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( - 'BookmarkPages', - 'Bookmarks', - 'Bookmark Pages' - ); - } - /** - * Add flexForm - */ - $pluginSignature = 'bookmarkpages_bookmarks'; - $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist'][$pluginSignature] = 'pi_flexform'; - \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue( - $pluginSignature, - 'FILE:EXT:ws_bookmark_pages/Configuration/FlexForms/Bookmarks.xml' + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin( + 'BookmarksLikesRatings', + 'Bookmarks', + 'Bookmark Pages' ); - $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist'][$pluginSignature] = 'recursive,select_key,pages'; + + $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['bookmarkpages_bookmarks'] = 'pi_flexform'; + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPiFlexFormValue( + 'bookmarkpages_bookmarks', + 'FILE:EXT:bookmarks_likes_ratings/Configuration/FlexForms/Bookmarks.xml' + ); + + $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['bookmarkpages_bookmarks'] = 'recursive,select_key,pages'; })(); diff --git a/Configuration/TCA/tx_bookmarkslikesratings_domain_model_rating.php b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_rating.php new file mode 100644 index 0000000..61fb826 --- /dev/null +++ b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_rating.php @@ -0,0 +1,100 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.title', + 'label' => 'uid', + 'label_alt' => 'ratingobject,ratedobjectuid,votes', + 'label_userFunc' => 'Thucke\\ThRating\\Userfuncs\\Tca->getRatingRecordTitle', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'delete' => 'deleted', + 'adminOnly' => true, + 'hideTable' => true, + 'editlock' => 'ratedobjectuid', + 'enablecolumns' => [ + 'disabled' => 'hidden', + ], + 'iconfile' => 'EXT:bookmarks_likes_ratings/Resources/Public/Icons/tx_bookmarkslikesratings_domain_model_rating.gif', + ], + 'interface' => [ + 'showRecordFieldList' => 'hidden, ratedobjectuid, votes', + ], + 'columns' => [ + 'pid' => [ + 'exclude' => 1, + 'config' => [ + 'type' => 'none', + ], + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'ratingobject' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.ratingobject', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_ratingobject', + 'maxitems' => 1, + 'minitems' => 1, + 'disableNoMatchingValueElement' => 1, + ], + ], + 'ratedobjectuid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.ratedobjectuid', + 'config' => [ + 'type' => 'input', + 'size' => '8', + 'max' => '12', + 'eval' => 'int', + 'default' => 0, + ], + ], + 'votes' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.votes', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_vote', + 'foreign_field' => 'rating', + 'foreign_default_sortby' => 'uid', + 'appearance' => [ + 'levelLinksPosition' => 'bottom', + 'collapseAll' => 1, + 'expandSingle' => 1, + ], + ], + ], + 'currentrates' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.currentrates', + 'config' => [ + 'type' => 'none', + 'size' => '30', + ], + ], + 'uid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.rating.uid', + 'config' => [ + 'type' => 'none' + ], + ], + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, ratedobjectuid, votes' + ], + ], + 'palettes' => [ + '1' => [ + 'showitem' => '' + ], + ], +]; + diff --git a/Configuration/TCA/tx_bookmarkslikesratings_domain_model_ratingobject.php b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_ratingobject.php new file mode 100644 index 0000000..22ac909 --- /dev/null +++ b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_ratingobject.php @@ -0,0 +1,111 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.title', + 'label' => 'uid', + 'label_alt' => 'ratetable,ratefield', + 'label_userFunc' => 'Thucke\\ThRating\\Userfuncs\\Tca->getRatingObjectRecordTitle', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + ], + 'iconfile' => 'EXT:bookmarks_likes_ratings/Resources/Public/Icons/tx_bookmarkslikesratings_domain_model_ratingobject.gif', + ], + 'interface' => [ + 'showRecordFieldList' => 'hidden, ratetable, ratefield', + ], + 'columns' => [ + 'pid' => [ + 'exclude' => 1, + 'config' => [ + 'type' => 'none', + ], + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'ratetable' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.ratetable', + 'config' => [ + 'type' => 'input', + 'size' => 20, + 'eval' => 'trim,required', + 'max' => 64, + ], + ], + 'ratefield' => [ + 'exclude' => 0, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.ratefield', + 'config' => [ + 'type' => 'input', + 'size' => 20, + 'eval' => 'trim,required', + 'max' => 64, + ], + ], + 'uid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.uid', + 'config' => [ + 'type' => 'none' + ], + ], + 'stepconfs' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.stepconfs', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_stepconf', + 'foreign_field' => 'ratingobject', + 'foreign_default_sortby' => 'steporder', + 'appearance' => [ + 'levelLinksPosition' => 'bottom', + 'collapseAll' => true, + 'expandSingle' => true, + 'newRecordLinkAddTitle' => true, + //'newRecordLinkPosition' => 'both', + //'showSynchronizationLink' => true, + //'showAllLocalizationLink' => true, + //'showPossibleLocalizationRecords' => 1, + //'showRemovedLocalizationRecords' => 1, + ], + ], + ], + 'ratings' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.ratingobject.ratings', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_rating', + 'foreign_field' => 'ratingobject', + 'foreign_default_sortby' => 'uid', + 'appearance' => [ + 'levelLinksPosition' => 'bottom', + 'collapseAll' => 1, + 'expandSingle' => 1, + 'newRecordLinkAddTitle' => 1, + 'newRecordLinkPosition' => 'both', + ], + ], + ], + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, ratetable, ratefield, stepconfs, ratings' + ], + ], + 'palettes' => [ + '1' => [ + 'showitem' => '' + ], + ], +]; + diff --git a/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepconf.php b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepconf.php new file mode 100644 index 0000000..5c8754b --- /dev/null +++ b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepconf.php @@ -0,0 +1,142 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.title', + 'label' => 'uid', + 'label_alt' => 'ratingobject,steporder', + 'label_userFunc' => 'Thucke\\ThRating\\Userfuncs\\Tca->getStepconfRecordTitle', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'delete' => 'deleted', + 'adminOnly' => true, + 'hideTable' => true, + 'editlock' => 'steporder,stepweight', + 'dividers2tabs' => true, + 'enablecolumns' => [ + 'disabled' => 'hidden' + ], + 'iconfile' => 'EXT:bookmarks_likes_ratings/Resources/Public/Icons/tx_bookmarkslikesratings_domain_model_stepconf.gif', + ], + 'interface' => [ + 'showRecordFieldList' => 'hidden, ratingobject, steporder, stepweight, stepname', + ], + 'columns' => [ + 'pid' => [ + 'exclude' => 1, + 'config' => [ + 'type' => 'none', + ], + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'ratingobject' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.ratingobject', + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'steporder' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.steporder', + 'l10n_display' => 'defaultAsReadonly', + //'l10n_cat' => 'media', + 'config' => [ + 'type' => 'input', + 'size' => '8', + 'max' => '12', + 'eval' => 'Thucke\\ThRating\\Evaluation\\DynamicCssEvaluator,int,required', + 'default' => '1', + 'range' => [ + 'lower' => 1 + ], + ], + ], + 'stepweight' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.stepweight', + 'l10n_display' => 'defaultAsReadonly', + //'l10n_cat' => 'media', + 'config' => [ + 'type' => 'input', + 'size' => '8', + 'max' => '12', + 'eval' => 'Thucke\\ThRating\\Evaluation\\DynamicCssEvaluator,int', + 'default' => '1', + ], + ], + 'stepname' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.stepname', + 'config' => [ + 'type' => 'inline', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_stepname', + 'foreign_field' => 'stepconf', + 'foreign_unique' => 'sys_language_uid', + 'foreign_default_sortby' => 'sys_language_uid', + 'appearance' => [ + 'levelLinksPosition' => 'bottom', + 'collapseAll' => true, + 'expandSingle' => true, + 'newRecordLinkAddTitle' => true, + 'enabledControls' => [ + 'info' => true, + 'delete' => true, + 'localize' => true, + ], + 'showPossibleLocalizationRecords' => true, + 'showSynchronizationLink' => true, + 'newRecordLinkPosition' => 'both', + 'showAllLocalizationLink' => true, + 'showRemovedLocalizationRecords' => 1, + ], + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + 'enableCascadingDelete' => true, + ], + ], + ], + 'votes' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.votes', + 'l10n_mode' => 'eclude', + 'config' => [ + 'type' => 'inline', + 'behaviour' => [ + 'allowLanguageSynchronization' => true, + ], + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_vote', + 'foreign_field' => 'vote', + 'foreign_default_sortby' => 'uid', + 'appearance' => [ + 'levelLinksPosition' => 'bottom', + 'collapseAll' => 1, + 'expandSingle' => 1, + 'newRecordLinkAddTitle' => true, + ], + ], + ], + 'uid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepconf.uid', + 'config' => [ + 'type' => 'passthrough', + ], + ], + ], + 'types' => [ + '0' => [ + 'showitem' => 'hidden, steporder, stepweight, stepname, votes' + ], + ], + 'palettes' => [ + '1' => [ + 'showitem' => '' + ], + ], +]; + diff --git a/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepname.php b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepname.php new file mode 100644 index 0000000..f371c17 --- /dev/null +++ b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_stepname.php @@ -0,0 +1,131 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepname.title', + 'label' => 'uid', + 'label_alt' => 'stepconf,sys_language_uid', + 'label_userFunc' => 'Thucke\\ThRating\\Userfuncs\\Tca->getStepnameRecordTitle', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'origUid' => 't3_origuid', + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l18n_parent', + 'transOrigDiffSourceField' => 'l18n_diffsource', + 'delete' => 'deleted', + 'adminOnly' => false, + 'hideTable' => false, + 'editlock' => 'sys_language_uid,stepconf', + 'dividers2tabs' => true, + 'enablecolumns' => [ + 'disabled' => 'hidden' + ], + 'iconfile' => 'EXT:bookmarks_likes_ratings/Resources/Public/Icons/tx_bookmarkslikesratings_domain_model_stepname.gif', + ], + 'interface' => [ + 'showRecordFieldList' => 'hidden, stepconf, stepname, sys_language_uid', + ], + 'columns' => [ + 'pid' => [ + 'exclude' => 1, + 'config' => [ + 'type' => 'none', + ], + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'l10n_display' => 'hideDiff', + 'l10n_mode' => 'exclude', + 'displayCond' => [ + 'AND' => [ + 'FIELD:sys_language_uid:=:0', + ], + ], + 'config' => [ + 'type' => 'check', + 'default' => 0, + ], + ], + 'stepconf' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepname.stepconf', + 'config' => [ + 'type' => 'passthrough', + ], + ], + 'stepname' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepname.stepname', + 'l10n_mode' => 'prefixLangTitle', + 'l10n_display' => 'hideDiff', + 'config' => [ + 'type' => 'input', + 'size' => '15', + 'max' => '64', + 'eval' => 'Thucke\\ThRating\\Evaluation\\DynamicCssEvaluator,trim', + ], + ], + 'uid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.stepname.uid', + 'config' => [ + 'type' => 'none', + ], + ], + 'sys_language_uid' => [ + 'exclude' => true, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.language', + 'config' => [ + 'readOnly' => true, + 'type' => 'select', + 'renderType' => 'selectSingle', + 'special' => 'languages', + 'foreign_table' => 'sys_language', + 'foreign_table_where' => 'ORDER BY sys_language.title', + 'items' => [ + [ + 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', + -1, + 'flags-multiple' + ] + ], + 'default' => 0, + ], + ], + 'l18n_parent' => [ + 'displayCond' => 'FIELD:sys_language_uid:>:0', + 'exclude' => true, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'items' => [ + [ + '', + 0 + ], + ], + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_stepname', + 'foreign_table_where' => 'AND {#tx_bookmarkslikesratings_domain_model_stepname}.{#uid}=###REC_FIELD_l18n_parent###' . + 'AND {#tx_bookmarkslikesratings_domain_model_stepname}.{#sys_language_uid} IN (-1,0)', + 'fieldWizard' => [ + 'selectIcons' => [ + 'disabled' => true, + ], + ], + 'default' => 0 + ], + ], + 'l18n_diffsource' => [ + 'config' => [ + 'type' => 'passthrough', + ], + ], + ], + 'types' => [ + '0' => ['showitem' => 'hidden, stepname, stepconf, sys_language_uid'], + ], + 'palettes' => [ + '1' => ['showitem' => ''], + ], +]; + diff --git a/Configuration/TCA/tx_bookmarkslikesratings_domain_model_vote.php b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_vote.php new file mode 100644 index 0000000..325db62 --- /dev/null +++ b/Configuration/TCA/tx_bookmarkslikesratings_domain_model_vote.php @@ -0,0 +1,108 @@ + [ + 'title' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.vote.title', + 'label' => 'uid', + 'label_alt' => 'rating,fe_user,vote', + 'label_userFunc' => 'WapplerSystems\\BookmarksLikesRatings\\Userfuncs\\Tca->getVoteRecordTitle', + 'tstamp' => 'tstamp', + 'crdate' => 'crdate', + 'cruser_id' => 'cruser_id', + 'delete' => 'deleted', + 'adminOnly' => true, + 'hideTable' => true, + 'editlock' => 'rating', + 'enablecolumns' => [ + 'disabled' => 'hidden', + ], + 'iconfile' => 'EXT:bookmarks_likes_ratings/Resources/Public/Icons/tx_bookmarkslikesratings_domain_model_vote.gif', + ], + 'interface' => [ + 'showRecordFieldList' => 'hidden, rating, voter, vote', + ], + 'columns' => [ + 'pid' => [ + 'exclude' => 1, + 'config' => [ + 'type' => 'none', + ], + ], + 'hidden' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.hidden', + 'config' => [ + 'type' => 'check', + ], + ], + 'rating' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.vote.rating', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_rating', + 'maxitems' => 1, + 'minitems' => 1, + 'disableNoMatchingValueElement' => 1, + ], + ], + 'voter' => [ + 'exclude' => 1, + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.vote.voter', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'fe_users', + 'foreign_table_where' => 'ORDER BY fe_users.username', + 'items' => [ + [ + '--div--', 0 + ], + ], + 'fieldControl' => [ + 'editPopup' => [ + 'disabled' => false, + ], + 'addRecord' => [ + 'disabled' => false, + 'options' => [ + 'setValue' => 'prepend', + ], + 'listModule' => [ + 'disabled' => false, + ], + ], + ], + ], + ], + //TODO Prio 3: only provide valid references from foreign table + 'vote' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.vote.vote', + 'config' => [ + 'type' => 'select', + 'renderType' => 'selectSingle', + 'foreign_table' => 'tx_bookmarkslikesratings_domain_model_stepconf', + 'maxitems' => 1, + 'minitems' => 1, + 'disableNoMatchingValueElement' => 1, + ], + ], + 'uid' => [ + 'label' => 'LLL:EXT:bookmarks_likes_ratings/Resources/Private/Language/locallang.xlf:tca.model.vote.uid', + 'config' => [ + 'type' => 'none' + ], + ], + ], + 'types' => [ + '1' => [ + 'showitem' => 'hidden, rating, voter, vote' + ], + ], + 'palettes' => [ + '1' => [ + 'showitem' => '' + ], + ], +]; diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript index f3a85f3..e421dbb 100644 --- a/Configuration/TypoScript/constants.typoscript +++ b/Configuration/TypoScript/constants.typoscript @@ -1,11 +1,11 @@ plugin.tx_wsbookmarkpages { view { # cat=plugin.tx_wsbookmarkpages/file; type=string; label=Path to template root (FE) - templateRootPath = EXT:ws_bookmark_pages/Resources/Private/Templates/ + templateRootPath = EXT:bookmarks_likes_ratings/Resources/Private/Templates/ # cat=plugin.tx_wsbookmarkpages/file; type=string; label=Path to template partials (FE) - partialRootPath = EXT:ws_bookmark_pages/Resources/Private/Partials/ + partialRootPath = EXT:bookmarks_likes_ratings/Resources/Private/Partials/ # cat=plugin.tx_wsbookmarkpages/file; type=string; label=Path to template layouts (FE) - layoutRootPath = EXT:ws_bookmark_pages/Resources/Private/Layouts/ + layoutRootPath = EXT:bookmarks_likes_ratings/Resources/Private/Layouts/ } settings { # cat=plugin.tx_wsbookmarkpages/general; type=boolean; label=Store bookmarks local:If set the bookmarks will be stored in the local storage from the clients browser. diff --git a/Documentation/AdministratorManual/Index.rst b/Documentation/AdministratorManual/Index.rst deleted file mode 100644 index b245b57..0000000 --- a/Documentation/AdministratorManual/Index.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. include:: ../Includes.txt - - -.. _admin-manual: - -Administrator Manual -==================== - -Installation ------------- - -There are two ways to properly install the extension. - -1. Composer installation -^^^^^^^^^^^^^^^^^^^^^^^^ - -In case you use Composer to manage dependencies of your TYPO3 project, -you can just issue the following Composer command in your project root directory. - -.. code-block:: bash - - composer require wapplersystems/bookmark-pages - -2. Installation with Extension Manager -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Download and install the extension with the extension manager module. - diff --git a/Documentation/Changelog/2.0.0.rst b/Documentation/Changelog/2.0.0.rst deleted file mode 100644 index 7a20e1d..0000000 --- a/Documentation/Changelog/2.0.0.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. include:: ../Includes.txt - -.. highlight:: none - -==================================== -Changelog for release 2.0.0 -==================================== - -Features -======== -* [FEATURE] Enable multiple plugin use per page (22.04.2021, f335afc by roman) -* [FEATURE] Load bookmarks from browsers local storage (16.04.2021, 1c7511d by roman) -* [FEATURE] Cache main action, load list asynchronous (15.04.2021, d4e04e5 by roman) - -Bugfixes -======== -* [BUGFIX] Correct issue with empty list (22.04.2021, 1f87412 by Roman) -* [BUGFIX] Fix issue updating links (20.04.2021, 0a6e47e by roman) -* [BUGFIX] Fix issue with empty accessible bookmarks (20.04.2021, 1b169ab by roman) - -Breaking changes -================ -* [!!!][TASK] Enable local storage per default (27.04.2021, 2e460ca by roman) -* [!!!][TASK] Remove access restriction (18.04.2021, 28c0248 by roman) -* [!!!][TASK] Refactor js (16.04.2021, 9a09b96 by roman) - -Reference -========= - -.. highlight:: shell - -Generated by: - -git log 1.2.2..2e460cac --pretty="* %s (%cd, %h by %an)" --date=format:%d.%m.%Y --abbrev-commit --grep - -**Note:** The above list contains just commits marked with [FEATURE], [BUGFIX] and [!!!]. Complementary commits are -available at `Github `__. - - diff --git a/Documentation/Configuration/Index.rst b/Documentation/Configuration/Index.rst deleted file mode 100644 index 6a175f3..0000000 --- a/Documentation/Configuration/Index.rst +++ /dev/null @@ -1,97 +0,0 @@ -.. include:: ../Includes.txt - - -============================== -Configuration -============================== - -The plugin output doesn't look very cute after install. It is necessary to adopt it to your needs. Nevertheless it shouldn't be too hard. - -.. tip:: - - Have a look at the comments in the example template. - - -Make it work -============ - -Here are the needed steps described to make the plugin work. - -Additional work might be needed to adapt is to your needs. Usually this is limited to template work. - - -1. Include TypoScript ---------------------- - -Include the TypoScript in your template record or your site package. - - - -2. Include Plugin ------------------ - -Include the `Bookmark Pages` plugin as content element. This is just for testing and not usually what you want. - -Login and you should see some output. On the introduction package it looks like this: - - -.. figure:: ../Images/screenshot-fe.png - - The login box is not part of this extension. - -The plugin might be inserted more than once on a page. In that case mark `Is complementary` under `Plugin Options` -for all plugins except the first one. - - -Customization -============= - - -1. TypoScript and Templates ---------------------------- - -You may just copy the needed parts to your site package. - -The fluid template paths can be configured as usual using TypoScript. Have a look into the TypoScript template -(look for plugin.tx_wsbookmarkpages.view) - -The bookmarks might be stored in the browsers local storage allowing users to bookmark pages without being registered -on the site. Storing the bookmarks locally as well safes unnecessary server requests. The feature can be enabled -through the constants editor. - - -2. JavaScript and JQuery ------------------------- - -The provided example uses JQuery for ajax requests. JQuery is included by TypoScript. You might want to remove that with: - -.. code-block:: typoscript - - page.includeJSFooterlibs.bookmark_pages_jquery > - -If you don't use JQuery you have to adapt the JavaScript. -Have a look into Resources/Public/Scripts/JavaScript/bookmark_pages.js - - -3. Include in your page template --------------------------------- - -The rendering of the bookmarks list might be something you want to include into your page template. This could be done -in a fluid template like this: - - -.. code-block:: html - - - - -Of course you want a bookmark button on every page. This can be done in fluid like this: - -.. code-block:: html - - - -You can place the snippet in any template not just the plugin templates. - diff --git a/Documentation/Images/screenshot-fe.png b/Documentation/Images/screenshot-fe.png deleted file mode 100644 index 2311908c739f0c33cf42697f8a01b4ad2ae8b022..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16333 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 diff --git a/Documentation/Images/screenshot-styled-fe.png b/Documentation/Images/screenshot-styled-fe.png deleted file mode 100644 index 9d87f6c32c69112030ecf12f320d2fcc3e2f82d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13366 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 diff --git a/Documentation/Includes.txt b/Documentation/Includes.txt deleted file mode 100644 index 427bdb5..0000000 --- a/Documentation/Includes.txt +++ /dev/null @@ -1,14 +0,0 @@ -.. This is 'Includes.txt'. It is included at the very top of each and - every ReST source file in THIS documentation project (= manual). - -.. role:: aspect (emphasis) -.. role:: html(code) -.. role:: js(code) -.. role:: php(code) -.. role:: typoscript(code) - -.. role:: ts(typoscript) - :class: typoscript - -.. default-role:: code -.. highlight:: php diff --git a/Documentation/Index.rst b/Documentation/Index.rst deleted file mode 100644 index ad29c61..0000000 --- a/Documentation/Index.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. ================================================== -.. FOR YOUR INFORMATION -.. -------------------------------------------------- -.. -*- coding: utf-8 -*- with BOM. - -.. include:: Includes.txt - - -.. _start: - -============================================================= -Bookmark Pages -============================================================= - -.. only:: html - - :Classification: - bookmark_pages - - :Version: - |release| - - :Language: - en - - :Description: - Provides bookmarks functionality of local pages for logged in frontend users - - :Keywords: - bookmark page - - :Copyright: - 2016-2020 - - :Author: - René Fritz - - :License: - This document is published under the Open Publication License - available from http://www.opencontent.org/openpub/ - - :Rendered: - |today| - - The content of this document is related to TYPO3, - a GNU/GPL CMS/Framework available from `www.typo3.org `_. - - -.. toctree:: - :hidden: - - Sitemap/Index - Introduction/Index - Configuration/Index - AdministratorManual/Index - Support/Index - Links diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst deleted file mode 100644 index 20902e9..0000000 --- a/Documentation/Introduction/Index.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. include:: ../Includes.txt - - -Introduction -============ - -What does it do? ----------------- - -This is a TYPO3 plugin that provides bookmarks functionality of local pages. -For example this is very helpful for intranet websites. - -The plugin can be configured to use the browsers local storage allowing non registered users to use the feature too. -In case a website user likes to have the bookmarks available on different devices a frontend account might be used. -After logging in the bookmarks are saved to the server hence might be accessed from within any device. - -TypoScript and fluid templates are just examples. Adapt it to your needs. - -The extension was created by `René Fritz `__. The ownership has been transferred in -april 2021. - - -Screenshots ------------ - - -.. figure:: ../Images/screenshot-styled-fe.png - - The screenshot shows the functionality, the styling is not included. - - -You have a list with bookmarks which can be deleted and you can place a 'bookmark' button anywhere on the page. - diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg deleted file mode 100644 index eab4681..0000000 --- a/Documentation/Settings.cfg +++ /dev/null @@ -1,17 +0,0 @@ -# coding: utf-8 - -[general] -project = bookmark_pages -release = 2.0.1 -version = 2.0.1 - -[html_theme_options] -github_branch = master -github_repository = buepro/typo-bookmark_pages -project_contact = rb@buechler.pro -project_home = https://github.com/buepro/typo3-bookmark_pages -project_issues = https://github.com/buepro/typo3-bookmark_pages/issues -project_repository = https://github.com/buepro/typo3-bookmark_pages - -[extensions] -issue = https://github.com/buepro/typo3-bookmark_pages/issues/%s | Issue # diff --git a/Documentation/Sitemap/Index.rst b/Documentation/Sitemap/Index.rst deleted file mode 100644 index 85fa8c0..0000000 --- a/Documentation/Sitemap/Index.rst +++ /dev/null @@ -1,12 +0,0 @@ - -:template: sitemap.html - - -.. _Sitemap: - -======= -Sitemap -======= - -.. template 'sitemap.html' will insert the toctree as a sitemap here - below normal contents diff --git a/Documentation/Support/Index.rst b/Documentation/Support/Index.rst deleted file mode 100644 index 61d34e4..0000000 --- a/Documentation/Support/Index.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. include:: ../Includes.txt - - -Support and Feedback -==================== - -The extension is provided as is. Please understand that I can't give an extensive amount of support time for free. - -Any feedback is always appreciated. - -So if you have questions or feedback there are several options: - -Bug Tracker ------------ - -For bug reports and feature requests: https://github.com/buepro/typo3-bookmark_pages/issues - -Commercial support ------------------- - -If you need commercial support, get in touch. - -Nice mails ----------- - -Some nice words fit every time - just drop me some kind words by mail to make me happy :-) - - diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d7f1051..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,339 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {description} - Copyright (C) {year} {fullname} - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/README.md b/README.md deleted file mode 100644 index da45f01..0000000 --- a/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Bookmark Pages TYPO3 Extension - -This is a TYPO3 extension that provides bookmarks functionality. In case the use from the browsers local storage is -disabled just logged in frontend users can bookmark pages. - -The extension was created by [René Fritz](https://github.com/colorcube). The ownership has been transferred in -april 2021. - -## Usage - -TypoScript and fluid templates are just examples. Adapt it to your needs. - -Further information: https://docs.typo3.org/p/wapplersystems/bookmark-pages/master/en-us/ - -### Dependencies - -* TYPO3 10.4 - -### Installation - -#### Installation using Composer - -In your Composer based TYPO3 project root, just do `composer require wapplersystems/bookmark-pages`. - -#### Installation as extension from TYPO3 Extension Repository (TER) - -Download and install the extension with the extension manager module. - diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 73c89a9..290b5e0 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -1,29 +1,17 @@ + product-name="bookmarks_likes_ratings">
- - Yes + + + + + Ratings - - Default - - - Alternative - - - Bookmark this page - - - Is complementary - - - In case the plugin is rendered more times on a page this option should be set for all instances except the first one. - - - List type + + Top likes diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html index 6eaa733..6871db9 100644 --- a/Resources/Private/Layouts/Default.html +++ b/Resources/Private/Layouts/Default.html @@ -20,4 +20,4 @@ - + diff --git a/Resources/Private/Templates/Bookmarks/Index.html b/Resources/Private/Templates/Bookmarks/Index.html index f4001a4..74c395b 100644 --- a/Resources/Private/Templates/Bookmarks/Index.html +++ b/Resources/Private/Templates/Bookmarks/Index.html @@ -1,7 +1,6 @@ + xmlns:f="http://xsd.helmut-hummel.de/ns/TYPO3/CMS/Fluid/ViewHelpers"> diff --git a/composer.json b/composer.json index af77f4d..acca703 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "wapplersystems/bookmark-pages", + "name": "wapplersystems/bookmarks-likes-ratings", "type": "typo3-cms-extension", "description": "A TYPO3 extension that provides bookmarks functionality of local pages.", "keywords": [ @@ -21,17 +21,14 @@ }, "autoload": { "psr-4": { - "WapplerSystems\\WsBookmarkPages\\": "Classes/" + "WapplerSystems\\BookmarksLikesRatings\\": "Classes/" } }, "replace": { }, "extra": { "typo3/cms": { - "extension-key": "ws_bookmark_pages" - }, - "branch-alias": { - "dev-master": "2.x-dev" + "extension-key": "bookmarks_likes_ratings" } } } diff --git a/ext_emconf.php b/ext_emconf.php index e7dd31e..9826230 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -8,13 +8,13 @@ */ $EM_CONF[$_EXTKEY] = [ - 'title' => 'Bookmark Pages', - 'description' => 'Provides bookmarks functionality of local pages for logged in frontend users.', + 'title' => 'Bookmarks/Likes/Ratings', + 'description' => 'Provides bookmarks/likes/ratings functionality of frontend pages and articles for logged in frontend users.', 'category' => 'plugin', - 'author' => 'René Fritz, Roman Büchler', - 'author_email' => 'r.fritz@colorcube.de, rb@buechler.pro', - 'author_company' => 'Colorcube, buechler.pro gmbh', - 'version' => '2.0.1', + 'author' => 'Sven Wappler', + 'author_email' => 'typo3@wappler.systems', + 'author_company' => 'WapplerSystems', + 'version' => '0.0.0', 'state' => 'stable', 'uploadfolder' => 0, 'createDirs' => '', @@ -22,17 +22,13 @@ $EM_CONF[$_EXTKEY] = [ 'clearCacheOnLoad' => 1, 'constraints' => [ 'depends' => [ - 'typo3' => '10.4.14-10.4.99', - 'typoscript_rendering' => '2.3.1-2.99.99', - ], - 'conflicts' => [], - 'suggests' => [ - 'news' => '*' + 'typo3' => '10.4.0-10.4.99' ], + 'conflicts' => [] ], 'autoload' => [ 'psr-4' => [ - 'WapplerSystems\\BookmarkPages\\' => 'Classes' + 'WapplerSystems\\BookmarksLikesRatings\\' => 'Classes' ] ] ]; diff --git a/ext_localconf.php b/ext_localconf.php index c5e0d69..e4dc831 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -10,32 +10,16 @@ defined('TYPO3') or die('Access denied.'); (function () { - $version = \TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(); - $version = \TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger($version); - if ($version < 10000000) { - // For TYPO3 < V10 - // @extensionScannerIgnoreLine - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'WapplerSystems.BookmarkPages', - 'Bookmarks', - [ - 'Bookmarks' => 'index, bookmark, delete, listEntries', - ], - // non-cacheable actions - [ - 'Bookmarks' => 'bookmark, delete, listEntries', - ] - ); - } else { - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( - 'BookmarkPages', - 'Bookmarks', - [ - \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'index, bookmark, delete, listEntries' - ], - [ - \WapplerSystems\BookmarkPages\Controller\BookmarksController::class => 'bookmark, delete, listEntries' - ] - ); - } + + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin( + 'BookmarkPages', + 'Bookmarks', + [ + \WapplerSystems\BookmarksLikesRatings\Controller\BookmarksController::class => 'index, bookmark, delete, listEntries' + ], + [ + \WapplerSystems\BookmarksLikesRatings\Controller\BookmarksController::class => 'bookmark, delete, listEntries' + ] + ); + })(); diff --git a/ext_tables.sql b/ext_tables.sql index f622475..bcc9ddd 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -1,7 +1,144 @@ # # Add field to table 'fe_users' # -CREATE TABLE fe_users ( +CREATE TABLE fe_users +( tx_bookmarks_pages blob ); +CREATE TABLE tx_bookmarkslikesratings_model_bookmark +( + +); + + +# +# Table structure for table 'tx_bookmarkslikesratings_domain_model_ratingobject' +# +CREATE TABLE tx_bookmarkslikesratings_domain_model_ratingobject +( + uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + ratetable varchar(64) DEFAULT '' NOT NULL, + ratefield varchar(64) DEFAULT '' NOT NULL, + stepconfs int(11) DEFAULT '0' NOT NULL, + ratings int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + is_dummy_record tinyint(1) unsigned DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY phpunit_dummy (is_dummy_record), + INDEX tx_bookmarkslikesratings_domain_model_ratingobject_i1 (ratetable, ratefield) +); + +# +# Table structure for table 'tx_bookmarkslikesratings_domain_model_stepconf' +# +CREATE TABLE tx_bookmarkslikesratings_domain_model_stepconf +( + uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + ratingobject int(11) DEFAULT '0' NOT NULL, + steporder int(11) DEFAULT '1' NOT NULL, + stepweight int(11) DEFAULT '1' NOT NULL, + stepname int(11) DEFAULT '0' NOT NULL, + votes int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) DEFAULT '0' NOT NULL, + is_dummy_record tinyint(1) unsigned DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY phpunit_dummy (is_dummy_record), + INDEX tx_bookmarkslikesratings_domain_model_stepconf_i1 (ratingobject, steporder) +); + +# +# Table structure for table 'tx_bookmarkslikesratings_domain_model_stepname' +# +CREATE TABLE tx_bookmarkslikesratings_domain_model_stepname +( + uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + stepconf int(11) DEFAULT '0' NOT NULL, + stepname varchar(64) DEFAULT '' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + t3_origuid int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + sys_language_uid int(11) DEFAULT '0' NOT NULL, + l18n_parent int(11) DEFAULT '0' NOT NULL, + l18n_diffsource mediumblob NOT NULL, + is_dummy_record tinyint(1) unsigned DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY phpunit_dummy (is_dummy_record), +); + +# +# Table structure for table 'tx_bookmarkslikesratings_domain_model_rating' +# +CREATE TABLE tx_bookmarkslikesratings_domain_model_rating +( + uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + ratingobject int(11) DEFAULT '0' NOT NULL, + ratedobjectuid int(11) DEFAULT '0' NOT NULL, + votes int(11) DEFAULT '0' NOT NULL, + currentrates varchar(255) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + is_dummy_record tinyint(1) unsigned DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY phpunit_dummy (is_dummy_record), + INDEX tx_bookmarkslikesratings_domain_model_rating_i1 (ratingobject, ratedobjectuid) +); + +# +# Table structure for table 'tx_bookmarkslikesratings_domain_model_vote' +# +CREATE TABLE tx_bookmarkslikesratings_domain_model_vote +( + uid int(11) unsigned DEFAULT '0' NOT NULL auto_increment, + pid int(11) DEFAULT '0' NOT NULL, + + rating int(11) DEFAULT '0' NOT NULL, + voter int(11) DEFAULT '0' NOT NULL, + vote int(11) DEFAULT '0' NOT NULL, + + tstamp int(11) unsigned DEFAULT '0' NOT NULL, + crdate int(11) unsigned DEFAULT '0' NOT NULL, + cruser_id int(11) DEFAULT '0' NOT NULL, + deleted tinyint(4) unsigned DEFAULT '0' NOT NULL, + hidden tinyint(4) unsigned DEFAULT '0' NOT NULL, + is_dummy_record tinyint(1) unsigned DEFAULT '0' NOT NULL, + + PRIMARY KEY (uid), + KEY parent (pid), + KEY phpunit_dummy (is_dummy_record), + INDEX tx_bookmarkslikesratings_domain_model_vote_i1 (rating, voter) +);