From fd39c9ad64b3525cd295425d9e996faf5cd4b958 Mon Sep 17 00:00:00 2001 From: Georgi Mateev <georgi.mateev@sgalinski.de> Date: Wed, 12 Jul 2023 14:39:07 +0300 Subject: [PATCH] [BUGFIX] Code style --- Classes/Backend/Ajax.php | 50 +- Classes/Controller/VimeoController.php | 3 +- Classes/Form/Element/LicenceStatus.php | 25 +- Classes/Hooks/LicenceCheckHook.php | 38 +- .../Hooks/PageLayoutView/PluginRenderer.php | 7 +- Classes/Preview/PreviewRenderer.php | 3 +- Classes/Preview/PreviewService.php | 96 +- Classes/Service/ExtensionSettingsService.php | 117 +- Classes/Service/LicenceCheckService.php | 1390 +++++++++-------- Classes/Service/VimeoService.php | 520 +++--- Classes/ViewHelpers/RenderSvgViewHelper.php | 100 +- .../StructuredVideoDataViewHelper.php | 113 +- .../UrlWithQueryParametersViewHelper.php | 65 +- 13 files changed, 1296 insertions(+), 1231 deletions(-) diff --git a/Classes/Backend/Ajax.php b/Classes/Backend/Ajax.php index a2f37d9..7c26119 100644 --- a/Classes/Backend/Ajax.php +++ b/Classes/Backend/Ajax.php @@ -1,7 +1,5 @@ <?php -namespace SGalinski\SgVimeo\Backend; - /*************************************************************** * Copyright notice * @@ -26,39 +24,39 @@ namespace SGalinski\SgVimeo\Backend; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ -use Exception; +namespace SGalinski\SgVimeo\Backend; + use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use SGalinski\SgVimeo\Service\LicenceCheckService; use TYPO3\CMS\Core\Http\Response; -use TYPO3\CMS\Extbase\Utility\LocalizationUtility; /** * Class Ajax * * @package SGalinski\SgVimeo\Backend */ -class Ajax { - /** - * Checks whether the license is valid - * - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @return ResponseInterface - * @throws \InvalidArgumentException - * @throws Exception - */ - public function checkLicense( - ServerRequestInterface $request, - ResponseInterface $response = NULL - ) { - if ($response === NULL) { - $response = new Response(); - } +class Ajax +{ + /** + * Checks whether the license is valid + * + * @param ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface|null $response + * @return ResponseInterface + */ + public function checkLicense( + ServerRequestInterface $request, + ResponseInterface $response = NULL + ) + { + if ($response === NULL) { + $response = new Response(); + } - LicenceCheckService::setLastAjaxNotificationCheckTimestamp(); - $responseData = LicenceCheckService::getLicenseCheckResponseData(TRUE); - $response->getBody()->write(json_encode($responseData)); - return $response; - } + LicenceCheckService::setLastAjaxNotificationCheckTimestamp(); + $responseData = LicenceCheckService::getLicenseCheckResponseData(TRUE); + $response->getBody()->write(json_encode($responseData)); + return $response; + } } diff --git a/Classes/Controller/VimeoController.php b/Classes/Controller/VimeoController.php index 329cfc9..a4314d3 100644 --- a/Classes/Controller/VimeoController.php +++ b/Classes/Controller/VimeoController.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; use TYPO3\CMS\Core\Resource\FileReference; use TYPO3\CMS\Core\Resource\FileRepository; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\VersionNumberUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Service\ImageService; @@ -124,7 +125,7 @@ class VimeoController extends ActionController { ] ); - if (version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '12.0.0', '>=')) { + if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '12.0.0', '>=')) { return $this->jsonResponse(); } } diff --git a/Classes/Form/Element/LicenceStatus.php b/Classes/Form/Element/LicenceStatus.php index f2cc4b5..c39c2de 100644 --- a/Classes/Form/Element/LicenceStatus.php +++ b/Classes/Form/Element/LicenceStatus.php @@ -17,6 +17,7 @@ * GNU General Public License for more details. * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ + namespace SGalinski\SgVimeo\Form\Element; use SGalinski\SgVimeo\Service\LicenceCheckService; @@ -35,20 +36,20 @@ class LicenceStatus extends AbstractFormElement return []; } - switch ($responseData['error']) { - case 1: - $errorOrWarning = 'danger'; - break; - case 2: - $errorOrWarning = 'warning'; - break; - default: - $errorOrWarning = 'success'; - } + switch ($responseData['error']) { + case 1: + $errorOrWarning = 'danger'; + break; + case 2: + $errorOrWarning = 'warning'; + break; + default: + $errorOrWarning = 'success'; + } - $message = '<div class="alert alert-'. $errorOrWarning .'" role="' + $message = '<div class="alert alert-' . $errorOrWarning . '" role="' . $errorOrWarning - . '">' . $responseData['message'] . '</div>'; + . '">' . $responseData['message'] . '</div>'; $resultArray['html'] = $message; return $resultArray; } diff --git a/Classes/Hooks/LicenceCheckHook.php b/Classes/Hooks/LicenceCheckHook.php index 5e2c989..223aa4e 100644 --- a/Classes/Hooks/LicenceCheckHook.php +++ b/Classes/Hooks/LicenceCheckHook.php @@ -1,4 +1,5 @@ <?php + /** * * Copyright notice @@ -38,22 +39,25 @@ use TYPO3\CMS\Extbase\Utility\LocalizationUtility; * @package SGalinski\ProjectBase\Hook * @author Georgi Mateev <georgi.mateev@sgalinski.de> */ -class LicenceCheckHook { - /** - * Add JavaScript to display the expiring license warning - */ - protected function addAjaxLicenseCheck() { - $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); - $pageRenderer->loadRequireJsModule('TYPO3/CMS/SgVimeo/Backend/LicenseNotification'); - } +class LicenceCheckHook +{ + /** + * Add JavaScript to display the expiring license warning + */ + protected function addAjaxLicenseCheck() + { + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $pageRenderer->loadRequireJsModule('TYPO3/CMS/SgVimeo/Backend/LicenseNotification'); + } - /** - * Checks if the license key is OK - * - * @param array $configuration - * @param BackendController $parentBackendController - */ - public function performLicenseCheck(array $configuration, BackendController $parentBackendController) { + /** + * Checks if the license key is OK + * + * @param array $configuration + * @param BackendController $parentBackendController + */ + public function performLicenseCheck(array $configuration, BackendController $parentBackendController) + { if (!LicenceCheckService::isTYPO3VersionSupported() || !LicenceCheckService::isTimeForNextCheck() || LicenceCheckService::isInDevelopmentContext() @@ -61,6 +65,6 @@ class LicenceCheckHook { return; } - $this->addAjaxLicenseCheck(); - } + $this->addAjaxLicenseCheck(); + } } diff --git a/Classes/Hooks/PageLayoutView/PluginRenderer.php b/Classes/Hooks/PageLayoutView/PluginRenderer.php index bb41fba..62ed516 100644 --- a/Classes/Hooks/PageLayoutView/PluginRenderer.php +++ b/Classes/Hooks/PageLayoutView/PluginRenderer.php @@ -1,7 +1,5 @@ <?php -namespace SGalinski\SgVimeo\Hooks\PageLayoutView; - /*************************************************************** * Copyright notice * (c) sgalinski Internet Services (https://www.sgalinski.de) @@ -20,6 +18,8 @@ namespace SGalinski\SgVimeo\Hooks\PageLayoutView; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +namespace SGalinski\SgVimeo\Hooks\PageLayoutView; + use SGalinski\SgVimeo\Preview\PreviewService; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\View\PageLayoutView; @@ -124,7 +124,8 @@ if (interface_exists('TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface } } } else { - class PluginRenderer { + class PluginRenderer + { } } diff --git a/Classes/Preview/PreviewRenderer.php b/Classes/Preview/PreviewRenderer.php index 55290a9..34e3ab9 100644 --- a/Classes/Preview/PreviewRenderer.php +++ b/Classes/Preview/PreviewRenderer.php @@ -58,8 +58,7 @@ class PreviewRenderer implements PreviewRendererInterface { * @return string */ public function renderPageModulePreviewContent(GridColumnItem $item): string { - $view = $this->previewService->getPluginView($item->getRecord()); - return $view->render(); + return $this->previewService->getPluginView($item->getRecord())->render(); } /** diff --git a/Classes/Preview/PreviewService.php b/Classes/Preview/PreviewService.php index 79dd150..b567bb0 100644 --- a/Classes/Preview/PreviewService.php +++ b/Classes/Preview/PreviewService.php @@ -17,6 +17,7 @@ * GNU General Public License for more details. * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ + namespace SGalinski\SgVimeo\Preview; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -25,58 +26,61 @@ use TYPO3\CMS\Fluid\View\StandaloneView; /** * PreviewService, to get Views while we have to duplicate previewCode */ -class PreviewService { - public const RETURNTYPE_ARR = 'array'; +class PreviewService +{ + public const RETURNTYPE_ARR = 'array'; - public function getPluginView(array $row): StandaloneView { - /** @var StandaloneView $view */ - $view = GeneralUtility::makeInstance(StandaloneView::class); - $view->setPartialRootPaths(['EXT:sg_vimeo/Resources/Private/Partials/Backend']); - $view->setTemplateRootPaths(['EXT:sg_vimeo/Resources/Private/Templates/Vimeo']); - $view->setTemplate('Backend.html'); - $view->assign('uid', $row['uid']); + public function getPluginView(array $row): StandaloneView + { + /** @var StandaloneView $view */ + $view = GeneralUtility::makeInstance(StandaloneView::class); + $view->setPartialRootPaths(['EXT:sg_vimeo/Resources/Private/Partials/Backend']); + $view->setTemplateRootPaths(['EXT:sg_vimeo/Resources/Private/Templates/Vimeo']); + $view->setTemplate('Backend.html'); + $view->assign('uid', $row['uid']); - // Get available plugin settings and their values from flexform - $pluginConfiguration = GeneralUtility::xml2array( - $row['pi_flexform'], - 'T3DataStructure' - )['data']['sDEF']['lDEF']; + // Get available plugin settings and their values from flexform + $pluginConfiguration = GeneralUtility::xml2array( + $row['pi_flexform'], + 'T3DataStructure' + )['data']['sDEF']['lDEF']; - $templateData = [ - 'vimeoId' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.id'), - 'maxResults' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.maxResults'), - 'showTitle' => (int) ($this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showTitle') ?? 1), - 'showDescription' => (int) ($this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showDescription') ?? 1), - 'disableLightbox' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.disableLightbox'), - 'disableLightboxMobile' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.disableLightboxMobile'), - 'aspectRatio' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.aspectRatio'), - 'thumbnailType' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.thumbnailType'), - 'thumbnailImagesCount' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.thumbnailImages'), - 'showApiResult' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showApiResult'), - 'urlParameters' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.urlParameters'), - ]; + $templateData = [ + 'vimeoId' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.id'), + 'maxResults' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.maxResults'), + 'showTitle' => (int)($this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showTitle') ?? 1), + 'showDescription' => (int)($this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showDescription') ?? 1), + 'disableLightbox' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.disableLightbox'), + 'disableLightboxMobile' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.disableLightboxMobile'), + 'aspectRatio' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.aspectRatio'), + 'thumbnailType' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.thumbnailType'), + 'thumbnailImagesCount' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.thumbnailImages'), + 'showApiResult' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.showApiResult'), + 'urlParameters' => $this->passVDefOnKeyToTemplate($pluginConfiguration, 'settings.urlParameters'), + ]; - $view->assign('data', $templateData); - return $view; - } + $view->assign('data', $templateData); + return $view; + } - /** - * @param array $conf - * @param string $key - * @param string $returnType - * @return array|mixed|string - */ - private function passVDefOnKeyToTemplate(array $conf, string $key, string $returnType = '') { - if (isset($conf[$key])) { - return $conf[$key]['vDEF']; - } + /** + * @param array $conf + * @param string $key + * @param string $returnType + * @return array|mixed|string + */ + private function passVDefOnKeyToTemplate(array $conf, string $key, string $returnType = '') + { + if (isset($conf[$key])) { + return $conf[$key]['vDEF']; + } - // check if we got a possible returntype: - if ($returnType === self::RETURNTYPE_ARR) { - return []; - } + // check if we got a possible returntype: + if ($returnType === self::RETURNTYPE_ARR) { + return []; + } - return ''; - } + return ''; + } } diff --git a/Classes/Service/ExtensionSettingsService.php b/Classes/Service/ExtensionSettingsService.php index 3c30c12..9dbb217 100644 --- a/Classes/Service/ExtensionSettingsService.php +++ b/Classes/Service/ExtensionSettingsService.php @@ -1,7 +1,5 @@ <?php -namespace SGalinski\SgVimeo\Service; - /*************************************************************** * Copyright notice * @@ -26,73 +24,78 @@ namespace SGalinski\SgVimeo\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +namespace SGalinski\SgVimeo\Service; + use TYPO3\CMS\Core\Utility\VersionNumberUtility; /** * Class SGalinski\SgVimeo\Service\ExtensionSettingsService */ -class ExtensionSettingsService { - const SETTING_LICENSE = 'key'; - const SETTING_FOLDER = 'folder'; - const SETTING_HIDE_MODULE_IN_PRODUCTION_CONTEXT = 'hideModuleInProductionContext'; +class ExtensionSettingsService +{ + const SETTING_LICENSE = 'key'; + const SETTING_FOLDER = 'folder'; + const SETTING_HIDE_MODULE_IN_PRODUCTION_CONTEXT = 'hideModuleInProductionContext'; - /** - * @var array Default settings mapped to constants. - */ - protected static $defaultValueMap = [ - self::SETTING_FOLDER => 'fileadmin/sg_vimeo/', - self::SETTING_HIDE_MODULE_IN_PRODUCTION_CONTEXT => FALSE, - ]; + /** + * @var array Default settings mapped to constants. + */ + protected static $defaultValueMap = [ + self::SETTING_FOLDER => 'fileadmin/sg_vimeo/', + self::SETTING_HIDE_MODULE_IN_PRODUCTION_CONTEXT => FALSE, + ]; - /** - * Returns the setting of one of the constants of this class. - * - * @param string $settingKey - * @return mixed - */ - public static function getSetting($settingKey) { - if (VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()) < 9000000) { - // the "options" parameter of unserialize exists since PHP 7.0.0 - if (version_compare(phpversion(), '7.0.0', '>=')) { - $configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['sg_vimeo'], [FALSE]); - } else { - $configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['sg_vimeo']); - } - } else { - $configuration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_vimeo'] ?? []; - } + /** + * Returns the setting of one of the constants of this class. + * + * @param string $settingKey + * @return mixed + */ + public static function getSetting($settingKey) + { + if (VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()) < 9000000) { + // the "options" parameter of unserialize exists since PHP 7.0.0 + if (version_compare(phpversion(), '7.0.0', '>=')) { + $configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['sg_vimeo'], [FALSE]); + } else { + $configuration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['sg_vimeo']); + } + } else { + $configuration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_vimeo'] ?? []; + } - $setting = ''; - if (isset(self::$defaultValueMap[$settingKey])) { - $setting = self::$defaultValueMap[$settingKey]; - } + $setting = ''; + if (isset(self::$defaultValueMap[$settingKey])) { + $setting = self::$defaultValueMap[$settingKey]; + } - if (isset($configuration[$settingKey])) { - $setting = self::postProcessSetting($configuration[$settingKey], $settingKey); - } + if (isset($configuration[$settingKey])) { + $setting = self::postProcessSetting($configuration[$settingKey], $settingKey); + } - return $setting; - } + return $setting; + } - /** - * Post process of the given setting, by the given setting key. - * - * @param mixed $value - * @param string $settingKey - * @return mixed - */ - protected static function postProcessSetting($value, $settingKey) { - if ($settingKey === self::SETTING_FOLDER) { - $value = trim($value, " \t\n\r\0\x0B\/") . '/'; + /** + * Post process of the given setting, by the given setting key. + * + * @param mixed $value + * @param string $settingKey + * @return mixed + */ + protected static function postProcessSetting($value, $settingKey) + { + if ($settingKey === self::SETTING_FOLDER) { + $value = trim($value, " \t\n\r\0\x0B\/") . '/'; - if (strpos($value, 'EXT:') === 0) { - $value = 'typo3conf/ext/' . substr($value, 4); - } - } + if (strpos($value, 'EXT:') === 0) { + $value = 'typo3conf/ext/' . substr($value, 4); + } + } - // TYPO3 6 stores all settings as strings, some are expected to be booleans, though. - $value = ($value === 'FALSE') ? FALSE : $value; + // TYPO3 6 stores all settings as strings, some are expected to be booleans, though. + $value = ($value === 'FALSE') ? FALSE : $value; - return $value; - } + return $value; + } } diff --git a/Classes/Service/LicenceCheckService.php b/Classes/Service/LicenceCheckService.php index 6619812..bf2d146 100644 --- a/Classes/Service/LicenceCheckService.php +++ b/Classes/Service/LicenceCheckService.php @@ -1,7 +1,5 @@ <?php -namespace SGalinski\SgVimeo\Service; - /*************************************************************** * Copyright notice * @@ -26,6 +24,8 @@ namespace SGalinski\SgVimeo\Service; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +namespace SGalinski\SgVimeo\Service; + use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Registry; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -37,680 +37,714 @@ use TYPO3\CMS\Extbase\Utility\LocalizationUtility; * * @package SGalinski\SgVimeo\Service */ -class LicenceCheckService { - const STATE_LICENSE_VALID = 2; - const STATE_LICENSE_INVALID = 1; - const STATE_LICENSE_NOT_SET = 0; - - const DEMO_MODE_KEY = 'demo_mode'; - const DEMO_MODE_LIFETIME = 86400; - const DEMO_MODE_MAX_AMOUNT = 3; - - /** - * The product key from ShopWare - */ - const PRODUCT_KEY = 'sg_vimeo'; - - /** - * Namespace for the sys registry - */ - const REGISTRY_NAMESPACE = 'tx_sgvimeo'; - - /** - * Keys for the sys registry - */ - const IS_KEY_VALID_KEY = 'isKeyValid'; - const LAST_WARNING_TIMESTAMP_KEY = 'lastWarningTimestamp'; - const HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY = 'hasValidLicenseUntilTimestamp'; - const LICENSE_CHECKED_IN_VERSION_KEY = 'licenceCheckedInVersion'; - const LAST_CHECKED_TIMESTAMP_KEY = 'lastCheckedTimestamp'; - const LAST_AJAX_TIMESTAMP_KEY = 'lastAjaxTimestamp'; - const LAST_LICENSE_KEY_CHECKED_KEY = 'lastLicenseKeyChecked'; - - /** - * Error codes - */ - const ERROR_INVALID_RESPONSE_CODE = -1; - const ERROR_INVALID_RESPONSE_DATA = -2; - const ERROR_INVALID_LICENSE_KEY = -3; - const ERROR_INVALID_LICENSE_STRUCTURE = -4; - const ERROR_TIMESTAMP_INVALID = -5; - const ERROR_LICENSE_CHECK_EXCEPTION = -6; - - /** - * Earliest TYPO3 Version that we support - */ - const EARLIEST_SUPPORTED_VERSION = 8000000; - - /** - * Last response code from server - * - * @var int - */ - protected static $lastHttpResponseCode = 0; - - /** - * The last exception from the server - * - * @var string - */ - protected static $lastException = ''; - - /** - * The validUntil timestamp - * - * @var null|int - */ - protected static $validUntil; - - /** - * Check the license key once per how many days - */ - const AMOUNT_OF_DAYS_UNTIL_NEXT_CHECK = 1; - - /** - * Show a warning if the license has expired but we are still in the same version once per how many days - */ - const AMOUNT_OF_DAYS_UNTIL_WARNING = 30; - - /** - * License server credentials - */ - const API_USER = 'license_check'; - const API_PASSWORD = 'lGKLiHc5We6gBqsggVlwdLNoWv9CEKnWiy7cgMUO'; - const API_URL = 'https://shop.sgalinski.de/api/license'; - - /** - * @var array - */ - private static $versionToReleaseTimestamp = [ - '2.5.0' => 1688468637, // Tue, 04 Jul 2023 14:11:50 GMT - ]; - - /** - * The current extension version - */ - const CURRENT_VERSION = '2.5.0'; - - /** - * @param mixed $validUntil A timestamp, which says the lifetime of this key. - * @return boolean True, if the timestamp is invalid. - */ - public static function isTimestampInvalid($validUntil) { - if ($validUntil < 0) { - return TRUE; - } - $releaseTimestampOfCurrentVersion = self::$versionToReleaseTimestamp[self::CURRENT_VERSION]; - if ($releaseTimestampOfCurrentVersion === NULL || $validUntil < $releaseTimestampOfCurrentVersion) { - return TRUE; - } - - self::$validUntil = $validUntil; - return FALSE; - } - - /** - * Should we perform the license check for this key and in this version at this point of time - * - * @param string $licenseKey - * @return bool - */ - public static function shouldCheckKey($licenseKey) { - if ($licenseKey !== self::getLastKey()) { - return TRUE; - } - - if (self::getLicenseCheckedInVersion() !== self::CURRENT_VERSION) { - return TRUE; - } - - // the license was valid last time we checked, but it has expired and we haven't done another check since it expired - // let's make sure we don't have the wrong state in this case - $licenseExpirationDate = self::getValidLicenseUntilTimestamp(); - /** @noinspection NotOptimalIfConditionsInspection */ - return self::getValidLicense() && $licenseExpirationDate < $GLOBALS['EXEC_TIME'] - && $licenseExpirationDate >= self::getLastLicenseCheckTimestamp(); - } - - /** - * Returns the license key that has been set - * - * @return string - */ - public static function getLicenseKey() { - return (string) ExtensionSettingsService::getSetting(ExtensionSettingsService::SETTING_LICENSE); - } - - /** - * Checks whether the system has a valid license - * - * @return bool - */ - public static function hasValidLicense() { - $licenseKey = self::getLicenseKey(); - if (!self::shouldCheckKey($licenseKey)) { - return self::getValidLicense(); - } - self::clearRegistryValues(); - - if (!self::isLicenseServerReachable()) { - return TRUE; - } - - if (!self::isLicenseValid($licenseKey)) { - self::setLastKey($licenseKey); - self::setValidLicense(FALSE); - self::setLicenseCheckedInVersion(self::CURRENT_VERSION); - self::setValidLicenseUntilTimestamp(0); - self::setLastLicenseCheckTimestamp(); - } else { - self::setValidLicenseUntilTimestamp(self::getValidUntil()); - self::setValidLicense(TRUE); - self::setLastKey($licenseKey); - self::setLicenseCheckedInVersion(self::CURRENT_VERSION); - self::setLastLicenseCheckTimestamp(); - return TRUE; - } - - return FALSE; - } - - /** - * Sets the last key checked for from the registry - * - * @param string $licenseKey - */ - protected static function setLastKey($licenseKey) { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY, $licenseKey); - } - - /** - * Gets the last key checked for from the registry - * - * @return mixed|null - */ - public static function getLastKey() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY); - } - - /** - * Sets if the license is valid in the registry - * - * @param bool $isValid - */ - protected static function setValidLicense($isValid) { - $isValid = (bool) $isValid; - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY, $isValid); - } - - /** - * Gets the isValid from the registry - * - * @return bool - */ - protected static function getValidLicense() { - $registry = GeneralUtility::makeInstance(Registry::class); - return (bool) $registry->get(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY); - } - - /** - * Stores the last warning timestamp - * - * @param int $timestamp - */ - protected static function setLastWarningTimestamp($timestamp) { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY, $timestamp); - } - - /** - * Gets the last warning timestamp - * - * @return mixed|null - */ - protected static function getLastWarningTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY); - } - - /** - * Stores the valid until timestamp in the registry - * - * @param mixed $validUntil - */ - protected static function setValidLicenseUntilTimestamp($validUntil) { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY, $validUntil); - } - - /** - * Gets the valid until timestamp from the registry - * - * @return mixed|null - */ - protected static function getValidLicenseUntilTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY); - } - - /** - * Sets the version that the license was last valid for - * - * @param string $version - */ - protected static function setLicenseCheckedInVersion($version) { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY, $version); - } - - /** - * Gets the version that the license was last valid for - * - * @return mixed|null - */ - protected static function getLicenseCheckedInVersion() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY); - } - - /** - * Sets the timestamp of the last check in the registry - */ - protected static function setLastLicenseCheckTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY, $GLOBALS['EXEC_TIME']); - } - - /** - * Gets the timestamp of the last check from the registry - * - * @return mixed|null - */ - public static function getLastLicenseCheckTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY); - } - - /** - * Sets the timestamp of the last AJAX Notification check in the registry - */ - public static function setLastAjaxNotificationCheckTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY, $GLOBALS['EXEC_TIME']); - } - - /** - * Gets the timestamp of the last AJAX Notification check from the registry - * - * @return mixed|null - */ - protected static function getLastAjaxNotificationCheckTimestamp() { - $registry = GeneralUtility::makeInstance(Registry::class); - return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY); - } - - /** - * Clears the registry values - */ - protected static function clearRegistryValues() { - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY); - $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY); - } - - /** - * Gets the validUntil date from this current check - * - * @return mixed - */ - public static function getValidUntil() { - if (self::$validUntil === NULL) { - self::$validUntil = self::getValidLicenseUntilTimestamp(); - } - return self::$validUntil; - } - - /** - * The timestamp of the key lifetime, if the given license key is valid, or -1 if invalid. - * - * @param string $licenseKey A license key, which should be validated. - * @return bool - */ - public static function isLicenseValid($licenseKey) { - if (!self::checkLicenseKeyStructure($licenseKey)) { - return FALSE; - } - - $validUntil = self::getValidUntilTimestampByLicenseKey($licenseKey); - return !self::isTimestampInvalid($validUntil); - } - - /** - * Check if the given license key is valid. - * - * @param string $licenseKey A license key, which should be validated. - * @return boolean - */ - public static function checkLicenseKeyStructure($licenseKey) { - // Structure: XXXXXX-XXXXXX-XXXXXX-XXXXXX | All upper case - if (substr_count($licenseKey, '-') !== 3) { - return FALSE; - } - - $caseControl = strtoupper($licenseKey); - return $licenseKey === $caseControl && strlen($licenseKey) === 27; - } - - /** - * True, if the license server is reachable. - * - * @return boolean - */ - public static function isLicenseServerReachable() { - try { - $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); - $response = $requestFactory->request( - self::API_URL, - 'GET', - [ - 'auth' => [self::API_USER, self::API_PASSWORD], - 'timeout' => 1, - 'connect_timeout' => 1, - ] - ); - - self::$lastHttpResponseCode = (int) $response->getStatusCode(); - - if (self::$lastHttpResponseCode !== 200 && self::$lastHttpResponseCode !== 201) { - return FALSE; - } - } catch (\Exception $exception) { - return FALSE; - } - - return TRUE; - } - - /** - * Returns The timestamp of the key lifetime, if the given license key is valid, on the server, or -1 if invalid. - * - * @param string $licenseKey - * @return int - */ - private static function getValidUntilTimestampByLicenseKey($licenseKey) { - try { - $url = self::API_URL . '/' . urldecode($licenseKey) . '?product=' - . self::PRODUCT_KEY; - $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); - $response = $requestFactory->request( - $url, - 'GET', - [ - 'auth' => [self::API_USER, self::API_PASSWORD], - 'timeout' => 1, - 'connect_timeout' => 1, - ] - ); - - self::$lastHttpResponseCode = (int) $response->getStatusCode(); - - if (self::$lastHttpResponseCode !== 200 && self::$lastHttpResponseCode !== 201) { - return self::ERROR_INVALID_RESPONSE_CODE; - } - - if (!$response->getBody()) { - return self::ERROR_INVALID_RESPONSE_DATA; - } - - $jsonData = json_decode($response->getBody(), TRUE); - if (!$jsonData['serial']['valid']) { - return self::ERROR_INVALID_LICENSE_KEY; - } - - return (int) $jsonData['serial']['validUntil']; - } catch (\Exception $exception) { - self::$lastException = $exception->getMessage(); - } - - return self::ERROR_LICENSE_CHECK_EXCEPTION; - } - - /** - * Checks whether we are in development context - * - * @return bool - */ - public static function isInDevelopmentContext() { - $versionNumber = VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()); - if ($versionNumber >= 9000000) { - // Since TYPO3 9LTS - $context = \TYPO3\CMS\Core\Core\Environment::getContext(); - } else { - // Prior to TYPO3 9LTS - $context = \TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext(); - } - return $context->isDevelopment(); - } - - /** - * Checks if the current TYPO3 version is supported for the license check - * - * @return bool - */ - public static function isTYPO3VersionSupported() { - $versionNumber = VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()); - return $versionNumber >= self::EARLIEST_SUPPORTED_VERSION; - } - - /** - * Checks if the time for the next check has expired. - * error = 0 means no error - * error = 1 is an error - * error = 2 is a warning - * - * @return bool - */ - public static function isTimeForNextCheck() { - return self::getLastAjaxNotificationCheckTimestamp() - + self::AMOUNT_OF_DAYS_UNTIL_NEXT_CHECK * 24 * 60 * 60 < $GLOBALS['EXEC_TIME']; - } - - /** - * Performs the license check and returns the output data to the frontend - * - * @param bool $isAjaxCheck - * @return array - */ - public static function getLicenseCheckResponseData($isAjaxCheck = FALSE) { - // if the key is empty - error - if (!self::getLicenseKey()) { - return [ - 'error' => 1, - 'title' => LocalizationUtility::translate('backend.licenceCheck.error.title', 'sg_vimeo'), - 'message' => LocalizationUtility::translate( - 'backend.licenceCheck.noLicenseKey', - 'sg_vimeo', - [ - LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') - ] - ) - ]; - } - - // if not valid - error - if (!self::hasValidLicense()) { - return [ - 'error' => 1, - 'title' => LocalizationUtility::translate('backend.licenceCheck.error.title', 'sg_vimeo'), - 'message' => LocalizationUtility::translate( - 'backend.licenceCheck.expiredError.message', - 'sg_vimeo', - [ - LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') - ] - ) - ]; - } - - // if it's valid - check validUntil and throw a warning if the license has expired but you are still - // on the valid version - if (self::getValidUntil() < $GLOBALS['EXEC_TIME']) { - $date = date('d.m.Y', self::getValidUntil()); - - if ($isAjaxCheck) { - $lastWarningTimestamp = (int) self::getLastWarningTimestamp( - ); // relevant only for the AJAX notifications - } - - if (!$isAjaxCheck || ($lastWarningTimestamp + self::AMOUNT_OF_DAYS_UNTIL_WARNING * 24 * 60 * 60 < $GLOBALS['EXEC_TIME'])) { - if ($isAjaxCheck) { - self::setLastWarningTimestamp($GLOBALS['EXEC_TIME']); - } - - return [ - 'error' => 2, - 'title' => LocalizationUtility::translate('backend.licenceCheck.warning.title', 'sg_vimeo'), - 'message' => LocalizationUtility::translate( - 'backend.licenceCheck.expiringWarning.message', - 'sg_vimeo', - [ - $date, LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') - ] - ) - ]; - } - } - - /** @noinspection SuspiciousAssignmentsInspection */ - $date = date('d.m.Y', self::getValidUntil()); - // 19.01.2038 == lifetime license - if ($date === '19.01.2038') { - $date = LocalizationUtility::translate( - 'backend.licenceCheck.status.lifetime', - 'sg_vimeo' - ); - } - - return [ - 'error' => 0, - 'title' => LocalizationUtility::translate('backend.licenceCheck.status.title', 'sg_vimeo'), - 'message' => LocalizationUtility::translate( - 'backend.licenceCheck.status.okMessage', - 'sg_vimeo', - [ - $date - ] - ) - ]; - } - - /** - * Returns one of the state constants of this class. - * - * @return int - */ - public static function checkKey() { - $key = ExtensionSettingsService::getSetting(ExtensionSettingsService::SETTING_LICENSE); - if (empty($key)) { - return self::STATE_LICENSE_NOT_SET; - } - - if ((bool) preg_match('/^([A-Z\d]{6}-?){4}$/', $key)) { - return self::STATE_LICENSE_VALID; - } - - return self::STATE_LICENSE_INVALID; - } - - /** - * Checks if this instance is in the demo mode. - * - * @return bool - */ - public static function isInDemoMode() { - $demoData = self::getDemoModeData(); - if (!$demoData) { - return FALSE; - } - - if (self::getRemainingTimeInDemoMode() <= 0) { - return FALSE; - } - - if ((int) $demoData['amount'] > self::DEMO_MODE_MAX_AMOUNT) { - return FALSE; - } - - return TRUE; - } - - /** - * Returns the remaining time in seconds for the demo mode. - * - * @return int - */ - public static function getRemainingTimeInDemoMode() { - $demoData = self::getDemoModeData(); - if (!$demoData) { - return 0; - } - - return $demoData['lastActivation'] + self::DEMO_MODE_LIFETIME - $GLOBALS['EXEC_TIME']; - } - - /** - * Activates the demo mode for this instance. - * - * @return void - */ - public static function activateDemoMode() { - $amount = 1; - $demoData = self::getDemoModeData(); - if ($demoData && isset($demoData['amount'])) { - $amount += (int) $demoData['amount']; - } - - $registry = GeneralUtility::makeInstance(Registry::class); - $registry->set(self::REGISTRY_NAMESPACE, self::DEMO_MODE_KEY, [ - 'lastActivation' => $GLOBALS['EXEC_TIME'], - 'amount' => $amount, - ]); - } - - /** - * Returns true, if this instance can use the demo mode. - * - * @return bool - */ - public static function isDemoModeAcceptable() { - $demoData = self::getDemoModeData(); - if (!$demoData) { - return TRUE; - } - - return (int) $demoData['amount'] < self::DEMO_MODE_MAX_AMOUNT; - } - - /** - * Returns the demo mode data, or an empty FALSE on error. - * - * @return array|FALSE - */ - protected static function getDemoModeData() { - $registry = GeneralUtility::makeInstance(Registry::class); - $demoData = $registry->get(self::REGISTRY_NAMESPACE, self::DEMO_MODE_KEY); - if (!isset($demoData['lastActivation'], $demoData['amount'])) { - return FALSE; - } - - return $demoData; - } +class LicenceCheckService +{ + const STATE_LICENSE_VALID = 2; + const STATE_LICENSE_INVALID = 1; + const STATE_LICENSE_NOT_SET = 0; + + const DEMO_MODE_KEY = 'demo_mode'; + const DEMO_MODE_LIFETIME = 86400; + const DEMO_MODE_MAX_AMOUNT = 3; + + /** + * The product key from ShopWare + */ + const PRODUCT_KEY = 'sg_vimeo'; + + /** + * Namespace for the sys registry + */ + const REGISTRY_NAMESPACE = 'tx_sgvimeo'; + + /** + * Keys for the sys registry + */ + const IS_KEY_VALID_KEY = 'isKeyValid'; + const LAST_WARNING_TIMESTAMP_KEY = 'lastWarningTimestamp'; + const HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY = 'hasValidLicenseUntilTimestamp'; + const LICENSE_CHECKED_IN_VERSION_KEY = 'licenceCheckedInVersion'; + const LAST_CHECKED_TIMESTAMP_KEY = 'lastCheckedTimestamp'; + const LAST_AJAX_TIMESTAMP_KEY = 'lastAjaxTimestamp'; + const LAST_LICENSE_KEY_CHECKED_KEY = 'lastLicenseKeyChecked'; + + /** + * Error codes + */ + const ERROR_INVALID_RESPONSE_CODE = -1; + const ERROR_INVALID_RESPONSE_DATA = -2; + const ERROR_INVALID_LICENSE_KEY = -3; + const ERROR_INVALID_LICENSE_STRUCTURE = -4; + const ERROR_TIMESTAMP_INVALID = -5; + const ERROR_LICENSE_CHECK_EXCEPTION = -6; + + /** + * Earliest TYPO3 Version that we support + */ + const EARLIEST_SUPPORTED_VERSION = 8000000; + + /** + * Last response code from server + * + * @var int + */ + protected static $lastHttpResponseCode = 0; + + /** + * The last exception from the server + * + * @var string + */ + protected static $lastException = ''; + + /** + * The validUntil timestamp + * + * @var null|int + */ + protected static $validUntil; + + /** + * Check the license key once per how many days + */ + const AMOUNT_OF_DAYS_UNTIL_NEXT_CHECK = 1; + + /** + * Show a warning if the license has expired but we are still in the same version once per how many days + */ + const AMOUNT_OF_DAYS_UNTIL_WARNING = 30; + + /** + * License server credentials + */ + const API_USER = 'license_check'; + const API_PASSWORD = 'lGKLiHc5We6gBqsggVlwdLNoWv9CEKnWiy7cgMUO'; + const API_URL = 'https://shop.sgalinski.de/api/license'; + + /** + * @var array + */ + private static $versionToReleaseTimestamp = [ + '2.5.0' => 1688468637, // Tue, 04 Jul 2023 14:11:50 GMT + ]; + + /** + * The current extension version + */ + const CURRENT_VERSION = '2.5.0'; + + /** + * @param mixed $validUntil A timestamp, which says the lifetime of this key. + * @return boolean True, if the timestamp is invalid. + */ + public static function isTimestampInvalid($validUntil) + { + if ($validUntil < 0) { + return TRUE; + } + $releaseTimestampOfCurrentVersion = self::$versionToReleaseTimestamp[self::CURRENT_VERSION]; + if ($releaseTimestampOfCurrentVersion === NULL || $validUntil < $releaseTimestampOfCurrentVersion) { + return TRUE; + } + + self::$validUntil = $validUntil; + return FALSE; + } + + /** + * Should we perform the license check for this key and in this version at this point of time + * + * @param string $licenseKey + * @return bool + */ + public static function shouldCheckKey($licenseKey) + { + if ($licenseKey !== self::getLastKey()) { + return TRUE; + } + + if (self::getLicenseCheckedInVersion() !== self::CURRENT_VERSION) { + return TRUE; + } + + // the license was valid last time we checked, but it has expired and we haven't done another check since it expired + // let's make sure we don't have the wrong state in this case + $licenseExpirationDate = self::getValidLicenseUntilTimestamp(); + /** @noinspection NotOptimalIfConditionsInspection */ + return self::getValidLicense() && $licenseExpirationDate < $GLOBALS['EXEC_TIME'] + && $licenseExpirationDate >= self::getLastLicenseCheckTimestamp(); + } + + /** + * Returns the license key that has been set + * + * @return string + */ + public static function getLicenseKey() + { + return (string)ExtensionSettingsService::getSetting(ExtensionSettingsService::SETTING_LICENSE); + } + + /** + * Checks whether the system has a valid license + * + * @return bool + */ + public static function hasValidLicense() + { + $licenseKey = self::getLicenseKey(); + if (!self::shouldCheckKey($licenseKey)) { + return self::getValidLicense(); + } + self::clearRegistryValues(); + + if (!self::isLicenseServerReachable()) { + return TRUE; + } + + if (!self::isLicenseValid($licenseKey)) { + self::setLastKey($licenseKey); + self::setValidLicense(FALSE); + self::setLicenseCheckedInVersion(self::CURRENT_VERSION); + self::setValidLicenseUntilTimestamp(0); + self::setLastLicenseCheckTimestamp(); + } else { + self::setValidLicenseUntilTimestamp(self::getValidUntil()); + self::setValidLicense(TRUE); + self::setLastKey($licenseKey); + self::setLicenseCheckedInVersion(self::CURRENT_VERSION); + self::setLastLicenseCheckTimestamp(); + return TRUE; + } + + return FALSE; + } + + /** + * Sets the last key checked for from the registry + * + * @param string $licenseKey + */ + protected static function setLastKey($licenseKey) + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY, $licenseKey); + } + + /** + * Gets the last key checked for from the registry + * + * @return mixed|null + */ + public static function getLastKey() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY); + } + + /** + * Sets if the license is valid in the registry + * + * @param bool $isValid + */ + protected static function setValidLicense($isValid) + { + $isValid = (bool)$isValid; + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY, $isValid); + } + + /** + * Gets the isValid from the registry + * + * @return bool + */ + protected static function getValidLicense() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return (bool)$registry->get(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY); + } + + /** + * Stores the last warning timestamp + * + * @param int $timestamp + */ + protected static function setLastWarningTimestamp($timestamp) + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY, $timestamp); + } + + /** + * Gets the last warning timestamp + * + * @return mixed|null + */ + protected static function getLastWarningTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY); + } + + /** + * Stores the valid until timestamp in the registry + * + * @param mixed $validUntil + */ + protected static function setValidLicenseUntilTimestamp($validUntil) + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY, $validUntil); + } + + /** + * Gets the valid until timestamp from the registry + * + * @return mixed|null + */ + protected static function getValidLicenseUntilTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY); + } + + /** + * Sets the version that the license was last valid for + * + * @param string $version + */ + protected static function setLicenseCheckedInVersion($version) + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY, $version); + } + + /** + * Gets the version that the license was last valid for + * + * @return mixed|null + */ + protected static function getLicenseCheckedInVersion() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY); + } + + /** + * Sets the timestamp of the last check in the registry + */ + protected static function setLastLicenseCheckTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY, $GLOBALS['EXEC_TIME']); + } + + /** + * Gets the timestamp of the last check from the registry + * + * @return mixed|null + */ + public static function getLastLicenseCheckTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY); + } + + /** + * Sets the timestamp of the last AJAX Notification check in the registry + */ + public static function setLastAjaxNotificationCheckTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY, $GLOBALS['EXEC_TIME']); + } + + /** + * Gets the timestamp of the last AJAX Notification check from the registry + * + * @return mixed|null + */ + protected static function getLastAjaxNotificationCheckTimestamp() + { + $registry = GeneralUtility::makeInstance(Registry::class); + return $registry->get(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY); + } + + /** + * Clears the registry values + */ + protected static function clearRegistryValues() + { + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_CHECKED_TIMESTAMP_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::HAS_VALID_LICENSE_UNTIL_TIMESTAMP_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::LICENSE_CHECKED_IN_VERSION_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::IS_KEY_VALID_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_LICENSE_KEY_CHECKED_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_WARNING_TIMESTAMP_KEY); + $registry->remove(self::REGISTRY_NAMESPACE, self::LAST_AJAX_TIMESTAMP_KEY); + } + + /** + * Gets the validUntil date from this current check + * + * @return mixed + */ + public static function getValidUntil() + { + if (self::$validUntil === NULL) { + self::$validUntil = self::getValidLicenseUntilTimestamp(); + } + return self::$validUntil; + } + + /** + * The timestamp of the key lifetime, if the given license key is valid, or -1 if invalid. + * + * @param string $licenseKey A license key, which should be validated. + * @return bool + */ + public static function isLicenseValid($licenseKey) + { + if (!self::checkLicenseKeyStructure($licenseKey)) { + return FALSE; + } + + $validUntil = self::getValidUntilTimestampByLicenseKey($licenseKey); + return !self::isTimestampInvalid($validUntil); + } + + /** + * Check if the given license key is valid. + * + * @param string $licenseKey A license key, which should be validated. + * @return boolean + */ + public static function checkLicenseKeyStructure($licenseKey) + { + // Structure: XXXXXX-XXXXXX-XXXXXX-XXXXXX | All upper case + if (substr_count($licenseKey, '-') !== 3) { + return FALSE; + } + + $caseControl = strtoupper($licenseKey); + return $licenseKey === $caseControl && strlen($licenseKey) === 27; + } + + /** + * True, if the license server is reachable. + * + * @return boolean + */ + public static function isLicenseServerReachable() + { + try { + $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); + $response = $requestFactory->request( + self::API_URL, + 'GET', + [ + 'auth' => [self::API_USER, self::API_PASSWORD], + 'timeout' => 1, + 'connect_timeout' => 1, + ] + ); + + self::$lastHttpResponseCode = (int)$response->getStatusCode(); + + if (self::$lastHttpResponseCode !== 200 && self::$lastHttpResponseCode !== 201) { + return FALSE; + } + } catch (\Exception $exception) { + return FALSE; + } + + return TRUE; + } + + /** + * Returns The timestamp of the key lifetime, if the given license key is valid, on the server, or -1 if invalid. + * + * @param string $licenseKey + * @return int + */ + private static function getValidUntilTimestampByLicenseKey($licenseKey) + { + try { + $url = self::API_URL . '/' . urldecode($licenseKey) . '?product=' + . self::PRODUCT_KEY; + $requestFactory = GeneralUtility::makeInstance(RequestFactory::class); + $response = $requestFactory->request( + $url, + 'GET', + [ + 'auth' => [self::API_USER, self::API_PASSWORD], + 'timeout' => 1, + 'connect_timeout' => 1, + ] + ); + + self::$lastHttpResponseCode = (int)$response->getStatusCode(); + + if (self::$lastHttpResponseCode !== 200 && self::$lastHttpResponseCode !== 201) { + return self::ERROR_INVALID_RESPONSE_CODE; + } + + if (!$response->getBody()) { + return self::ERROR_INVALID_RESPONSE_DATA; + } + + $jsonData = json_decode($response->getBody(), TRUE); + if (!$jsonData['serial']['valid']) { + return self::ERROR_INVALID_LICENSE_KEY; + } + + return (int)$jsonData['serial']['validUntil']; + } catch (\Exception $exception) { + self::$lastException = $exception->getMessage(); + } + + return self::ERROR_LICENSE_CHECK_EXCEPTION; + } + + /** + * Checks whether we are in development context + * + * @return bool + */ + public static function isInDevelopmentContext() + { + $versionNumber = VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()); + if ($versionNumber >= 9000000) { + // Since TYPO3 9LTS + $context = \TYPO3\CMS\Core\Core\Environment::getContext(); + } else { + // Prior to TYPO3 9LTS + $context = \TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext(); + } + return $context->isDevelopment(); + } + + /** + * Checks if the current TYPO3 version is supported for the license check + * + * @return bool + */ + public static function isTYPO3VersionSupported() + { + $versionNumber = VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version()); + return $versionNumber >= self::EARLIEST_SUPPORTED_VERSION; + } + + /** + * Checks if the time for the next check has expired. + * error = 0 means no error + * error = 1 is an error + * error = 2 is a warning + * + * @return bool + */ + public static function isTimeForNextCheck() + { + return self::getLastAjaxNotificationCheckTimestamp() + + self::AMOUNT_OF_DAYS_UNTIL_NEXT_CHECK * 24 * 60 * 60 < $GLOBALS['EXEC_TIME']; + } + + /** + * Performs the license check and returns the output data to the frontend + * + * @param bool $isAjaxCheck + * @return array + */ + public static function getLicenseCheckResponseData($isAjaxCheck = FALSE) + { + // if the key is empty - error + if (!self::getLicenseKey()) { + return [ + 'error' => 1, + 'title' => LocalizationUtility::translate('backend.licenceCheck.error.title', 'sg_vimeo'), + 'message' => LocalizationUtility::translate( + 'backend.licenceCheck.noLicenseKey', + 'sg_vimeo', + [ + LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') + ] + ) + ]; + } + + // if not valid - error + if (!self::hasValidLicense()) { + return [ + 'error' => 1, + 'title' => LocalizationUtility::translate('backend.licenceCheck.error.title', 'sg_vimeo'), + 'message' => LocalizationUtility::translate( + 'backend.licenceCheck.expiredError.message', + 'sg_vimeo', + [ + LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') + ] + ) + ]; + } + + // if it's valid - check validUntil and throw a warning if the license has expired but you are still + // on the valid version + if (self::getValidUntil() < $GLOBALS['EXEC_TIME']) { + $date = date('d.m.Y', self::getValidUntil()); + + if ($isAjaxCheck) { + $lastWarningTimestamp = (int)self::getLastWarningTimestamp(); // relevant only for the AJAX notifications + } + + if (!$isAjaxCheck || ($lastWarningTimestamp + self::AMOUNT_OF_DAYS_UNTIL_WARNING * 24 * 60 * 60 < $GLOBALS['EXEC_TIME'])) { + if ($isAjaxCheck) { + self::setLastWarningTimestamp($GLOBALS['EXEC_TIME']); + } + + return [ + 'error' => 2, + 'title' => LocalizationUtility::translate('backend.licenceCheck.warning.title', 'sg_vimeo'), + 'message' => LocalizationUtility::translate( + 'backend.licenceCheck.expiringWarning.message', + 'sg_vimeo', + [ + $date, LocalizationUtility::translate('backend.licenceCheck.shopLink', 'sg_vimeo') + ] + ) + ]; + } + } + + /** @noinspection SuspiciousAssignmentsInspection */ + $date = date('d.m.Y', self::getValidUntil()); + // 19.01.2038 == lifetime license + if ($date === '19.01.2038') { + $date = LocalizationUtility::translate( + 'backend.licenceCheck.status.lifetime', + 'sg_vimeo' + ); + } + + return [ + 'error' => 0, + 'title' => LocalizationUtility::translate('backend.licenceCheck.status.title', 'sg_vimeo'), + 'message' => LocalizationUtility::translate( + 'backend.licenceCheck.status.okMessage', + 'sg_vimeo', + [ + $date + ] + ) + ]; + } + + /** + * Returns one of the state constants of this class. + * + * @return int + */ + public static function checkKey() + { + $key = ExtensionSettingsService::getSetting(ExtensionSettingsService::SETTING_LICENSE); + if (empty($key)) { + return self::STATE_LICENSE_NOT_SET; + } + + if ((bool)preg_match('/^([A-Z\d]{6}-?){4}$/', $key)) { + return self::STATE_LICENSE_VALID; + } + + return self::STATE_LICENSE_INVALID; + } + + /** + * Checks if this instance is in the demo mode. + * + * @return bool + */ + public static function isInDemoMode() + { + $demoData = self::getDemoModeData(); + if (!$demoData) { + return FALSE; + } + + if (self::getRemainingTimeInDemoMode() <= 0) { + return FALSE; + } + + if ((int)$demoData['amount'] > self::DEMO_MODE_MAX_AMOUNT) { + return FALSE; + } + + return TRUE; + } + + /** + * Returns the remaining time in seconds for the demo mode. + * + * @return int + */ + public static function getRemainingTimeInDemoMode() + { + $demoData = self::getDemoModeData(); + if (!$demoData) { + return 0; + } + + return $demoData['lastActivation'] + self::DEMO_MODE_LIFETIME - $GLOBALS['EXEC_TIME']; + } + + /** + * Activates the demo mode for this instance. + * + * @return void + */ + public static function activateDemoMode() + { + $amount = 1; + $demoData = self::getDemoModeData(); + if ($demoData && isset($demoData['amount'])) { + $amount += (int)$demoData['amount']; + } + + $registry = GeneralUtility::makeInstance(Registry::class); + $registry->set(self::REGISTRY_NAMESPACE, self::DEMO_MODE_KEY, [ + 'lastActivation' => $GLOBALS['EXEC_TIME'], + 'amount' => $amount, + ]); + } + + /** + * Returns true, if this instance can use the demo mode. + * + * @return bool + */ + public static function isDemoModeAcceptable() + { + $demoData = self::getDemoModeData(); + if (!$demoData) { + return TRUE; + } + + return (int)$demoData['amount'] < self::DEMO_MODE_MAX_AMOUNT; + } + + /** + * Returns the demo mode data, or an empty FALSE on error. + * + * @return array|FALSE + */ + protected static function getDemoModeData() + { + $registry = GeneralUtility::makeInstance(Registry::class); + $demoData = $registry->get(self::REGISTRY_NAMESPACE, self::DEMO_MODE_KEY); + if (!isset($demoData['lastActivation'], $demoData['amount'])) { + return FALSE; + } + + return $demoData; + } } diff --git a/Classes/Service/VimeoService.php b/Classes/Service/VimeoService.php index 4681f67..d667c1c 100644 --- a/Classes/Service/VimeoService.php +++ b/Classes/Service/VimeoService.php @@ -37,259 +37,269 @@ use Vimeo\Vimeo; /** * Vimeo Helper Service */ -class VimeoService implements LoggerAwareInterface { - use LoggerAwareTrait; - - protected const API_CHANNEL = '/channels/'; - protected const API_VIDEO = '/videos/'; - protected const API_SHOWCASE = '/me/albums/'; - - /** - * https://developer.vimeo.com/api/authentication#supported-scopes - */ - protected const SCOPE = 'public'; - - public const CACHE_LIFETIME_IN_SECONDS = 86400; - - /** - * @var FrontendInterface - */ - protected $cache; - - /** - * @var Vimeo - */ - protected $vimeoApiClient; - - /** - * @var array - */ - protected $paginatedResponseData; - - /** - * @var int - * Used for the `per_page` param, for the vimeo API requests. - * Use `per_page` to set the number of representations per page from the default value of 25 up to a maximum value of 100. - */ - protected $maxResultsPerPage = 25; - - /** - * @var int - * The amount of videos fetched within the current pagination request - */ - protected $amountOfVideosFetched = 0; - - /** - * VimeoService constructor. - * - * @param string $clientId - * @param string $clientSecret - * @param string $personalAccessToken - */ - public function __construct( - string $clientId, - string $clientSecret, - string $personalAccessToken, - FrontendInterface $cache - ) { - $this->vimeoApiClient = new Vimeo($clientId, $clientSecret, $personalAccessToken); - // We only need to request an unauthenticated token, if there is no personal access token provided already. - // An authenticated access token with the public scope is identical to an unauthenticated access token, - // except that you can use the /me endpoint to refer to the currently logged-in user. - // Accessing /me with an unauthenticated access token generates an error. - // See also: https://developer.vimeo.com/api/authentication - if ($personalAccessToken === '') { - $this->requestAccessToken(); - } - $this->cache = $cache; - } - - /** - * @param string $vimeoId can be a video id, showcase id or a channel name - * @param int $maxResults - * @return array|null - */ - public function getVimeoData(string $vimeoId, int $maxResults): ?array { - $response = []; - $this->maxResultsPerPage = $maxResults; - $cacheKey = 'sg_vimeo' . sha1($vimeoId . $maxResults); - $disableVimeoCache = (bool) GeneralUtility::_GP('disableVimeoCache'); - if (!$disableVimeoCache) { - $cachedResult = $this->cache->get($cacheKey); - if ($cachedResult) { - return $cachedResult; - } - } - - if (strpos($vimeoId, 'showcase') === 0) { - $showcaseId = explode('/', $vimeoId)[1]; - $response['items'] = $this->addVideoIdsToResponse($this->getShowcaseVideos((int) $showcaseId)); - $response['kind'] = 'showcase'; - } elseif (strpos($vimeoId, 'channel') === 0) { - $channelId = explode('/', $vimeoId)[1]; - $response['items'] = $this->addVideoIdsToResponse($this->getChannelVideos($channelId)); - $response['kind'] = 'channel'; - } else { - $response['items'] = $this->addVideoIdsToResponse($this->getVideo((int) $vimeoId)); - $response['kind'] = 'video'; - } - - if (!$disableVimeoCache) { - $this->cache->set($cacheKey, $response, [], self::CACHE_LIFETIME_IN_SECONDS); - } - - return $response; - } - - /** - * Extracts the video id from the video's canonical relative URI and adds it to each entry with the key 'videoId' - * - * @param array|null $response - * @return array|null - */ - protected function addVideoIdsToResponse(?array $response): ?array { - if (!is_array($response)) { - return NULL; - } - - foreach ($response as $index => $item) { - if (array_key_exists('uri', $item)) { - $uri = $item['uri']; - $videoId = (int) str_replace('/videos/', '', $uri); - $response[$index]['videoId'] = $videoId; - } - } - - return $response; - } - - /** - * Unauthenticated API requests must generate an access token. - * (Access tokens without a user. These tokens can view only public data.) - * You should not generate a new access token for each request. - * Instead, request an access token once and use it forever. - */ - protected function requestAccessToken(): void { - $token = $this->vimeoApiClient->clientCredentials(self::SCOPE); - if (isset($token['body']['access_token'])) { - $this->vimeoApiClient->setToken($token['body']['access_token']); - } - } - - /** - * Returns the response body, wrapped in an array if the response contains a single item. - * If the response is a paginated response, all items are fetched until the maxResultsPerPage is reached, - * or no $nextUrl is available anymore (last page reached). Since the flexform allows a max value of 100 currently, - * this function will never go past the first page, since the vimeo API allows a value of 100 as max value for the `per_page` parameter. - * - * @param array|null $response - * @return array|null - */ - protected function preprocessApiResponse(?array $response): ?array { - if (!is_array($response)) { - return NULL; - } - - // log error & return here, since the response was not OK - if ($response['status'] !== 200) { - $this->logger->error('sg_vimeo API Request failed, got the following response:', $response); - return [$response['body']]; - } - - // @TODO: we could check $response['headers‘]['X-RateLimit-Remaining'] here for remaining quota - if (array_key_exists('paging', $response['body'])) { - $amountOfVideosInResponse = is_countable($response['body']['data']) ? count($response['body']['data']) : 0; - $this->amountOfVideosFetched += $amountOfVideosInResponse; - $this->paginatedResponseData[] = $response['body']['data']; - $nextUrl = $response['body']['paging']['next']; - if ($this->amountOfVideosFetched === $this->maxResultsPerPage || $nextUrl === NULL) { - // return flattened array here, so that we don't end up with one sub array per pagination page - return array_merge(...$this->paginatedResponseData); - } - - $this->fetchPaginatedResult($nextUrl); - } - - // wrap response body in an array, so that we can treat all return values the same in the template - return [$response['body']]; - } - - /** - * @param string $nextUrl - * @return array|null - */ - protected function fetchPaginatedResult(string $nextUrl): ?array { - try { - $response = $this->vimeoApiClient->request($nextUrl); - } catch (VimeoRequestException $e) { - return NULL; - } - - return $this->preprocessApiResponse($response); - } - - /** - * Returns a single video for the given $videoId - * - * @see https://developer.vimeo.com/api/reference/videos#get_video - * @param int $videoId - * @return array|null - */ - public function getVideo(int $videoId): ?array { - // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting - $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; - try { - $response = $this->vimeoApiClient->request(self::API_VIDEO . $videoId . '?fields=' . $fieldsToSelect); - } catch (VimeoRequestException $e) { - $response = NULL; - throw $e; - } - - return $this->preprocessApiResponse($response); - } - - /** - * Returns all videos for the given $channelIdentifier - * - * @see https://developer.vimeo.com/api/reference/channels#get_channel_videos - * @param string $channelIdentifier - * @return array|null - */ - public function getChannelVideos(string $channelIdentifier): ?array { - // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting - $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; - try { - $response = $this->vimeoApiClient->request( - self::API_CHANNEL . $channelIdentifier . self::API_VIDEO . '?fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage - ); - } catch (VimeoRequestException $e) { - $response = NULL; - throw $e; - } - - return $this->preprocessApiResponse($response); - } - - /** - * Returns all videos for the given $showcaseId - * - * @see https://developer.vimeo.com/api/reference/showcases#get_showcase - * @param string $showcaseId - * @return array|null - */ - public function getShowcaseVideos(string $showcaseId): ?array { - // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting - $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; - // sort videos by the user-selected default: ?sort=default - try { - $response = $this->vimeoApiClient->request( - self::API_SHOWCASE . $showcaseId . self::API_VIDEO . '?sort=default&fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage - ); - } catch (VimeoRequestException $e) { - $response = NULL; - throw $e; - } - - return $this->preprocessApiResponse($response); - } +class VimeoService implements LoggerAwareInterface +{ + use LoggerAwareTrait; + + protected const API_CHANNEL = '/channels/'; + protected const API_VIDEO = '/videos/'; + protected const API_SHOWCASE = '/me/albums/'; + + /** + * https://developer.vimeo.com/api/authentication#supported-scopes + */ + protected const SCOPE = 'public'; + + public const CACHE_LIFETIME_IN_SECONDS = 86400; + + /** + * @var FrontendInterface + */ + protected $cache; + + /** + * @var Vimeo + */ + protected $vimeoApiClient; + + /** + * @var array + */ + protected $paginatedResponseData; + + /** + * @var int + * Used for the `per_page` param, for the vimeo API requests. + * Use `per_page` to set the number of representations per page from the default value of 25 up to a maximum value of 100. + */ + protected $maxResultsPerPage = 25; + + /** + * @var int + * The amount of videos fetched within the current pagination request + */ + protected $amountOfVideosFetched = 0; + + /** + * VimeoService constructor. + * + * @param string $clientId + * @param string $clientSecret + * @param string $personalAccessToken + */ + public function __construct( + string $clientId, + string $clientSecret, + string $personalAccessToken, + FrontendInterface $cache + ) + { + $this->vimeoApiClient = new Vimeo($clientId, $clientSecret, $personalAccessToken); + // We only need to request an unauthenticated token, if there is no personal access token provided already. + // An authenticated access token with the public scope is identical to an unauthenticated access token, + // except that you can use the /me endpoint to refer to the currently logged-in user. + // Accessing /me with an unauthenticated access token generates an error. + // See also: https://developer.vimeo.com/api/authentication + if ($personalAccessToken === '') { + $this->requestAccessToken(); + } + $this->cache = $cache; + } + + /** + * @param string $vimeoId can be a video id, showcase id or a channel name + * @param int $maxResults + * @return array|null + */ + public function getVimeoData(string $vimeoId, int $maxResults): ?array + { + $response = []; + $this->maxResultsPerPage = $maxResults; + $cacheKey = 'sg_vimeo' . sha1($vimeoId . $maxResults); + $disableVimeoCache = (bool)GeneralUtility::_GP('disableVimeoCache'); + if (!$disableVimeoCache) { + $cachedResult = $this->cache->get($cacheKey); + if ($cachedResult) { + return $cachedResult; + } + } + + if (strpos($vimeoId, 'showcase') === 0) { + $showcaseId = explode('/', $vimeoId)[1]; + $response['items'] = $this->addVideoIdsToResponse($this->getShowcaseVideos((int)$showcaseId)); + $response['kind'] = 'showcase'; + } elseif (strpos($vimeoId, 'channel') === 0) { + $channelId = explode('/', $vimeoId)[1]; + $response['items'] = $this->addVideoIdsToResponse($this->getChannelVideos($channelId)); + $response['kind'] = 'channel'; + } else { + $response['items'] = $this->addVideoIdsToResponse($this->getVideo((int)$vimeoId)); + $response['kind'] = 'video'; + } + + if (!$disableVimeoCache) { + $this->cache->set($cacheKey, $response, [], self::CACHE_LIFETIME_IN_SECONDS); + } + + return $response; + } + + /** + * Extracts the video id from the video's canonical relative URI and adds it to each entry with the key 'videoId' + * + * @param array|null $response + * @return array|null + */ + protected function addVideoIdsToResponse(?array $response): ?array + { + if (!is_array($response)) { + return NULL; + } + + foreach ($response as $index => $item) { + if (array_key_exists('uri', $item)) { + $uri = $item['uri']; + $videoId = (int)str_replace('/videos/', '', $uri); + $response[$index]['videoId'] = $videoId; + } + } + + return $response; + } + + /** + * Unauthenticated API requests must generate an access token. + * (Access tokens without a user. These tokens can view only public data.) + * You should not generate a new access token for each request. + * Instead, request an access token once and use it forever. + */ + protected function requestAccessToken(): void + { + $token = $this->vimeoApiClient->clientCredentials(self::SCOPE); + if (isset($token['body']['access_token'])) { + $this->vimeoApiClient->setToken($token['body']['access_token']); + } + } + + /** + * Returns the response body, wrapped in an array if the response contains a single item. + * If the response is a paginated response, all items are fetched until the maxResultsPerPage is reached, + * or no $nextUrl is available anymore (last page reached). Since the flexform allows a max value of 100 currently, + * this function will never go past the first page, since the vimeo API allows a value of 100 as max value for the `per_page` parameter. + * + * @param array|null $response + * @return array|null + */ + protected function preprocessApiResponse(?array $response): ?array + { + if (!is_array($response)) { + return NULL; + } + + // log error & return here, since the response was not OK + if ($response['status'] !== 200) { + $this->logger->error('sg_vimeo API Request failed, got the following response:', $response); + return [$response['body']]; + } + + // @TODO: we could check $response['headers‘]['X-RateLimit-Remaining'] here for remaining quota + if (array_key_exists('paging', $response['body'])) { + $amountOfVideosInResponse = is_countable($response['body']['data']) ? count($response['body']['data']) : 0; + $this->amountOfVideosFetched += $amountOfVideosInResponse; + $this->paginatedResponseData[] = $response['body']['data']; + $nextUrl = $response['body']['paging']['next']; + if ($this->amountOfVideosFetched === $this->maxResultsPerPage || $nextUrl === NULL) { + // return flattened array here, so that we don't end up with one sub array per pagination page + return array_merge(...$this->paginatedResponseData); + } + + $this->fetchPaginatedResult($nextUrl); + } + + // wrap response body in an array, so that we can treat all return values the same in the template + return [$response['body']]; + } + + /** + * @param string $nextUrl + * @return array|null + */ + protected function fetchPaginatedResult(string $nextUrl): ?array + { + try { + $response = $this->vimeoApiClient->request($nextUrl); + } catch (VimeoRequestException $e) { + return NULL; + } + + return $this->preprocessApiResponse($response); + } + + /** + * Returns a single video for the given $videoId + * + * @see https://developer.vimeo.com/api/reference/videos#get_video + * @param int $videoId + * @return array|null + */ + public function getVideo(int $videoId): ?array + { + // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting + $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; + try { + $response = $this->vimeoApiClient->request(self::API_VIDEO . $videoId . '?fields=' . $fieldsToSelect); + } catch (VimeoRequestException $e) { + $response = NULL; + throw $e; + } + + return $this->preprocessApiResponse($response); + } + + /** + * Returns all videos for the given $channelIdentifier + * + * @see https://developer.vimeo.com/api/reference/channels#get_channel_videos + * @param string $channelIdentifier + * @return array|null + */ + public function getChannelVideos(string $channelIdentifier): ?array + { + // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting + $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; + try { + $response = $this->vimeoApiClient->request( + self::API_CHANNEL . $channelIdentifier . self::API_VIDEO . '?fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage + ); + } catch (VimeoRequestException $e) { + $response = NULL; + throw $e; + } + + return $this->preprocessApiResponse($response); + } + + /** + * Returns all videos for the given $showcaseId + * + * @see https://developer.vimeo.com/api/reference/showcases#get_showcase + * @param string $showcaseId + * @return array|null + */ + public function getShowcaseVideos(string $showcaseId): ?array + { + // use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting + $fieldsToSelect = 'uri,name,description,link,embed,pictures,release_time,width,height'; + // sort videos by the user-selected default: ?sort=default + try { + $response = $this->vimeoApiClient->request( + self::API_SHOWCASE . $showcaseId . self::API_VIDEO . '?sort=default&fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage + ); + } catch (VimeoRequestException $e) { + $response = NULL; + throw $e; + } + + return $this->preprocessApiResponse($response); + } } diff --git a/Classes/ViewHelpers/RenderSvgViewHelper.php b/Classes/ViewHelpers/RenderSvgViewHelper.php index 07811ef..f5fbbc5 100644 --- a/Classes/ViewHelpers/RenderSvgViewHelper.php +++ b/Classes/ViewHelpers/RenderSvgViewHelper.php @@ -33,23 +33,25 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * * @package SGalinski\SgVimeo\ViewHelpers */ -class RenderSvgViewHelper extends AbstractTagBasedViewHelper { +class RenderSvgViewHelper extends AbstractTagBasedViewHelper +{ - /** - * Name of the tag to be created by this view helper - * - * @var string - * @api - */ - protected $tagName = 'svg'; + /** + * Name of the tag to be created by this view helper + * + * @var string + * @api + */ + protected $tagName = 'svg'; protected $directoryPath = __DIR__ . '/../../Resources/Public/Icons/'; - /** - * Register the ViewHelper arguments - */ - public function initializeArguments(): void { - parent::initializeArguments(); + /** + * Register the ViewHelper arguments + */ + public function initializeArguments(): void + { + parent::initializeArguments(); $this->registerArgument('name', 'string', 'The SVG name', TRUE); $this->registerArgument('color', 'string', 'The fill color', TRUE); $this->registerArgument('id', 'string', 'The HTML id attribute', FALSE); @@ -60,7 +62,7 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { $this->registerArgument('height', 'string', 'The HTML height attribute', FALSE); $this->registerArgument('style', 'string', 'Inline CSS styles', FALSE); $this->registerArgument('use', 'string', 'Inline CSS styles', FALSE); - } + } /** @@ -81,7 +83,7 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { $style = $this->arguments['style']; $title = $this->arguments['title']; - $src = $path ?? $this->directoryPath . '/' . $name .'.svg'; + $src = $path ?? $this->directoryPath . '/' . $name . '.svg'; // Get the content of the SVG file @@ -97,7 +99,7 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { // Set the attributes of the SVG element if ($width > 0) { - $this->addOrReplaceAttribute($svg, 'width', $width); + $this->addOrReplaceAttribute($svg, 'width', $width); } if ($height > 0) { @@ -117,7 +119,7 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { } if ($title) { - $this->addOrReplaceAttribute($svg,'title', $title); + $this->addOrReplaceAttribute($svg, 'title', $title); } // // Set the ID of the SVG element @@ -153,11 +155,11 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { //Todo: we don't really want this, do we? if (!empty($width) && !empty($height)) { - $resultWithoutCssClass = str_replace($cssClass, '', $result); - $cssClasses = $this->processWrapperClasses($cssClassPlain); + $resultWithoutCssClass = str_replace($cssClass, '', $result); + $cssClasses = $this->processWrapperClasses($cssClassPlain); - return "<span $cssClasses style='width: $width; height: $height;'>$resultWithoutCssClass</span>"; - } + return "<span $cssClasses style='width: $width; height: $height;'>$resultWithoutCssClass</span>"; + } if (!empty($width)) { $resultWithoutCssClass = str_replace($cssClass, '', $result); @@ -182,8 +184,9 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { * @param string $cssClass * @return string */ - function processWrapperClasses(string $cssClass): string { - return "class='$cssClass svg-wrapper d-inline-flex justify-content-center align-items-center'"; + function processWrapperClasses(string $cssClass): string + { + return "class='$cssClass svg-wrapper d-inline-flex justify-content-center align-items-center'"; } /** @@ -194,31 +197,32 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { * @param string $use * @return string */ - function processUseWrappers(string $svgContent, string $id, string $use): string { - $svgContent = "<g $id>$svgContent</g>"; + function processUseWrappers(string $svgContent, string $id, string $use): string + { + $svgContent = "<g $id>$svgContent</g>"; - if ($use) { - $svgContent = "<use href='#$use' />"; - } + if ($use) { + $svgContent = "<use href='#$use' />"; + } - return $svgContent; + return $svgContent; } /** - * Set the fill color of an SVG element. - * - * @param \SimpleXMLElement $element The SVG element - * @param string $fill The fill color to set - */ - protected function setFill(\SimpleXMLElement $element, string $fill): void - { - foreach ($element->children() as $child) { - $this->setFill($child, $fill); - } - if (isset($element->attributes()->fill)) { - $element->attributes()->fill = $fill; - } - } + * Set the fill color of an SVG element. + * + * @param \SimpleXMLElement $element The SVG element + * @param string $fill The fill color to set + */ + protected function setFill(\SimpleXMLElement $element, string $fill): void + { + foreach ($element->children() as $child) { + $this->setFill($child, $fill); + } + if (isset($element->attributes()->fill)) { + $element->attributes()->fill = $fill; + } + } /** * Gets the contents of the SVG file @@ -250,14 +254,14 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { */ private function xmlAdopt(\SimpleXMLElement $root, \SimpleXMLElement $newElement): void { - $node = $root->addChild($newElement->getName(), (string) $newElement); + $node = $root->addChild($newElement->getName(), (string)$newElement); - foreach($newElement->attributes() as $attr => $value) { + foreach ($newElement->attributes() as $attr => $value) { /** @noinspection NullPointerExceptionInspection */ $node->addAttribute($attr, $value); } - foreach($newElement->children() as $ch) { + foreach ($newElement->children() as $ch) { /** @noinspection NullPointerExceptionInspection */ $this->xmlAdopt($node, $ch); } @@ -272,8 +276,8 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { * @return void */ private function addOrReplaceAttribute(\SimpleXMLElement $element, - string $attributeName, - string $attributeValue): void + string $attributeName, + string $attributeValue): void { if (isset($element[$attributeName])) { $element[$attributeName] = $attributeValue; // Replace existing attribute value diff --git a/Classes/ViewHelpers/StructuredVideoDataViewHelper.php b/Classes/ViewHelpers/StructuredVideoDataViewHelper.php index 0f54b16..05cb911 100644 --- a/Classes/ViewHelpers/StructuredVideoDataViewHelper.php +++ b/Classes/ViewHelpers/StructuredVideoDataViewHelper.php @@ -33,63 +33,66 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; * * @package SGalinski\SgVimeo\ViewHelpers */ -class StructuredVideoDataViewHelper extends AbstractTagBasedViewHelper { - /** - * Name of the tag to be created by this view helper - * - * @var string - * @api - */ - protected $tagName = 'script'; +class StructuredVideoDataViewHelper extends AbstractTagBasedViewHelper +{ + /** + * Name of the tag to be created by this view helper + * + * @var string + * @api + */ + protected $tagName = 'script'; - /** - * Register the ViewHelper arguments - */ - public function initializeArguments(): void { - parent::initializeArguments(); - $this->registerArgument('videoArray', 'array', 'An array of videos', TRUE); - $this->registerArgument('arrayType', 'string', 'Either "youtube" or "vimeo"', FALSE, "youtube"); - } + /** + * Register the ViewHelper arguments + */ + public function initializeArguments(): void + { + parent::initializeArguments(); + $this->registerArgument('videoArray', 'array', 'An array of videos', TRUE); + $this->registerArgument('arrayType', 'string', 'Either "youtube" or "vimeo"', FALSE, "youtube"); + } - /** - * Takes the provided array of videos and creates an array for structured data JSON - * - * @return string - */ - public function render(): string { - $this->escapeOutput = FALSE; - $videoArray = $this->arguments['videoArray']; - $arrayType = $this->arguments['arrayType']; + /** + * Takes the provided array of videos and creates an array for structured data JSON + * + * @return string + */ + public function render(): string + { + $this->escapeOutput = FALSE; + $videoArray = $this->arguments['videoArray']; + $arrayType = $this->arguments['arrayType']; - $structuredData = []; - if ($arrayType === "youtube") { - foreach ($videoArray as $video) { - $structuredData[] = [ - '@type' => 'VideoObject', - 'name' => $video['title'], - 'description' => $video['description'], - 'thumbnailUrl' => $video['thumbnail'], - 'contentUrl' => $video['url'], - 'uploadDate' => $video['publishedAt'], - ]; - } - } else { - foreach ($videoArray as $video) { - $structuredData[] = [ - '@type' => 'VideoObject', - 'name' => $video['name'], - 'description' => $video['description'], - 'thumbnailUrl' => $video['pictures']['sizes'][count($video['pictures']['sizes']) - 1]['link'], - 'contentUrl' => $video['link'], - 'uploadDate' => $video['release_time'], - ]; - } - } + $structuredData = []; + if ($arrayType === "youtube") { + foreach ($videoArray as $video) { + $structuredData[] = [ + '@type' => 'VideoObject', + 'name' => $video['title'], + 'description' => $video['description'], + 'thumbnailUrl' => $video['thumbnail'], + 'contentUrl' => $video['url'], + 'uploadDate' => $video['publishedAt'], + ]; + } + } else { + foreach ($videoArray as $video) { + $structuredData[] = [ + '@type' => 'VideoObject', + 'name' => $video['name'], + 'description' => $video['description'], + 'thumbnailUrl' => $video['pictures']['sizes'][count($video['pictures']['sizes']) - 1]['link'], + 'contentUrl' => $video['link'], + 'uploadDate' => $video['release_time'], + ]; + } + } - $this->tag->addAttribute('type', 'application/ld+json'); - $this->tag->setContent( - '{"@context": "http://schema.org", "@type": "WebPage", "video": ' . json_encode($structuredData) . '}' - ); - return $this->tag->render(); - } + $this->tag->addAttribute('type', 'application/ld+json'); + $this->tag->setContent( + '{"@context": "http://schema.org", "@type": "WebPage", "video": ' . json_encode($structuredData) . '}' + ); + return $this->tag->render(); + } } diff --git a/Classes/ViewHelpers/UrlWithQueryParametersViewHelper.php b/Classes/ViewHelpers/UrlWithQueryParametersViewHelper.php index c09daf6..d381d61 100644 --- a/Classes/ViewHelpers/UrlWithQueryParametersViewHelper.php +++ b/Classes/ViewHelpers/UrlWithQueryParametersViewHelper.php @@ -1,7 +1,5 @@ <?php -namespace SGalinski\SgVimeo\ViewHelpers; - /*************************************************************** * Copyright notice * @@ -26,6 +24,8 @@ namespace SGalinski\SgVimeo\ViewHelpers; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +namespace SGalinski\SgVimeo\ViewHelpers; + use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** @@ -36,38 +36,41 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; * <vi:urlWithQueryParameters url="https://player.vimeo.com/video/123?h=xyz" parameters="?origin=https://demo.sgalinski.de" /> * Result: https://player.vimeo.com/video/123?h=xyz&origin=https://demo.sgalinski.de */ -class UrlWithQueryParametersViewHelper extends AbstractViewHelper { - /** - * Register the ViewHelper arguments - */ - public function initializeArguments(): void { - parent::initializeArguments(); - $this->registerArgument('url', 'string', 'The url to add the query parameters to', TRUE); - $this->registerArgument('parameters', 'string', 'The query parameters to add', FALSE, ''); - } +class UrlWithQueryParametersViewHelper extends AbstractViewHelper +{ + /** + * Register the ViewHelper arguments + */ + public function initializeArguments(): void + { + parent::initializeArguments(); + $this->registerArgument('url', 'string', 'The url to add the query parameters to', TRUE); + $this->registerArgument('parameters', 'string', 'The query parameters to add', FALSE, ''); + } - /** - * Returns the url with the added query parameters - * - * @return string - */ - public function render(): string { - $url = $this->arguments['url']; - $additionalUrlParameters = $this->arguments['parameters']; + /** + * Returns the url with the added query parameters + * + * @return string + */ + public function render(): string + { + $url = $this->arguments['url']; + $additionalUrlParameters = $this->arguments['parameters']; - if ($additionalUrlParameters === '') { - return $url; - } + if ($additionalUrlParameters === '') { + return $url; + } - $beginsWithQuestionMark = $additionalUrlParameters[0] === '?'; - $beginsWithAmpersand = $additionalUrlParameters[0] === '&'; + $beginsWithQuestionMark = $additionalUrlParameters[0] === '?'; + $beginsWithAmpersand = $additionalUrlParameters[0] === '&'; - if ($beginsWithQuestionMark || $beginsWithAmpersand) { - $additionalUrlParameters = substr($additionalUrlParameters, 1); - } + if ($beginsWithQuestionMark || $beginsWithAmpersand) { + $additionalUrlParameters = substr($additionalUrlParameters, 1); + } - return strpos($url, '?') !== FALSE - ? $url . '&' . $additionalUrlParameters - : $url . '?' . $additionalUrlParameters; - } + return strpos($url, '?') !== FALSE + ? $url . '&' . $additionalUrlParameters + : $url . '?' . $additionalUrlParameters; + } } -- GitLab