diff --git a/.Build/phpstan.cms11.neon b/.Build/phpstan.cms11.neon index 31836d7c..96bdbd38 100644 --- a/.Build/phpstan.cms11.neon +++ b/.Build/phpstan.cms11.neon @@ -1,11 +1,21 @@ parameters: - level: 0 + level: 8 paths: - ../Classes - ../Configuration + excludePaths: + - ../Classes/Updates ignoreErrors: - '#Parameter \$event of method#' - '#TYPO3\\CMS\\Frontend\\Page\\PageInformation#' - '#TYPO3\\CMS\\Backend\\View\\BackendViewFactory#' - - '#TYPO3\\CMS\\Install\\Attribute\\UpgradeWizard#' + - '#TYPO3\\CMS\\Backend\\Template\\ModuleTemplate#' + - '#TYPO3\\CMS\\Extbase\\Mvc\\RequestInterface#' + - '#TYPO3\\CMS\\Core\\View\\ViewInterface#' + - '#TYPO3\\CMS\\Core\\Domain\\Repository\\PageRepository::getLanguageOverlay#' - '#frontend.page.information#' + - '#ModifyPageLayoutContentEvent#' + - '#AfterCacheableContentIsGeneratedEvent#' + - '#loadJavaScriptModule#' + - '#getLanguageCode#' + - '#addJsInlineCode#' diff --git a/.Build/phpstan.cms12.neon b/.Build/phpstan.cms12.neon index 6d3cb5ed..9e7b1d7b 100644 --- a/.Build/phpstan.cms12.neon +++ b/.Build/phpstan.cms12.neon @@ -1,9 +1,12 @@ parameters: - level: 0 + level: 8 paths: - ../Classes - ../Configuration + excludePaths: + - ../Classes/Updates ignoreErrors: - '#TYPO3\\CMS\\Backend\\ViewHelpers\\ModuleLayoutViewHelper#' - '#TYPO3\\CMS\\Frontend\\Page\\PageInformation#' - '#frontend.page.information#' + - '#protected method getRecordOverlay#' \ No newline at end of file diff --git a/.Build/phpstan.cms13.neon b/.Build/phpstan.cms13.neon index 4a89c28a..7b1e54ac 100644 --- a/.Build/phpstan.cms13.neon +++ b/.Build/phpstan.cms13.neon @@ -1,10 +1,15 @@ parameters: - level: 0 + level: 8 paths: - ../Classes - ../Configuration + excludePaths: + - ../Classes/Updates ignoreErrors: - '#TYPO3\\CMS\\Backend\\ViewHelpers\\ModuleLayoutViewHelper#' + - '#TYPO3\\CMS\\Backend\\Template\\ModuleTemplate#' + - '#getRecordOverlay#' + - '#loadRequireJsModule#' typo3: requestGetAttributeMapping: frontend.page.information: TYPO3\CMS\Frontend\Page\PageInformation \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index bb0a62f4..a3e92698 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,20 +1,64 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file root = true -[{*.rst,*.rst.txt}] +# 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 -indent_style = space -indent_size = 3 + +# TS/JS-Files +[*.{ts,js,mjs}] +indent_size = 2 + +# JSON-Files +[*.json] +indent_style = tab + +# ReST-Files +[*.{rst,rst.txt}] +indent_size = 4 max_line_length = 80 -# MD-Files +# Markdown-Files [*.md] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 4 max_line_length = 80 + +# YAML-Files +[*.{yaml,yml}] +indent_size = 2 + +# NEON-Files +[*.neon] +indent_size = 2 +indent_style = tab + +# stylelint +[.stylelintrc] +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 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7747dbe1..100212f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ We will follow [Semantic Versioning](http://semver.org/). - Dropped support for PHP 7 - Removed `DbalService` which was used to support older `doctrine/dbal` versions for TYPO3 10 - Removed `ArrayPaginator` which was used to support TYPO3 10 +- Changed the structure of the overview filters for the `Overview` backend module + - Instead of registering it in the `EXTCONF` the filters are now registered through the `Services.yaml` + - Extra methods have been added to the `OverviewDataProviderInterface` as a replacement of the array configuration + - DataProviders are now initialized with a new `DataProviderRequest` DTO object ### Added - Support for TYPO3 13 diff --git a/Classes/Backend/Overview/DataProviderRequest.php b/Classes/Backend/Overview/DataProviderRequest.php new file mode 100644 index 00000000..51dd4890 --- /dev/null +++ b/Classes/Backend/Overview/DataProviderRequest.php @@ -0,0 +1,29 @@ +id; + } + + public function getLanguage(): int + { + return $this->language; + } + + public function getTable(): string + { + return $this->table; + } +} diff --git a/Classes/Backend/Overview/LanguageMenu/LanguageMenuFactory.php b/Classes/Backend/Overview/LanguageMenu/LanguageMenuFactory.php new file mode 100644 index 00000000..cd6937be --- /dev/null +++ b/Classes/Backend/Overview/LanguageMenu/LanguageMenuFactory.php @@ -0,0 +1,120 @@ +siteFinder->getSiteByPageId($pageUid); + } catch (SiteNotFoundException) { + return null; + } + + $languages = $site->getAvailableLanguages($this->getBackendUser()); + if (empty($languages)) { + return null; + } + + $this->request = $request; + $this->moduleTemplate = $moduleTemplate; + $this->pageUid = $pageUid; + + return $this->buildLanguageMenu($languages); + } + + /** + * @param SiteLanguage[] $languages + */ + protected function buildLanguageMenu( + array $languages + ): Menu { + $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu(); + $languageMenu->setIdentifier('languageMenu'); + $languageMenu->setLabel( + $this->getLanguageService()->sL( + 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language' + ) + ); + foreach ($this->getLanguageMenuItems($languages) as $languageMenuItem) { + $menuItem = $languageMenu + ->makeMenuItem() + ->setTitle($languageMenuItem->getTitle()) + ->setHref($languageMenuItem->getHref()) + ->setActive($languageMenuItem->isActive()); + $languageMenu->addMenuItem($menuItem); + } + return $languageMenu; + } + + /** + * @param SiteLanguage[] $languages + * @return LanguageMenuItem[] + */ + protected function getLanguageMenuItems( + array $languages + ): array { + $arguments = $this->getArguments(); + + $filter = $arguments['filter'] ?? ''; + $returnUrl = $arguments['returnUrl'] ?? ''; + $items = []; + foreach ($languages as $language) { + $url = $this->uriBuilder + ->reset() + ->setRequest($this->request) + ->setTargetPageUid($this->pageUid) + ->setArguments([ + 'tx_yoastseo_yoast_yoastseooverview' => [ + 'filter' => $filter, + 'language' => $language->getLanguageId(), + 'returnUrl' => $returnUrl, + 'controller' => 'Overview', + ], + ]) + ->build(); + $items[] = new LanguageMenuItem( + $language->getTitle(), + $url, + (int)($arguments['language'] ?? 0) === $language->getLanguageId() + ); + } + return $items; + } + + /** + * @return array + */ + protected function getArguments(): array + { + return $this->request->getArguments()['tx_yoastseo_yoast_yoastseooverview'] ?? $this->request->getArguments(); + } +} diff --git a/Classes/Backend/Overview/LanguageMenu/LanguageMenuItem.php b/Classes/Backend/Overview/LanguageMenu/LanguageMenuItem.php new file mode 100644 index 00000000..353eec9f --- /dev/null +++ b/Classes/Backend/Overview/LanguageMenu/LanguageMenuItem.php @@ -0,0 +1,45 @@ +title; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function getHref(): string + { + return $this->href; + } + + public function setHref(string $href): void + { + $this->href = $href; + } + + public function isActive(): bool + { + return $this->active; + } + + public function setActive(bool $active): void + { + $this->active = $active; + } +} diff --git a/Classes/Backend/PageLayoutHeader.php b/Classes/Backend/PageLayoutHeader.php index 4f2cb0bc..29e32dbd 100644 --- a/Classes/Backend/PageLayoutHeader.php +++ b/Classes/Backend/PageLayoutHeader.php @@ -21,7 +21,10 @@ public function __construct( ) { } - public function render(array $params = null, $parentObj = null): string + /** + * @param array|null $params + */ + public function render(array $params = null, PageLayoutController|ModuleTemplate|null $parentObj = null): string { $languageId = $this->getLanguageId(); $pageId = (int)$_GET['id']; @@ -60,7 +63,10 @@ protected function renderHtml(): string return $templateView->render(); } - protected function getCurrentPage(int $pageId, int $languageId, object $parentObj): ?array + /** + * @return array|null + */ + protected function getCurrentPage(int $pageId, int $languageId, PageLayoutController|ModuleTemplate|null $parentObj): ?array { if ((!$parentObj instanceof PageLayoutController && !$parentObj instanceof ModuleTemplate) || $pageId <= 0) { return null; @@ -88,6 +94,9 @@ protected function getCurrentPage(int $pageId, int $languageId, object $parentOb return null; } + /** + * @param array $pageRecord + */ protected function shouldShowPreview(int $pageId, array $pageRecord): bool { if (!YoastUtility::snippetPreviewEnabled($pageId, $pageRecord)) { diff --git a/Classes/Controller/AbstractBackendController.php b/Classes/Controller/AbstractBackendController.php index 7ccfed86..9799f72b 100644 --- a/Classes/Controller/AbstractBackendController.php +++ b/Classes/Controller/AbstractBackendController.php @@ -8,15 +8,21 @@ use TYPO3\CMS\Backend\Template\ModuleTemplate; use TYPO3\CMS\Backend\Template\ModuleTemplateFactory; use TYPO3\CMS\Backend\Utility\BackendUtility; -use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use YoastSeoForTypo3\YoastSeo\Traits\BackendUserTrait; +use YoastSeoForTypo3\YoastSeo\Traits\LanguageServiceTrait; abstract class AbstractBackendController extends ActionController { - protected function returnResponse(array $data = [], ModuleTemplate $moduleTemplate = null): ResponseInterface + use BackendUserTrait, LanguageServiceTrait; + + /** + * @param array $data + */ + protected function returnResponse(string $template, array $data = [], ModuleTemplate $moduleTemplate = null): ResponseInterface { $data['layout'] = GeneralUtility::makeInstance(Typo3Version::class) ->getMajorVersion() < 13 ? 'Default' : 'Module'; @@ -33,7 +39,7 @@ protected function returnResponse(array $data = [], ModuleTemplate $moduleTempla $moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->getPageInformation()); $moduleTemplate->assignMultiple($data); - return $moduleTemplate->renderResponse(); + return $moduleTemplate->renderResponse($template); } protected function getModuleTemplate(): ModuleTemplate @@ -42,6 +48,9 @@ protected function getModuleTemplate(): ModuleTemplate return $moduleTemplateFactory->create($this->request); } + /** + * @return array + */ protected function getPageInformation(): array { $id = (int)($this->request->getQueryParams()['id'] ?? 0); @@ -54,9 +63,4 @@ protected function getPageInformation(): array ); return is_array($pageInformation) ? $pageInformation : []; } - - protected function getBackendUser(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } } diff --git a/Classes/Controller/AjaxController.php b/Classes/Controller/AjaxController.php index 144d6107..b89e4a8f 100644 --- a/Classes/Controller/AjaxController.php +++ b/Classes/Controller/AjaxController.php @@ -4,6 +4,7 @@ namespace YoastSeoForTypo3\YoastSeo\Controller; +use Throwable; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -63,10 +64,19 @@ public function saveScoresAction( return new JsonResponse($data); } + /** + * @param array $data + */ protected function saveScores(array $data): void { $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($data['table']); - $row = $connection->select(['*'], $data['table'], ['uid' => (int)$data['uid']], [], [], 1)->fetchAssociative(); + try { + $row = $connection->select(['*'], $data['table'], ['uid' => (int)$data['uid']], [], + [], + 1)->fetchAssociative(); + } catch (Throwable) { + return; + } if ($row !== false && isset($row['tx_yoastseo_score_readability'], $row['tx_yoastseo_score_seo'])) { $connection->update($data['table'], [ @@ -149,6 +159,9 @@ public function crawlerIndexPages( return new JsonResponse($indexInformation); } + /** + * @return array + */ protected function getCrawlerRequestData(ServerRequestInterface $request): array { $crawlerData = $this->getJsonData($request); @@ -162,6 +175,9 @@ protected function getCrawlerRequestData(ServerRequestInterface $request): array ]; } + /** + * @return array + */ protected function getJsonData(ServerRequestInterface $request): array { $body = $request->getBody()->getContents(); diff --git a/Classes/Controller/CrawlerController.php b/Classes/Controller/CrawlerController.php index 4016e0ff..47afcff2 100644 --- a/Classes/Controller/CrawlerController.php +++ b/Classes/Controller/CrawlerController.php @@ -23,7 +23,7 @@ public function __construct( public function indexAction(): ResponseInterface { $this->addYoastJavascriptConfig(); - return $this->returnResponse(['sites' => $this->siteFinder->getAllSites()]); + return $this->returnResponse('Crawler/Index', ['sites' => $this->siteFinder->getAllSites()]); } public function resetProgressAction(int $site, int $language):? ResponseInterface diff --git a/Classes/Controller/DashboardController.php b/Classes/Controller/DashboardController.php index c1b3b732..535a9b09 100644 --- a/Classes/Controller/DashboardController.php +++ b/Classes/Controller/DashboardController.php @@ -10,6 +10,6 @@ class DashboardController extends AbstractBackendController { public function indexAction(): ResponseInterface { - return $this->returnResponse(); + return $this->returnResponse('Dashboard/Index'); } } diff --git a/Classes/Controller/OverviewController.php b/Classes/Controller/OverviewController.php index 82954581..62ef6239 100644 --- a/Classes/Controller/OverviewController.php +++ b/Classes/Controller/OverviewController.php @@ -5,45 +5,54 @@ namespace YoastSeoForTypo3\YoastSeo\Controller; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Core\Exception\SiteNotFoundException; -use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Pagination\ArrayPaginator; -use TYPO3\CMS\Core\Site\Entity\Site; -use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Backend\Overview\DataProviderRequest; +use YoastSeoForTypo3\YoastSeo\Backend\Overview\LanguageMenu\LanguageMenuFactory; +use YoastSeoForTypo3\YoastSeo\DataProviders\OverviewDataProviderInterface; use YoastSeoForTypo3\YoastSeo\Pagination\Pagination; class OverviewController extends AbstractBackendController { + /** @var array */ + protected array $filters; + + /** + * @param iterable $filters + */ + public function __construct( + protected LanguageMenuFactory $languageMenuFactory, + iterable $filters + ) { + foreach ($filters as $key => $dataProvider) { + $this->addFilter($key, $dataProvider); + } + } + public function listAction(int $currentPage = 1): ResponseInterface { $overviewData = $this->getOverviewData($currentPage) + ['action' => 'list']; $moduleTemplate = $this->getModuleTemplate(); if (!isset($overviewData['pageInformation'])) { - return $this->returnResponse($overviewData, $moduleTemplate); + return $this->returnResponse('Overview/List', $overviewData, $moduleTemplate); } $moduleTemplate->getDocHeaderComponent()->setMetaInformation($overviewData['pageInformation']); - $languageMenuItems = $this->getLanguageMenuItems($overviewData['pageInformation']); - if (is_array($languageMenuItems)) { - $languageMenu = $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu(); - $languageMenu->setIdentifier('languageMenu'); - $languageMenu->setLabel( - $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language') - ); - foreach ($languageMenuItems as $languageMenuItem) { - $menuItem = $languageMenu - ->makeMenuItem() - ->setTitle($languageMenuItem['title']) - ->setHref($languageMenuItem['href']) - ->setActive($languageMenuItem['active']); - $languageMenu->addMenuItem($menuItem); - } + $languageMenu = $this->languageMenuFactory->create( + $this->request, + $moduleTemplate, + $overviewData['pageInformation']['uid'] ?? 0 + ); + if ($languageMenu !== null) { $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu); } - return $this->returnResponse($overviewData, $moduleTemplate); + + return $this->returnResponse('Overview/List', $overviewData, $moduleTemplate); } + /** + * @return array + */ protected function getOverviewData(int $currentPage): array { $pageInformation = $this->getPageInformation(); @@ -52,11 +61,12 @@ protected function getOverviewData(int $currentPage): array } $filters = $this->getAvailableFilters(); - $activeFilter = $this->getActiveFilter($filters); - $currentFilter = $filters[$activeFilter]; + if ($filters === null) { + return []; + } - $params = $this->getParams(); - $items = GeneralUtility::callUserFunction($currentFilter['dataProvider'], $params, $this) ?: []; + $activeFilter = $this->getActiveFilter(); + $items = $activeFilter->process(); $arrayPaginator = GeneralUtility::makeInstance( ArrayPaginator::class, @@ -73,114 +83,54 @@ protected function getOverviewData(int $currentPage): array 'pagination' => $pagination, 'filters' => $filters, 'activeFilter' => $activeFilter, - 'params' => $params, - 'subtitle' => $this->getLanguageService()->sL($currentFilter['label']), - 'description' => $currentFilter['description'], - 'link' => $currentFilter['link'], + 'params' => $this->getDataProviderRequest(), ]; } + /** + * @return array|null + */ public function getAvailableFilters(): ?array { - if (!is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['yoast_seo']['overview_filters'] ?? false)) { + if ($this->filters === []) { return null; } - $params = $this->getParams(); - foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['yoast_seo']['overview_filters'] as &$filter) { - $filter['numberOfItems'] = (int)GeneralUtility::callUserFunction( - $filter['countProvider'], - $params, - $this - ); - } - return (array)$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['yoast_seo']['overview_filters']; - } - - protected function getActiveFilter(array $filters): string - { - $activeFilter = ''; - - if ($this->request->hasArgument('filter')) { - foreach ($filters as $k => $f) { - if ($f['key'] === $this->request->getArgument('filter')) { - $activeFilter = $k; - break; - } - } - } - - if (empty($activeFilter)) { - $activeFilter = key($filters); + $dataProviderRequest = $this->getDataProviderRequest(); + foreach ($this->filters as $dataProvider) { + $dataProvider->initialize($dataProviderRequest); } - return (string)$activeFilter; + return $this->filters; } - protected function getLanguageMenuItems(array $pageInformation): ?array + protected function getActiveFilter(): OverviewDataProviderInterface { - $site = $this->getSite($pageInformation['uid']); - if ($site === null) { - return null; - } - - $languages = $site->getAvailableLanguages($this->getBackendUser()); - if (count($languages) === 0) { - return null; + if ($this->filters === []) { + throw new \RuntimeException('No filters available'); } - $arguments = $this->getArguments(); - - $filter = $arguments['filter'] ?? ''; - $returnUrl = $arguments['returnUrl'] ?? ''; - $items = []; - foreach ($languages as $language) { - $url = $this->uriBuilder - ->reset() - ->setTargetPageUid($pageInformation['uid']) - ->setArguments([ - 'tx_yoastseo_yoast_yoastseooverview' => [ - 'filter' => $filter, - 'language' => $language->getLanguageId(), - 'returnUrl' => $returnUrl, - 'controller' => 'Overview' - ] - ]) - ->build(); - $items[] = [ - 'title' => $language->getTitle(), - 'href' => $url, - 'active' => (int)($arguments['language'] ?? 0) === $language->getLanguageId() - ]; - } - return $items; - } - - protected function getSite(int $pageUid): ?Site - { - $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); - try { - return $siteFinder->getSiteByPageId($pageUid); - } catch (SiteNotFoundException $e) { - return null; + if ($this->request->hasArgument('filter')) { + $activeFilter = $this->request->getArgument('filter'); + if (is_string($activeFilter) && isset($this->filters[$activeFilter])) { + return $this->filters[$activeFilter]; + } } - } - protected function getParams(): array - { - return [ - 'language' => $this->getArguments()['language'] ?? 0, - 'table' => 'pages' - ]; + return current($this->filters); } - protected function getArguments(): array + protected function getDataProviderRequest(): DataProviderRequest { - return $this->request->getArguments()['tx_yoastseo_yoast_yoastseooverview'] ?? $this->request->getArguments(); + return new DataProviderRequest( + (int)($this->request->getQueryParams()['id'] ?? 0), + (int)($this->request->getQueryParams()['tx_yoastseo_yoast_yoastseooverview']['language'] ?? $this->request->getQueryParams()['language'] ?? 0), + 'pages' + ); } - public function getLanguageService(): LanguageService + protected function addFilter(string $key, OverviewDataProviderInterface $dataProvider): void { - return $GLOBALS['LANG']; + $this->filters[$key] = $dataProvider; } } diff --git a/Classes/DataProviders/AbstractOverviewDataProvider.php b/Classes/DataProviders/AbstractOverviewDataProvider.php index 90a8a210..c9cea0f2 100644 --- a/Classes/DataProviders/AbstractOverviewDataProvider.php +++ b/Classes/DataProviders/AbstractOverviewDataProvider.php @@ -7,6 +7,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Platform\PlatformInformation; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Backend\Overview\DataProviderRequest; use YoastSeoForTypo3\YoastSeo\Utility\PageAccessUtility; abstract class AbstractOverviewDataProvider implements OverviewDataProviderInterface @@ -30,54 +31,62 @@ abstract class AbstractOverviewDataProvider implements OverviewDataProviderInter 'title', 'seo_title', 'tx_yoastseo_score_readability', - 'tx_yoastseo_score_seo' + 'tx_yoastseo_score_seo', ]; - protected array $callerParams = []; + protected DataProviderRequest $dataProviderRequest; - public function process(array $params): array + public function initialize(DataProviderRequest $dataProviderRequest): void { - $this->callerParams = $params; - - return $this->getRestrictedPagesResults(); + $this->dataProviderRequest = $dataProviderRequest; } - public function numberOfItems(array $params): int + /** + * @return array> + */ + public function process(): array { - $this->callerParams = $params; - - return $this->getRestrictedPagesResults(true); - } - - protected function getRestrictedPagesResults(bool $returnOnlyCount = false) - { - $pageIds = PageAccessUtility::getPageIds((int)$_GET['id']); - - $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::PAGES_TABLE); - $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform()); - $pages = []; - $pageCount = 0; - foreach (array_chunk($pageIds, $maxBindParameters - 10) as $chunk) { + foreach (array_chunk($this->getPageIds(), $this->getMaxBindParameters() - 10) as $chunk) { $query = $this->getResults($chunk); if ($query === null) { continue; } - - if ($returnOnlyCount) { - $pageCount += $query->rowCount(); - continue; - } - foreach ($query->fetchAllAssociative() as $page) { $pages[] = $page; } } - if ($returnOnlyCount === false) { - return $pages; + return $pages; + } + + public function getNumberOfItems(): int + { + $pageCount = 0; + foreach (array_chunk($this->getPageIds(), $this->getMaxBindParameters() - 10) as $chunk) { + $query = $this->getResults($chunk); + if ($query === null) { + continue; + } + $pageCount += $query->rowCount(); } + return (int)$pageCount; + } + + /** + * @return int[] + */ + protected function getPageIds(): array + { + return PageAccessUtility::getPageIds($this->dataProviderRequest->getId()); + } - return $pageCount; + /** + * @return int<999, max> + */ + protected function getMaxBindParameters(): int + { + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::PAGES_TABLE); + return max(999, PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform())); } } diff --git a/Classes/DataProviders/CornerstoneOverviewDataProvider.php b/Classes/DataProviders/CornerstoneOverviewDataProvider.php index 489fa639..f64862a2 100644 --- a/Classes/DataProviders/CornerstoneOverviewDataProvider.php +++ b/Classes/DataProviders/CornerstoneOverviewDataProvider.php @@ -10,18 +10,41 @@ class CornerstoneOverviewDataProvider extends AbstractOverviewDataProvider { + public function getKey(): string + { + return 'cornerstone'; + } + + public function getLabel(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:cornerstoneContent'; + } + + public function getDescription(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:cornerstoneContent.description'; + } + + public function getLink(): ?string + { + return 'https://yoa.st/typo3-cornerstone-content'; + } + + /** + * @param int[] $pageIds + */ public function getResults(array $pageIds = []): ?Result { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::PAGES_TABLE); $constraints = [ - $queryBuilder->expr()->eq('sys_language_uid', (int)$this->callerParams['language']), - $queryBuilder->expr()->eq('tx_yoastseo_cornerstone', 1) + $queryBuilder->expr()->eq('sys_language_uid', $this->dataProviderRequest->getLanguage()), + $queryBuilder->expr()->eq('tx_yoastseo_cornerstone', 1), ]; if (count($pageIds) > 0) { $constraints[] = $queryBuilder->expr()->in( - (int)$this->callerParams['language'] > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', + $this->dataProviderRequest->getLanguage() > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', $pageIds ); } diff --git a/Classes/DataProviders/OrphanedContentDataProvider.php b/Classes/DataProviders/OrphanedContentDataProvider.php index c141ad1e..d054b07a 100644 --- a/Classes/DataProviders/OrphanedContentDataProvider.php +++ b/Classes/DataProviders/OrphanedContentDataProvider.php @@ -12,26 +12,50 @@ class OrphanedContentDataProvider extends AbstractOverviewDataProvider { + public function getKey(): string + { + return 'orphaned'; + } + + public function getLabel(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:orphanedContent'; + } + + public function getDescription(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:orphanedContent.description'; + } + + public function getLink(): ?string + { + return 'https://yoa.st/1ja'; + } + + /** @var int[] */ protected array $referencedPages = []; + /** + * @param int[] $pageIds + */ public function getResults(array $pageIds = []): ?Result { if ($this->referencedPages === []) { - $this->setReferencedPages(); + $this->referencedPages = $this->getReferencedPages(); } $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); $constraints = [ $qb->expr()->in('doktype', YoastUtility::getAllowedDoktypes()), - $qb->expr()->eq('sys_language_uid', (int)$this->callerParams['language']) + $qb->expr()->eq('sys_language_uid', $this->dataProviderRequest->getLanguage()) ]; if (count($this->referencedPages) > 0) { $constraints[] = $qb->expr()->notIn('uid', $this->referencedPages); } if (count($pageIds) > 0) { $constraints[] = $qb->expr()->in( - (int)$this->callerParams['language'] > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', + $this->dataProviderRequest->getLanguage() > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', $pageIds ); } @@ -42,7 +66,10 @@ public function getResults(array $pageIds = []): ?Result ->executeQuery(); } - protected function setReferencedPages(): void + /** + * @return int[] + */ + protected function getReferencedPages(): array { $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex'); @@ -64,8 +91,6 @@ protected function setReferencedPages(): void ->executeQuery() ->fetchAllAssociative(); - foreach ($references as $ref) { - $this->referencedPages[] = $ref['ref_uid']; - } + return array_column($references, 'ref_uid'); } } diff --git a/Classes/DataProviders/OverviewDataProviderInterface.php b/Classes/DataProviders/OverviewDataProviderInterface.php index a618ddbc..930d170a 100644 --- a/Classes/DataProviders/OverviewDataProviderInterface.php +++ b/Classes/DataProviders/OverviewDataProviderInterface.php @@ -5,8 +5,29 @@ namespace YoastSeoForTypo3\YoastSeo\DataProviders; use Doctrine\DBAL\Result; +use YoastSeoForTypo3\YoastSeo\Backend\Overview\DataProviderRequest; interface OverviewDataProviderInterface { + public function getKey(): string; + + public function getLabel(): string; + + public function getDescription(): string; + + public function getLink(): ?string; + + public function initialize(DataProviderRequest $dataProviderRequest): void; + + /** + * @return array> + */ + public function process(): array; + + public function getNumberOfItems(): int; + + /** + * @param int[] $pageIds + */ public function getResults(array $pageIds = []): ?Result; } diff --git a/Classes/DataProviders/PagesWithoutDescriptionOverviewDataProvider.php b/Classes/DataProviders/PagesWithoutDescriptionOverviewDataProvider.php index cc34dd8c..3dcabb64 100644 --- a/Classes/DataProviders/PagesWithoutDescriptionOverviewDataProvider.php +++ b/Classes/DataProviders/PagesWithoutDescriptionOverviewDataProvider.php @@ -11,6 +11,26 @@ class PagesWithoutDescriptionOverviewDataProvider extends AbstractOverviewDataProvider { + public function getKey(): string + { + return 'withoutDescription'; + } + + public function getLabel(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:withoutDescription'; + } + + public function getDescription(): string + { + return 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf:withoutDescription.description'; + } + + public function getLink(): ?string + { + return 'https://yoa.st/typo3-meta-description'; + } + protected const PAGES_TABLE = 'pages'; public function getResults(array $pageIds = []): ?Result @@ -24,12 +44,12 @@ public function getResults(array $pageIds = []): ?Result ), $queryBuilder->expr()->in('doktype', YoastUtility::getAllowedDoktypes()), $queryBuilder->expr()->eq('tx_yoastseo_hide_snippet_preview', 0), - $queryBuilder->expr()->eq('sys_language_uid', (int)$this->callerParams['language']) + $queryBuilder->expr()->eq('sys_language_uid', $this->dataProviderRequest->getLanguage()) ]; if (count($pageIds) > 0) { $constraints[] = $queryBuilder->expr()->in( - (int)$this->callerParams['language'] > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', + $this->dataProviderRequest->getLanguage() > 0 ? $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] : 'uid', $pageIds ); } diff --git a/Classes/Form/Element/Cornerstone.php b/Classes/Form/Element/Cornerstone.php index c78487b1..b3c0c3b1 100644 --- a/Classes/Form/Element/Cornerstone.php +++ b/Classes/Form/Element/Cornerstone.php @@ -10,6 +10,9 @@ class Cornerstone extends AbstractNode { + /** + * @return array + */ public function render(): array { $resultArray = $this->initializeResultArray(); diff --git a/Classes/Form/Element/FocusKeywordAnalysis.php b/Classes/Form/Element/FocusKeywordAnalysis.php index e2ced961..b1354f74 100644 --- a/Classes/Form/Element/FocusKeywordAnalysis.php +++ b/Classes/Form/Element/FocusKeywordAnalysis.php @@ -11,6 +11,9 @@ class FocusKeywordAnalysis extends AbstractNode { + /** + * @return array + */ public function render(): array { $resultArray = $this->initializeResultArray(); diff --git a/Classes/Form/Element/Insights.php b/Classes/Form/Element/Insights.php index b712460b..1671ab2b 100644 --- a/Classes/Form/Element/Insights.php +++ b/Classes/Form/Element/Insights.php @@ -10,6 +10,9 @@ class Insights extends AbstractNode { + /** + * @return array + */ public function render(): array { $resultArray = $this->initializeResultArray(); diff --git a/Classes/Form/Element/InternalLinkingSuggestion.php b/Classes/Form/Element/InternalLinkingSuggestion.php index f5d77873..68aa4a34 100644 --- a/Classes/Form/Element/InternalLinkingSuggestion.php +++ b/Classes/Form/Element/InternalLinkingSuggestion.php @@ -22,6 +22,9 @@ class InternalLinkingSuggestion extends AbstractNode protected int $languageId; protected int $currentPage; + /** + * @return array + */ public function render(): array { $this->init(); diff --git a/Classes/Form/Element/ReadabilityAnalysis.php b/Classes/Form/Element/ReadabilityAnalysis.php index df309273..bba25420 100644 --- a/Classes/Form/Element/ReadabilityAnalysis.php +++ b/Classes/Form/Element/ReadabilityAnalysis.php @@ -11,6 +11,9 @@ class ReadabilityAnalysis extends AbstractNode { + /** + * @return array + */ public function render(): array { $resultArray = $this->initializeResultArray(); diff --git a/Classes/Form/Element/SnippetPreview.php b/Classes/Form/Element/SnippetPreview.php index a522640a..688d5a5d 100644 --- a/Classes/Form/Element/SnippetPreview.php +++ b/Classes/Form/Element/SnippetPreview.php @@ -29,6 +29,9 @@ class SnippetPreview extends AbstractNode protected UrlService $urlService; + /** + * @return array + */ public function render(): array { $this->initialize(); @@ -187,7 +190,7 @@ protected function getPreviewUrl(): string // map record data to GET parameters if (isset($previewConfiguration['fieldToParameterMap.'])) { foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) { - $value = $recordArray[$field]; + $value = $recordArray[$field] ?? ''; if ($field === 'uid') { $value = $recordId; } @@ -220,6 +223,9 @@ protected function getPreviewUrl(): string return $this->urlService->getPreviewUrl($previewPageId, $languageId, $additionalParamsForUrl); } + /** + * @param array $previewConfiguration + */ protected function getPreviewPageId(int $currentPageId, array $previewConfiguration): int { // find the right preview page id @@ -259,8 +265,8 @@ protected function getPreviewPageId(int $currentPageId, array $previewConfigurat * The result can be used to create a query string with * GeneralUtility::implodeArrayForUrl(). * - * @param array $parameters Should be an empty array by default - * @param array $typoScript The TypoScript configuration + * @param array $parameters Should be an empty array by default + * @param array $typoScript The TypoScript configuration */ protected function parseAdditionalGetParameters( array &$parameters, diff --git a/Classes/Frontend/AdditionalPreviewData.php b/Classes/Frontend/AdditionalPreviewData.php index b5347859..894a16f0 100644 --- a/Classes/Frontend/AdditionalPreviewData.php +++ b/Classes/Frontend/AdditionalPreviewData.php @@ -13,6 +13,7 @@ class AdditionalPreviewData implements SingletonInterface { + /** @var array */ protected array $config; public function __construct() @@ -20,6 +21,9 @@ public function __construct() $this->config = $GLOBALS['TSFE']->tmpl->setup['config.'] ?? []; } + /** + * @param array $params + */ public function render(array &$params, object $pObj): void { $serverParams = $GLOBALS['TYPO3_REQUEST'] ? $GLOBALS['TYPO3_REQUEST']->getServerParams() : $_SERVER; @@ -51,6 +55,9 @@ protected function getWebsiteTitle(): string return ''; } + /** + * @return string[] + */ protected function getPageTitlePrependAppend(): array { $prependAppend = ['prepend' => '', 'append' => '']; @@ -81,7 +88,7 @@ protected function getPageTitleSeparator(): string } if (is_array($this->config['pageTitleSeparator.'] ?? null)) { - return GeneralUtility::makeInstance(ContentObjectRenderer::class) + return (string)GeneralUtility::makeInstance(ContentObjectRenderer::class) ->stdWrap($this->config['pageTitleSeparator'], $this->config['pageTitleSeparator.']); } diff --git a/Classes/Hooks/BackendYoastConfig.php b/Classes/Hooks/BackendYoastConfig.php index 0533e71f..ef4cdc6a 100644 --- a/Classes/Hooks/BackendYoastConfig.php +++ b/Classes/Hooks/BackendYoastConfig.php @@ -12,6 +12,9 @@ class BackendYoastConfig { + /** + * @param array $params + */ public function renderConfig(array &$params, PageRenderer $pObject): void { if (!($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface diff --git a/Classes/MetaTag/AdvancedRobotsGenerator.php b/Classes/MetaTag/AdvancedRobotsGenerator.php index cb51ae2c..95918d89 100644 --- a/Classes/MetaTag/AdvancedRobotsGenerator.php +++ b/Classes/MetaTag/AdvancedRobotsGenerator.php @@ -13,16 +13,13 @@ class AdvancedRobotsGenerator { - protected RecordService $recordService; - - public function __construct(RecordService $recordService = null) - { - if ($recordService === null) { - $recordService = GeneralUtility::makeInstance(RecordService::class); - } - $this->recordService = $recordService; - } + public function __construct( + protected RecordService $recordService + ) {} + /** + * @param array $params + */ public function generate(array $params): void { $activeRecord = $this->recordService->getActiveRecord(); diff --git a/Classes/MetaTag/Generator/AbstractGenerator.php b/Classes/MetaTag/Generator/AbstractGenerator.php index 6b3741bc..f9bc4e28 100644 --- a/Classes/MetaTag/Generator/AbstractGenerator.php +++ b/Classes/MetaTag/Generator/AbstractGenerator.php @@ -12,7 +12,7 @@ use TYPO3\CMS\Extbase\Service\ImageService; use YoastSeoForTypo3\YoastSeo\Record\Record; -abstract class AbstractGenerator +abstract class AbstractGenerator implements GeneratorInterface { protected MetaTagManagerRegistry $managerRegistry; @@ -26,8 +26,8 @@ public function __construct(MetaTagManagerRegistry $managerRegistry = null) /** * @see \TYPO3\CMS\Seo\MetaTag\MetaTagGenerator - * @param array $fileReferences - * @return array + * @param FileReference[] $fileReferences + * @return array */ protected function generateSocialImages(array $fileReferences): array { @@ -35,7 +35,6 @@ protected function generateSocialImages(array $fileReferences): array $socialImages = []; - /** @var FileReference $file */ foreach ($fileReferences as $file) { $arguments = $file->getProperties(); $cropVariantCollection = CropVariantCollection::create((string)$arguments['crop']); @@ -65,6 +64,4 @@ protected function generateSocialImages(array $fileReferences): array return $socialImages; } - - abstract public function generate(Record $record): void; } diff --git a/Classes/MetaTag/Generator/GeneratorInterface.php b/Classes/MetaTag/Generator/GeneratorInterface.php new file mode 100644 index 00000000..f25c6b7d --- /dev/null +++ b/Classes/MetaTag/Generator/GeneratorInterface.php @@ -0,0 +1,12 @@ +recordService = $recordService; - } + public function __construct( + protected RecordService $recordService + ) {} public function generate(): void { @@ -27,8 +22,12 @@ public function generate(): void return; } + /** @var class-string $generatorClass */ foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['yoast_seo']['recordMetaTags'] ?? [] as $generatorClass) { - GeneralUtility::makeInstance($generatorClass)->generate($activeRecord); + $generator = GeneralUtility::makeInstance($generatorClass); + if ($generator instanceof GeneratorInterface) { + $generator->generate($activeRecord); + } } } } diff --git a/Classes/Pagination/Pagination.php b/Classes/Pagination/Pagination.php index d4fdfa1f..b00ca0ee 100644 --- a/Classes/Pagination/Pagination.php +++ b/Classes/Pagination/Pagination.php @@ -62,7 +62,7 @@ public function getLastPageNumber(): int } /** - * @return int[] + * @return array */ public function getAllPageNumbers(): array { diff --git a/Classes/Record/Builder/AbstractBuilder.php b/Classes/Record/Builder/AbstractBuilder.php index 84e4ec1f..bb25e1ea 100644 --- a/Classes/Record/Builder/AbstractBuilder.php +++ b/Classes/Record/Builder/AbstractBuilder.php @@ -17,5 +17,9 @@ public function setRecord(Record $record): self } abstract public function build(): void; + + /** + * @return array|string[] + */ abstract public function getResult(): array; } diff --git a/Classes/Record/Builder/SchemaBuilder.php b/Classes/Record/Builder/SchemaBuilder.php index c52b25a7..c2c4a361 100644 --- a/Classes/Record/Builder/SchemaBuilder.php +++ b/Classes/Record/Builder/SchemaBuilder.php @@ -6,6 +6,7 @@ class SchemaBuilder extends AbstractBuilder { + /** @var string[] */ protected array $sqlData = []; public function build(): void diff --git a/Classes/Record/Record.php b/Classes/Record/Record.php index 930dcbd8..b87b20a0 100644 --- a/Classes/Record/Record.php +++ b/Classes/Record/Record.php @@ -16,9 +16,12 @@ class Record protected string $descriptionField = 'description'; protected bool $addDescriptionField = false; protected string $fieldsPosition = 'after:' . self::DEFAULT_TITLE_FIELD; + /** @var array */ protected array $overrideTca = []; + /** @var array */ protected array $getParameters = []; protected ?int $recordUid = null; + /** @var array */ protected array $recordData = []; protected bool $generatePageTitle = true; protected bool $generateMetaTags = true; @@ -123,22 +126,34 @@ public function setFieldsPosition(string $fieldsPosition): self return $this; } + /** + * @return array + */ public function getOverrideTca(): array { return $this->overrideTca; } + /** + * @param array $overrideTca + */ public function setOverrideTca(array $overrideTca): self { $this->overrideTca = $overrideTca; return $this; } + /** + * @return array + */ public function getGetParameters(): array { return $this->getParameters; } + /** + * @param array $getParameters + */ public function setGetParameters(array $getParameters): self { $this->getParameters = $getParameters; @@ -189,11 +204,17 @@ public function setRecordUid(int $recordUid): self return $this; } + /** + * @return array + */ public function getRecordData(): array { return $this->recordData; } + /** + * @param array $recordData + */ public function setRecordData(array $recordData): self { $this->recordData = $recordData; diff --git a/Classes/Record/RecordRegistry.php b/Classes/Record/RecordRegistry.php index d5722bff..2794802e 100644 --- a/Classes/Record/RecordRegistry.php +++ b/Classes/Record/RecordRegistry.php @@ -41,7 +41,7 @@ public function getRecords(bool $useCache = false): array if ($this->records === null) { $this->retrieveRecords($useCache); } - return $this->records; + return (array)$this->records; } protected function retrieveRecords(bool $useCache): void diff --git a/Classes/Record/RecordService.php b/Classes/Record/RecordService.php index 82c3fd23..ee56c4fa 100644 --- a/Classes/Record/RecordService.php +++ b/Classes/Record/RecordService.php @@ -68,6 +68,9 @@ protected function findRecord(array $records): ?Record return null; } + /** + * @return array + */ protected function getRecordData(Record $record): array { $recordRow = GeneralUtility::makeInstance(ConnectionPool::class) diff --git a/Classes/Service/CrawlerService.php b/Classes/Service/CrawlerService.php index 494fa612..eb758cb0 100644 --- a/Classes/Service/CrawlerService.php +++ b/Classes/Service/CrawlerService.php @@ -27,7 +27,10 @@ public function getAmountOfPages(int $site, int $languageId): int return count($this->getPagesToIndex($site, $languageId)); } - public function getIndexInformation(int $site, int $languageId, $offset = 0): array + /** + * @return array{pages: array, current: int, nextOffset: int, total: int} + */ + public function getIndexInformation(int $site, int $languageId, int $offset = 0): array { $pagesToIndex = $this->getPagesToIndex($site, $languageId); $total = count($pagesToIndex); @@ -44,6 +47,9 @@ public function getIndexInformation(int $site, int $languageId, $offset = 0): ar ]; } + /** + * @return array{offset?: int, total?: int} + */ public function getProgressInformation(int $site, int $languageId): array { return (array)$this->registry->get( @@ -70,6 +76,9 @@ public function resetProgressInformation(int $site, int $languageId): void $this->registry->remove(self::REGISTRY_NAMESPACE, sprintf(self::REGISTRY_KEY, $site, $languageId)); } + /** + * @return int[] + */ protected function getPagesToIndex(int $site, int $languageId): array { $cacheIdentifier = 'YoastSeoCrawler' . $site . '-' . $languageId; diff --git a/Classes/Service/LinkingSuggestionsService.php b/Classes/Service/LinkingSuggestionsService.php index b0204d68..53371ac6 100644 --- a/Classes/Service/LinkingSuggestionsService.php +++ b/Classes/Service/LinkingSuggestionsService.php @@ -5,16 +5,20 @@ namespace YoastSeoForTypo3\YoastSeo\Service; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\Context\LanguageAspect; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Exception\SiteNotFoundException; -use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Traits\LanguageServiceTrait; class LinkingSuggestionsService { + use LanguageServiceTrait; + protected const PROMINENT_WORDS_TABLE = 'tx_yoastseo_prominent_word'; protected int $excludePageId; @@ -27,6 +31,10 @@ public function __construct( ) { } + /** + * @param array $words + * @return array> + */ public function getLinkingSuggestions( array $words, int $excludePageId, @@ -80,6 +88,10 @@ public function getLinkingSuggestions( return $this->linkRecords($scores, $this->getCurrentContentLinks($content)); } + /** + * @param array $requestWords + * @return array + */ protected function composeRequestData(array $requestWords): array { $requestDocFrequencies = $this->countDocumentFrequencies(array_keys($requestWords)); @@ -97,6 +109,10 @@ protected function composeRequestData(array $requestWords): array return $combinedRequestData; } + /** + * @param string[] $stems + * @return array + */ protected function countDocumentFrequencies(array $stems): array { if ($stems === []) { @@ -133,6 +149,9 @@ static function ($item) { return $docFrequencies; } + /** + * @param array $prominentWords + */ protected function computeVectorLength(array $prominentWords): float { $sumOfSquares = 0; @@ -148,12 +167,16 @@ protected function computeVectorLength(array $prominentWords): float return sqrt($sumOfSquares); } - protected function computeTfIdfScore(int $termFrequency, int $docFrequency) + protected function computeTfIdfScore(int $termFrequency, int $docFrequency): float { $docFrequency = max(1, $docFrequency); return $termFrequency * (1 / $docFrequency); } + /** + * @param string[] $stems + * @return array + */ protected function getCandidateWords(array $stems, int $batchSize, int $page): array { return $this->findStemsByRecords( @@ -161,6 +184,10 @@ protected function getCandidateWords(array $stems, int $batchSize, int $page): a ); } + /** + * @param array $records + * @return array + */ protected function findStemsByRecords(array $records): array { if ($records === []) { @@ -200,10 +227,14 @@ protected function findStemsByRecords(array $records): array return $prominentWords; } + /** + * @param string[] $stems + * @return array + */ protected function findRecordsByStems(array $stems, int $batchSize, int $page): array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::PROMINENT_WORDS_TABLE); - return $queryBuilder->select('pid', 'tablenames') + $queryBuilder->select('pid', 'tablenames') ->from(self::PROMINENT_WORDS_TABLE) ->where( $queryBuilder->expr()->in( @@ -214,11 +245,16 @@ protected function findRecordsByStems(array $stems, int $batchSize, int $page): $queryBuilder->expr()->eq('site', $this->site) ) ->setMaxResults($batchSize) - ->setFirstResult(($page - 1) * $batchSize) - ->executeQuery() - ->fetchAllAssociative(); + ->setFirstResult(($page - 1) * $batchSize); + /** @var array $records */ + $records = $queryBuilder->executeQuery()->fetchAllAssociative(); + return $records; } + /** + * @param array $records + * @return array + */ protected function getProminentWords(array $records): array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::PROMINENT_WORDS_TABLE); @@ -233,31 +269,40 @@ protected function getProminentWords(array $records): array ) ); } - return $queryBuilder->select('stem', 'weight', 'pid', 'tablenames', 'uid_foreign') + $queryBuilder->select('stem', 'weight', 'pid', 'tablenames', 'uid_foreign') ->from(self::PROMINENT_WORDS_TABLE) ->where( $queryBuilder->expr()->or(...$orStatements) - ) - ->executeQuery() - ->fetchAllAssociative(); + ); + /** @var array $prominentWords */ + $prominentWords = $queryBuilder->executeQuery()->fetchAllAssociative(); + return $prominentWords; } + /** + * @param array $candidateWords + * @return array> + */ protected function groupWordsByRecord(array $candidateWords): array { $candidateWordsByRecords = []; foreach ($candidateWords as $candidateWord) { - if (!isset($candidateWord['weight'], $candidateWord['df'])) { + if (!isset($candidateWord['df'])) { continue; } $recordKey = $candidateWord['uid_foreign'] . '-' . $candidateWord['tablenames']; $candidateWordsByRecords[$recordKey][$candidateWord['stem']] = [ 'weight' => (int)$candidateWord['weight'], - 'df' => (int)$candidateWord['df'] + 'df' => (int)$candidateWord['df'], ]; } return $candidateWordsByRecords; } + /** + * @param array $requestData + * @param array $candidateData + */ protected function calculateScoreForIndexable( array $requestData, float $requestVectorLength, @@ -268,6 +313,10 @@ protected function calculateScoreForIndexable( return $this->normalizeScore($rawScore, $candidateVectorLength, $requestVectorLength); } + /** + * @param array $requestData + * @param array $candidateData + */ protected function computeRawScore(array $requestData, array $candidateData): float { $rawScore = 0; @@ -299,6 +348,10 @@ protected function normalizeScore(float $rawScore, float $vectorLengthCandidate, return (float)($rawScore / $normalizingFactor); } + /** + * @param array $scores + * @return array + */ protected function getTopSuggestions(array $scores): array { // Sort the indexables by descending score. @@ -316,6 +369,11 @@ static function ($score1, $score2) { return \array_slice($scores, 0, 20, true); } + /** + * @param array $scores + * @param array $currentLinks + * @return array + */ protected function linkRecords(array $scores, array $currentLinks): array { $links = []; @@ -330,7 +388,7 @@ protected function linkRecords(array $scores, array $currentLinks): array continue; } if ($this->languageId > 0 - && ($overlay = $this->pageRepository->getRecordOverlay($table, $data, $this->languageId, 'mixed'))) { + && ($overlay = $this->getRecordOverlay($table, $data, $this->languageId))) { $data = $overlay; } @@ -343,7 +401,7 @@ protected function linkRecords(array $scores, array $currentLinks): array 'table' => $table, 'cornerstone' => (int)($data['tx_yoastseo_cornerstone'] ?? 0), 'score' => $score, - 'active' => isset($currentLinks[$record]) + 'active' => isset($currentLinks[$record]), ]; } $this->sortSuggestions($links); @@ -354,6 +412,9 @@ protected function linkRecords(array $scores, array $currentLinks): array return array_merge_recursive([], $cornerStoneSuggestions, $nonCornerStoneSuggestions); } + /** + * @param array $links + */ protected function sortSuggestions(array &$links): void { usort( @@ -368,6 +429,10 @@ static function ($suggestion1, $suggestion2) { ); } + /** + * @param array $links + * @return array + */ protected function filterSuggestions(array $links, bool $cornerstone): array { return \array_filter( @@ -378,6 +443,9 @@ static function ($suggestion) use ($cornerstone) { ); } + /** + * @return array + */ protected function getCurrentContentLinks(string $content): array { $currentLinks = []; @@ -406,8 +474,17 @@ protected function getRecordType(string $table): string ); } - protected function getLanguageService(): LanguageService + /** + * @param array $data + * @return array|null + */ + protected function getRecordOverlay(string $table, array $data, int $languageId): array|null { - return $GLOBALS['LANG']; + if (is_callable([$this->pageRepository, 'getRecordOverlay']) + && GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 12) { + return $this->pageRepository->getRecordOverlay($table, $data, $languageId, 'mixed'); + } + $languageAspect = GeneralUtility::makeInstance(LanguageAspect::class, $languageId, $languageId, 'mixed'); + return $this->pageRepository->getLanguageOverlay($table, $data, $languageAspect); } } diff --git a/Classes/Service/LocaleService.php b/Classes/Service/LocaleService.php index 06a82300..aea5934d 100644 --- a/Classes/Service/LocaleService.php +++ b/Classes/Service/LocaleService.php @@ -7,9 +7,12 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Traits\LanguageServiceTrait; class LocaleService { + use LanguageServiceTrait; + protected const APP_TRANSLATION_FILE_PATTERN = 'EXT:yoast_seo/Resources/Private/Language/wordpress-seo-%s.json'; public function __construct( @@ -17,34 +20,44 @@ public function __construct( ) { } + /** + * @return array> + */ public function getTranslations(): array { $interfaceLocale = $this->getInterfaceLocale(); - if ($interfaceLocale !== null - && ($translationFilePath = sprintf( - static::APP_TRANSLATION_FILE_PATTERN, - $interfaceLocale - )) !== false - && ($translationFilePath = GeneralUtility::getFileAbsFileName( - $translationFilePath - )) !== false - && file_exists($translationFilePath) - ) { - return json_decode(file_get_contents($translationFilePath), true); + if ($interfaceLocale === null) { + return []; + } + + $translationFilePath = GeneralUtility::getFileAbsFileName( + sprintf(static::APP_TRANSLATION_FILE_PATTERN, $interfaceLocale) + ); + + if ($translationFilePath === '' || !file_exists($translationFilePath)) { + return []; + } + + if ($jsonContents = file_get_contents($translationFilePath)) { + return json_decode($jsonContents, true); } + return []; } + /** + * @return array + */ public function getLabels(): array { $llPrefix = 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModule.xlf:label'; return [ - 'readability' => $GLOBALS['LANG']->sL($llPrefix . 'Readability'), - 'seo' => $GLOBALS['LANG']->sL($llPrefix . 'Seo'), - 'bad' => $GLOBALS['LANG']->sL($llPrefix . 'Bad'), - 'ok' => $GLOBALS['LANG']->sL($llPrefix . 'Ok'), - 'good' => $GLOBALS['LANG']->sL($llPrefix . 'Good') + 'readability' => $this->getLanguageService()->sL($llPrefix . 'Readability'), + 'seo' => $this->getLanguageService()->sL($llPrefix . 'Seo'), + 'bad' => $this->getLanguageService()->sL($llPrefix . 'Bad'), + 'ok' => $this->getLanguageService()->sL($llPrefix . 'Ok'), + 'good' => $this->getLanguageService()->sL($llPrefix . 'Good') ]; } diff --git a/Classes/Service/PreviewService.php b/Classes/Service/PreviewService.php index 57336174..c45e83aa 100644 --- a/Classes/Service/PreviewService.php +++ b/Classes/Service/PreviewService.php @@ -5,6 +5,7 @@ namespace YoastSeoForTypo3\YoastSeo\Service; use GuzzleHttp\Exception\RequestException; +use JsonException; use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -13,10 +14,9 @@ class PreviewService { protected int $pageId = 0; - protected array $config = []; protected ContentObjectRenderer $cObj; - public function getPreviewData(string $uriToCheck, int $pageId) + public function getPreviewData(string $uriToCheck, int $pageId): string { $this->pageId = $pageId; @@ -33,7 +33,12 @@ public function getPreviewData(string $uriToCheck, int $pageId) ] ]; } - return json_encode($data); + + try { + return (string)json_encode($data, JSON_THROW_ON_ERROR); + } catch (JsonException) { + return ''; + } } protected function getContentFromUrl(string $uriToCheck): ?string @@ -65,8 +70,15 @@ protected function getContentFromUrl(string $uriToCheck): ?string throw new Exception((string)$response->getStatusCode()); } + /** + * @return array + */ protected function getDataFromContent(?string $content, string $uriToCheck): array { + if ($content === null) { + return []; + } + $title = $body = $metaDescription = ''; $locale = 'en'; @@ -104,11 +116,11 @@ protected function getDataFromContent(?string $content, string $uriToCheck): arr if ($localeFound) { [$locale] = explode('-', trim($matchesLocale[1])); } - $urlParts = parse_url(preg_replace('/\/$/', '', $uriToCheck)); + $urlParts = parse_url((string)preg_replace('/\/$/', '', $uriToCheck)); if ($urlParts['port'] ?? false) { - $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . ':' . $urlParts['port']; + $baseUrl = (isset($urlParts['scheme']) ? $urlParts['scheme'] . ':' : '') . '//' . ($urlParts['host'] ?? '') . ':' . $urlParts['port']; } else { - $baseUrl = $urlParts['scheme'] . '://' . $urlParts['host']; + $baseUrl = (isset($urlParts['scheme']) ? $urlParts['scheme'] . ':' : '') . '//' . ($urlParts['host'] ?? ''); } $url = $baseUrl . ($urlParts['path'] ?? ''); @@ -130,29 +142,26 @@ protected function getDataFromContent(?string $content, string $uriToCheck): arr $body = $this->prepareBody($body); - if ($content !== null) { - return [ - 'id' => $this->pageId, - 'url' => $url, - 'baseUrl' => $baseUrl, - 'slug' => '/', - 'title' => strip_tags(html_entity_decode($title)), - 'description' => strip_tags(html_entity_decode($metaDescription)), - 'locale' => $locale, - 'body' => $body, - 'faviconSrc' => $faviconSrc, - 'pageTitlePrepend' => $titlePrepend, - 'pageTitleAppend' => $titleAppend, - ]; - } - return []; + return [ + 'id' => $this->pageId, + 'url' => $url, + 'baseUrl' => $baseUrl, + 'slug' => '/', + 'title' => strip_tags(html_entity_decode($title)), + 'description' => strip_tags(html_entity_decode($metaDescription)), + 'locale' => $locale, + 'body' => $body, + 'faviconSrc' => $faviconSrc, + 'pageTitlePrepend' => $titlePrepend, + 'pageTitleAppend' => $titleAppend, + ]; } protected function prepareBody(string $body): string { $body = $this->stripTagsContent($body, ''; } + /** + * @return array> + */ public function getStructuredData(): array { $structuredData = []; foreach ($this->getOrderedStructuredDataProviders() as $provider => $configuration) { $cacheIdentifier = $this->getTypoScriptFrontendController()->newHash . '-structured-data-' . $provider; - if ($this->pageCache instanceof FrontendInterface && $data = $this->pageCache->get($cacheIdentifier)) { - if (!empty($data)) { + if ($this->pageCache instanceof FrontendInterface) { + $data = $this->pageCache->get($cacheIdentifier); + if ($data !== false) { $structuredData[$provider] = $data; + continue; } - continue; } $structuredDataProviderObject = $this->getStructuredDataProviderObject($configuration); if ($structuredDataProviderObject === null) { @@ -71,7 +81,7 @@ public function getStructuredData(): array $this->pageCache->set( $cacheIdentifier, $data, - ['pageId_' . $this->getTypoScriptFrontendController()->page['uid']], + ['pageId_' . ($this->getTypoScriptFrontendController()->page['uid'] ?? $this->getTypoScriptFrontendController()->id)], // TODO: Fix this call, protected method since v13 //$this->getTypoScriptFrontendController()->get_cache_timeout() ); @@ -85,6 +95,9 @@ public function getStructuredData(): array return $structuredData; } + /** + * @param array $configuration + */ protected function getStructuredDataProviderObject(array $configuration): StructuredDataProviderInterface|null { if (!class_exists($configuration['provider']) || !is_subclass_of($configuration['provider'], StructuredDataProviderInterface::class)) { @@ -103,6 +116,9 @@ private function getTypoScriptFrontendController(): TypoScriptFrontendController return $GLOBALS['TSFE']; } + /** + * @return array> + */ private function getOrderedStructuredDataProviders(): array { $structuredDataProviders = $this->getStructuredDataProviderConfiguration(); @@ -112,6 +128,9 @@ private function getOrderedStructuredDataProviders(): array ->orderByDependencies($structuredDataProviders); } + /** + * @return array> + */ private function getStructuredDataProviderConfiguration(): array { $typoscriptService = GeneralUtility::makeInstance(TypoScriptService::class); @@ -122,6 +141,10 @@ private function getStructuredDataProviderConfiguration(): array return $config['structuredData']['providers'] ?? []; } + /** + * @param array> $orderInformation + * @return array> + */ protected function setProviderOrder(array $orderInformation): array { foreach ($orderInformation as $provider => &$configuration) { @@ -135,6 +158,10 @@ protected function setProviderOrder(array $orderInformation): array return $orderInformation; } + /** + * @param array $configuration + * @return string[] + */ private function getOrderConfiguration(string $provider, array $configuration, string $key): array { if (is_string($configuration[$key])) { diff --git a/Classes/Traits/BackendUserTrait.php b/Classes/Traits/BackendUserTrait.php new file mode 100644 index 00000000..6c6505a3 --- /dev/null +++ b/Classes/Traits/BackendUserTrait.php @@ -0,0 +1,15 @@ +> + */ public static function getFormEngineNodes(): array { return [ @@ -22,13 +24,15 @@ public static function getFormEngineNodes(): array ]; } + /** + * @return array + */ public static function getDefaultConfiguration(): array { - $llBackendOverview = 'LLL:EXT:yoast_seo/Resources/Private/Language/BackendModuleOverview.xlf'; return [ 'allowedDoktypes' => [ 'page' => 1, - 'backend_section' => 5 + 'backend_section' => 5, ], 'translations' => [ 'availableLocales' => [ @@ -83,7 +87,7 @@ public static function getDefaultConfiguration(): array 'vi', 'zh_CN', 'zh_HK', - 'zh_TW' + 'zh_TW', ], 'languageKeyToLocaleMapping' => [ 'bg' => 'bg_BG', @@ -102,46 +106,20 @@ public static function getDefaultConfiguration(): array 'ru' => 'ru_RU', 'sk' => 'sk_SK', 'sv' => 'sv_SE', - 'tr' => 'tr_TR' - ] + 'tr' => 'tr_TR', + ], ], 'previewSettings' => [ 'basicAuth' => [ 'username' => '', 'password' => '', - ] - ], - 'overview_filters' => [ - '10' => [ - 'key' => 'cornerstone', - 'label' => $llBackendOverview . ':cornerstoneContent', - 'description' => $llBackendOverview . ':cornerstoneContent.description', - 'link' => 'https://yoa.st/typo3-cornerstone-content', - 'dataProvider' => DataProviders\CornerstoneOverviewDataProvider::class . '->process', - 'countProvider' => DataProviders\CornerstoneOverviewDataProvider::class . '->numberOfItems' ], - '20' => [ - 'key' => 'withoutDescription', - 'label' => $llBackendOverview . ':withoutDescription', - 'description' => $llBackendOverview . ':withoutDescription.description', - 'link' => 'https://yoa.st/typo3-meta-description', - 'dataProvider' => DataProviders\PagesWithoutDescriptionOverviewDataProvider::class . '->process', - 'countProvider' => DataProviders\PagesWithoutDescriptionOverviewDataProvider::class . '->numberOfItems' - ], - '30' => [ - 'key' => 'orphaned', - 'label' => $llBackendOverview . ':orphanedContent', - 'description' => $llBackendOverview . ':orphanedContent.description', - 'link' => 'https://yoa.st/1ja', - 'dataProvider' => DataProviders\OrphanedContentDataProvider::class . '->process', - 'countProvider' => DataProviders\OrphanedContentDataProvider::class . '->numberOfItems' - ] ], 'recordMetaTags' => [ 'description' => Generator\DescriptionGenerator::class, 'opengraph' => Generator\OpenGraphGenerator::class, - 'twitter' => Generator\TwitterGenerator::class - ] + 'twitter' => Generator\TwitterGenerator::class, + ], ]; } } diff --git a/Classes/Utility/JsonConfigUtility.php b/Classes/Utility/JsonConfigUtility.php index 5dbd1097..861338f7 100644 --- a/Classes/Utility/JsonConfigUtility.php +++ b/Classes/Utility/JsonConfigUtility.php @@ -9,8 +9,12 @@ class JsonConfigUtility implements SingletonInterface { + /** @var array */ protected array $config = []; + /** + * @param array $config + */ public function addConfig(array $config): void { ArrayUtility::mergeRecursiveWithOverrule($this->config, $config, true, false); diff --git a/Classes/Utility/PageAccessUtility.php b/Classes/Utility/PageAccessUtility.php index e77fa07f..3841fa91 100644 --- a/Classes/Utility/PageAccessUtility.php +++ b/Classes/Utility/PageAccessUtility.php @@ -14,8 +14,12 @@ class PageAccessUtility { + /** @var int[] */ protected static array $cache = []; + /** + * @return int[] + */ public static function getPageIds(int $pid): array { if (self::$cache !== []) { @@ -33,12 +37,6 @@ public static function getPageIds(int $pid): array /** * Copied from EXT:core, removed in v12 - * - * @param int $id - * @param int $depth - * @param int $begin - * @param string $permClause - * @return string */ protected static function getTreeList(int $id, int $depth, int $begin = 0, string $permClause = ''): string { diff --git a/Classes/Utility/YoastRequestHash.php b/Classes/Utility/YoastRequestHash.php index 294c6748..486d7544 100644 --- a/Classes/Utility/YoastRequestHash.php +++ b/Classes/Utility/YoastRequestHash.php @@ -8,6 +8,9 @@ class YoastRequestHash { + /** + * @param array $serverParams + */ public static function isValid(array $serverParams): bool { return isset($serverParams['HTTP_X_YOAST_PAGE_REQUEST']) diff --git a/Classes/Utility/YoastUtility.php b/Classes/Utility/YoastUtility.php index bb4377db..97a7edae 100644 --- a/Classes/Utility/YoastUtility.php +++ b/Classes/Utility/YoastUtility.php @@ -15,7 +15,11 @@ class YoastUtility { protected const COLUMN_NAME_FOCUSKEYWORD = 'tx_yoastseo_focuskeyword'; - public static function getAllowedDoktypes(?array $configuration = null, bool $returnInString = false) + /** + * @param array|null $configuration + * @return int[] + */ + public static function getAllowedDoktypes(?array $configuration = null): array { $allowedDoktypes = array_map(function ($doktype) { return (int)$doktype; @@ -31,16 +35,22 @@ public static function getAllowedDoktypes(?array $configuration = null, bool $re } } - $allowedDoktypes = $allowedDoktypes ?: [1]; - - if ($returnInString) { - return implode(',', $allowedDoktypes); - } + return $allowedDoktypes ?: [1]; + } - return $allowedDoktypes; + /** + * @param array|null $configuration + */ + public static function getAllowedDoktypesList(?array $configuration = null): string + { + return implode(',', self::getAllowedDoktypes($configuration)); } - public static function snippetPreviewEnabled(int $pageId, array $pageRecord, $pageTs = null): bool + /** + * @param array $pageRecord + * @param array $pageTs + */ + public static function snippetPreviewEnabled(int $pageId, array $pageRecord, ?array $pageTs = null): bool { if (!$GLOBALS['BE_USER'] instanceof BackendUserAuthentication || !$GLOBALS['BE_USER']->check('non_exclude_fields', 'pages:tx_yoastseo_snippetpreview')) { @@ -78,6 +88,9 @@ public static function getFocusKeywordOfRecord(int $uid, string $table = 'pages' return $focusKeyword; } + /** + * @return array + */ public static function getRelatedKeyphrases(string $parentTable, int $parentId): array { $config = []; @@ -109,10 +122,10 @@ public static function getRelatedKeyphrases(string $parentTable, int $parentId): * * You can set development by using TypoScript "module.tx_yoastseo.settings.developmentMode = 1" * - * @param array|null $configuration + * @param array|null $configuration * @return bool */ - public static function inProductionMode(array $configuration = null): bool + public static function inProductionMode(?array $configuration = null): bool { if ($configuration === null) { $configuration = self::getTypoScriptConfiguration(); @@ -121,6 +134,9 @@ public static function inProductionMode(array $configuration = null): bool return !((int)($_ENV['YOAST_DEVELOPMENT_MODE'] ?? 0) === 1 || (int)($configuration['developmentMode'] ?? 0) === 1); } + /** + * @return array + */ protected static function getTypoScriptConfiguration(): array { $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); diff --git a/Classes/ViewHelpers/CrawlerProgressViewHelper.php b/Classes/ViewHelpers/CrawlerProgressViewHelper.php index 7ff3c14e..22aae60d 100644 --- a/Classes/ViewHelpers/CrawlerProgressViewHelper.php +++ b/Classes/ViewHelpers/CrawlerProgressViewHelper.php @@ -19,6 +19,9 @@ public function initializeArguments(): void $this->registerArgument('language', 'integer', 'Language id', true); } + /** + * @return array{percentage: int|float, offset: int, total: int}|null + */ public function render(): ?array { $progressInformation = $this->crawlerService->getProgressInformation( diff --git a/Classes/ViewHelpers/ModuleLayout/MetaInformationViewHelper.php b/Classes/ViewHelpers/ModuleLayout/MetaInformationViewHelper.php index 45ccd777..6bb67bd0 100644 --- a/Classes/ViewHelpers/ModuleLayout/MetaInformationViewHelper.php +++ b/Classes/ViewHelpers/ModuleLayout/MetaInformationViewHelper.php @@ -14,16 +14,19 @@ class MetaInformationViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; - public function initializeArguments() + public function initializeArguments(): void { $this->registerArgument('pageInformation', 'array', 'Page information', true); } + /** + * @param array $arguments + */ public static function renderStatic( array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext - ) { + ): void { $viewHelperVariableContainer = $renderingContext->getViewHelperVariableContainer(); /** @var \TYPO3\CMS\Backend\Template\ModuleTemplate $moduleTemplate */ diff --git a/Classes/ViewHelpers/RecordIconViewHelper.php b/Classes/ViewHelpers/RecordIconViewHelper.php index 13d7e242..80d00db7 100644 --- a/Classes/ViewHelpers/RecordIconViewHelper.php +++ b/Classes/ViewHelpers/RecordIconViewHelper.php @@ -21,6 +21,9 @@ public function initializeArguments(): void $this->registerArgument('size', 'string', '', false, Icon::SIZE_DEFAULT); } + /** + * @param array $arguments + */ public static function renderStatic( array $arguments, \Closure $renderChildrenClosure, diff --git a/Classes/ViewHelpers/RecordLinksViewHelper.php b/Classes/ViewHelpers/RecordLinksViewHelper.php index dd5e1d25..7ee676b5 100644 --- a/Classes/ViewHelpers/RecordLinksViewHelper.php +++ b/Classes/ViewHelpers/RecordLinksViewHelper.php @@ -19,6 +19,9 @@ public function initializeArguments(): void $this->registerArgument('module', 'string', '', true, ''); } + /** + * @param array $arguments + */ public static function renderStatic( array $arguments, \Closure $renderChildrenClosure, diff --git a/Classes/Widgets/AbstractPageOverviewWidget.php b/Classes/Widgets/AbstractPageOverviewWidget.php index bc7449e1..f7fed3e9 100644 --- a/Classes/Widgets/AbstractPageOverviewWidget.php +++ b/Classes/Widgets/AbstractPageOverviewWidget.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\View\BackendViewFactory; +use TYPO3\CMS\Core\View\ViewInterface; use TYPO3\CMS\Dashboard\Widgets\RequestAwareWidgetInterface; use TYPO3\CMS\Dashboard\Widgets\WidgetConfigurationInterface; use TYPO3\CMS\Dashboard\Widgets\WidgetInterface; @@ -15,9 +16,13 @@ if (interface_exists(RequestAwareWidgetInterface::class)) { abstract class AbstractPageOverviewWidget implements WidgetInterface, RequestAwareWidgetInterface { - protected ?ServerRequestInterface $request = null; + protected ServerRequestInterface $request; + /** @var array|string[] */ protected array $options; + /** + * @param array|string[] $options + */ public function __construct( protected WidgetConfigurationInterface $configuration, protected PageProviderInterface $dataProvider, @@ -44,13 +49,17 @@ public function renderWidgetContent(): string return $view->render($this->options['template']); } - abstract protected function assignToView($view): void; + abstract protected function assignToView(ViewInterface|StandaloneView $view): void; } } else { abstract class AbstractPageOverviewWidget implements WidgetInterface { + /** @var array|string[] */ protected array $options; + /** + * @param array|string[] $options + */ public function __construct( protected WidgetConfigurationInterface $configuration, protected PageProviderInterface $dataProvider, @@ -72,6 +81,6 @@ public function renderWidgetContent(): string return $this->view->render(); } - abstract protected function assignToView($view): void; + abstract protected function assignToView(ViewInterface|StandaloneView $view): void; } } diff --git a/Classes/Widgets/PageOverviewWidget.php b/Classes/Widgets/PageOverviewWidget.php index 625a53eb..9fafabaa 100644 --- a/Classes/Widgets/PageOverviewWidget.php +++ b/Classes/Widgets/PageOverviewWidget.php @@ -4,16 +4,23 @@ namespace YoastSeoForTypo3\YoastSeo\Widgets; -use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\View\ViewInterface; +use TYPO3\CMS\Fluid\View\StandaloneView; +use YoastSeoForTypo3\YoastSeo\Traits\BackendUserTrait; class PageOverviewWidget extends AbstractPageOverviewWidget { + use BackendUserTrait; + + /** + * @return array|string[] + */ public function getOptions(): array { return []; } - protected function assignToView($view): void + protected function assignToView(ViewInterface|StandaloneView $view): void { $view->assignMultiple([ 'pages' => $this->dataProvider->getPages(), @@ -24,9 +31,4 @@ protected function assignToView($view): void 'timeFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], ]); } - - protected function getBackendUser(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } } diff --git a/Classes/Widgets/Provider/OrphanedContentDataProvider.php b/Classes/Widgets/Provider/OrphanedContentDataProvider.php index 0fc8fea0..a0e95ae2 100644 --- a/Classes/Widgets/Provider/OrphanedContentDataProvider.php +++ b/Classes/Widgets/Provider/OrphanedContentDataProvider.php @@ -4,23 +4,27 @@ namespace YoastSeoForTypo3\YoastSeo\Widgets\Provider; -use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Traits\BackendUserTrait; class OrphanedContentDataProvider implements PageProviderInterface { - private array $excludedDoktypes; - private int $limit; + use BackendUserTrait; - public function __construct(array $excludedDoktypes, int $limit) - { - $this->excludedDoktypes = $excludedDoktypes; + public function __construct( + /** @var int[] */ + private array $excludedDoktypes, + private int $limit + ) { $this->limit = $limit ?: 5; } + /** + * @return array> + */ public function getPages(): array { $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex'); @@ -88,9 +92,4 @@ public function getPages(): array return $items; } - - protected function getBackendUser(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } } diff --git a/Classes/Widgets/Provider/PageProviderInterface.php b/Classes/Widgets/Provider/PageProviderInterface.php index 70788fc3..6469d1d0 100644 --- a/Classes/Widgets/Provider/PageProviderInterface.php +++ b/Classes/Widgets/Provider/PageProviderInterface.php @@ -6,5 +6,8 @@ interface PageProviderInterface { + /** + * @return array> + */ public function getPages(): array; } diff --git a/Classes/Widgets/Provider/PagesWithoutDescriptionDataProvider.php b/Classes/Widgets/Provider/PagesWithoutDescriptionDataProvider.php index 19d404a3..24dfc07e 100644 --- a/Classes/Widgets/Provider/PagesWithoutDescriptionDataProvider.php +++ b/Classes/Widgets/Provider/PagesWithoutDescriptionDataProvider.php @@ -8,21 +8,26 @@ use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; +use YoastSeoForTypo3\YoastSeo\Traits\BackendUserTrait; /** * @deprecated Will be removed once TYPO3 v11 support is dropped */ class PagesWithoutDescriptionDataProvider implements PageProviderInterface { - private array $excludedDoktypes; - private int $limit; + use BackendUserTrait; - public function __construct(array $excludedDoktypes, int $limit) - { - $this->excludedDoktypes = $excludedDoktypes; + public function __construct( + /** @var int[] */ + private array $excludedDoktypes, + private int $limit + ) { $this->limit = $limit ?: 5; } + /** + * @return array> + */ public function getPages(): array { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); @@ -67,9 +72,4 @@ public function getPages(): array } return $items; } - - protected function getBackendUser(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index c71de403..3471e743 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -19,6 +19,13 @@ services: YoastSeoForTypo3\YoastSeo\Controller\AjaxController: public: true + YoastSeoForTypo3\YoastSeo\Controller\OverviewController: + arguments: + $filters: + cornerstore: '@YoastSeoForTypo3\YoastSeo\DataProviders\CornerstoneOverviewDataProvider' + withoutDescription: '@YoastSeoForTypo3\YoastSeo\DataProviders\PagesWithoutDescriptionOverviewDataProvider' + orphaned: '@YoastSeoForTypo3\YoastSeo\DataProviders\OrphanedContentDataProvider' + YoastSeoForTypo3\YoastSeo\Service\CrawlerService: public: true arguments: @@ -57,12 +64,13 @@ services: method: 'setCanonical' YoastSeoForTypo3\YoastSeo\PageTitle\RecordPageTitleProvider: - arguments: - - '@YoastSeoForTypo3\YoastSeo\Record\RecordService' + public: true YoastSeoForTypo3\YoastSeo\MetaTag\RecordMetaTagGenerator: - arguments: - - '@YoastSeoForTypo3\YoastSeo\Record\RecordService' + public: true + + YoastSeoForTypo3\YoastSeo\MetaTag\AdvancedRobotsGenerator: + public: true YoastSeoForTypo3\YoastSeo\Backend\ModifyPageLayoutContentListener: tags: diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 4db397cc..1815a2d2 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -7,7 +7,7 @@ GeneralUtility::makeInstance(TcaService::class) ->addYoastFields( 'pages', - YoastUtility::getAllowedDoktypes(null, true) + YoastUtility::getAllowedDoktypesList() ); // Remove description from metatags tab diff --git a/Configuration/TCA/tx_yoastseo_related_focuskeyword.php b/Configuration/TCA/tx_yoastseo_related_focuskeyword.php index 4b2da38a..00f0b264 100644 --- a/Configuration/TCA/tx_yoastseo_related_focuskeyword.php +++ b/Configuration/TCA/tx_yoastseo_related_focuskeyword.php @@ -1,8 +1,11 @@ [ 'title' => $llPrefix . 'tx_yoastseo_related_focuskeyword.title', 'label' => 'keyword', @@ -25,16 +28,6 @@ ], ], 'columns' => [ - 'sys_language_uid' => $GLOBALS['TCA']['tt_content']['columns']['sys_language_uid'], - 'l10n_parent' => array_replace_recursive($GLOBALS['TCA']['tt_content']['columns']['l18n_parent'], [ - 'config' => [ - 'foreign_table' => 'tx_yoastseo_related_focuskeyword', - 'foreign_table_where' => 'AND tx_yoastseo_related_focuskeyword.pid=###CURRENT_PID### AND tx_yoastseo_related_focuskeyword.sys_language_uid IN (-1,0)', - ] - ]), - 'l10n_source' => $GLOBALS['TCA']['tt_content']['columns']['l10n_source'], - 'l10n_diffsource' => $GLOBALS['TCA']['tt_content']['columns']['l18n_diffsource'], - 'hidden' => $GLOBALS['TCA']['tt_content']['columns']['hidden'], 'keyword' => [ 'exclude' => 1, 'l10n_mode' => 'prefixLangTitle', @@ -92,3 +85,23 @@ ] ], ]; + +if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) { + $focusKeywordTca['columns'] = array_merge( + $focusKeywordTca['columns'], + [ + 'sys_language_uid' => $GLOBALS['TCA']['tt_content']['columns']['sys_language_uid'], + 'l10n_parent' => array_replace_recursive($GLOBALS['TCA']['tt_content']['columns']['l18n_parent'], [ + 'config' => [ + 'foreign_table' => 'tx_yoastseo_related_focuskeyword', + 'foreign_table_where' => 'AND tx_yoastseo_related_focuskeyword.pid=###CURRENT_PID### AND tx_yoastseo_related_focuskeyword.sys_language_uid IN (-1,0)', + ] + ]), + 'l10n_source' => $GLOBALS['TCA']['tt_content']['columns']['l10n_source'], + 'l10n_diffsource' => $GLOBALS['TCA']['tt_content']['columns']['l18n_diffsource'], + 'hidden' => $GLOBALS['TCA']['tt_content']['columns']['hidden'], + ] + ); +} + +return $focusKeywordTca; diff --git a/Configuration/page.tsconfig b/Configuration/page.tsconfig new file mode 100644 index 00000000..15795645 --- /dev/null +++ b/Configuration/page.tsconfig @@ -0,0 +1,3 @@ +templates.yoast-seo-for-typo3/yoast_seo { + 10 = yoast-seo-for-typo3/yoast_seo:Resources/Private +} diff --git a/Resources/Private/Partials/Overview/View.html b/Resources/Private/Partials/Overview/View.html index 2c64daa2..4bd6fbf0 100644 --- a/Resources/Private/Partials/Overview/View.html +++ b/Resources/Private/Partials/Overview/View.html @@ -13,11 +13,11 @@
-

- {subtitle}

+

-

- - ({filter.numberOfItems}) + + ({filter.numberOfItems})
@@ -27,9 +27,9 @@

- - -

+ + +

diff --git a/Resources/Private/Templates/Dashboard/Legacy.html b/Resources/Private/Templates/Dashboard/Legacy.html deleted file mode 100644 index ceb644c5..00000000 --- a/Resources/Private/Templates/Dashboard/Legacy.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/Resources/Private/Templates/Overview/Legacy.html b/Resources/Private/Templates/Overview/Legacy.html deleted file mode 100644 index 5a20495e..00000000 --- a/Resources/Private/Templates/Overview/Legacy.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Resources/Private/Templates/Widget/OrphanedContentWidget.html b/Resources/Private/Templates/Widget/OrphanedContentWidget.html index d72a07a5..c49bd742 100644 --- a/Resources/Private/Templates/Widget/OrphanedContentWidget.html +++ b/Resources/Private/Templates/Widget/OrphanedContentWidget.html @@ -7,7 +7,7 @@
- +
diff --git a/Resources/Private/Templates/Widget/PageWithoutMetaDescriptionWidget.html b/Resources/Private/Templates/Widget/PageWithoutMetaDescriptionWidget.html index d72a07a5..c49bd742 100644 --- a/Resources/Private/Templates/Widget/PageWithoutMetaDescriptionWidget.html +++ b/Resources/Private/Templates/Widget/PageWithoutMetaDescriptionWidget.html @@ -7,7 +7,7 @@
-
+