From 5a8d5a8aa3eb059485905c0b68df21298112eda1 Mon Sep 17 00:00:00 2001 From: Benoit Chenu Date: Sun, 28 Jan 2024 01:59:02 +0100 Subject: [PATCH] [RELEASE] Version 11.0.0 --- Classes/Configuration/TcaConfiguration.php | 101 ++++++++ Classes/Controller/ReportController.php | 218 ++++++++++++++++++ Classes/Domain/Model/Content.php | 70 ++++++ Classes/Domain/Model/Ctype.php | 149 ++++++++++++ Classes/Domain/Model/Doktype.php | 128 ++++++++++ Classes/Domain/Model/Page.php | 58 +++++ Classes/Domain/Model/Plugin.php | 149 ++++++++++++ .../Domain/Repository/AbstractRepository.php | 68 ++++++ .../Domain/Repository/ContentRepository.php | 176 ++++++++++++++ Classes/Domain/Repository/PageRepository.php | 94 ++++++++ Classes/ViewHelpers/Be/LinkViewHelper.php | 77 +++++++ Configuration/Extbase/Persistence/Classes.php | 15 ++ Configuration/Icons.php | 15 ++ Configuration/Services.yaml | 10 + README.md | 32 +++ Resources/Private/Language/backend.xlf | 17 ++ Resources/Private/Layouts/Module.html | 8 + Resources/Private/Partials/BackButton.html | 11 + Resources/Private/Partials/Card/Header.html | 20 ++ Resources/Private/Templates/CtypeDetail.html | 66 ++++++ Resources/Private/Templates/Ctypes.html | 62 +++++ .../Private/Templates/DoktypeDetail.html | 63 +++++ Resources/Private/Templates/Doktypes.html | 62 +++++ .../Private/Templates/ListTypeDetail.html | 66 ++++++ Resources/Private/Templates/ListTypes.html | 62 +++++ Resources/Private/Templates/Main.html | 47 ++++ Resources/Public/Icons/Extension.png | Bin 0 -> 12336 bytes composer.json | 39 ++++ ext_tables.php | 22 ++ 29 files changed, 1905 insertions(+) create mode 100644 Classes/Configuration/TcaConfiguration.php create mode 100644 Classes/Controller/ReportController.php create mode 100644 Classes/Domain/Model/Content.php create mode 100644 Classes/Domain/Model/Ctype.php create mode 100644 Classes/Domain/Model/Doktype.php create mode 100644 Classes/Domain/Model/Page.php create mode 100644 Classes/Domain/Model/Plugin.php create mode 100644 Classes/Domain/Repository/AbstractRepository.php create mode 100644 Classes/Domain/Repository/ContentRepository.php create mode 100644 Classes/Domain/Repository/PageRepository.php create mode 100644 Classes/ViewHelpers/Be/LinkViewHelper.php create mode 100644 Configuration/Extbase/Persistence/Classes.php create mode 100644 Configuration/Icons.php create mode 100644 Configuration/Services.yaml create mode 100644 README.md create mode 100644 Resources/Private/Language/backend.xlf create mode 100644 Resources/Private/Layouts/Module.html create mode 100644 Resources/Private/Partials/BackButton.html create mode 100644 Resources/Private/Partials/Card/Header.html create mode 100644 Resources/Private/Templates/CtypeDetail.html create mode 100644 Resources/Private/Templates/Ctypes.html create mode 100644 Resources/Private/Templates/DoktypeDetail.html create mode 100644 Resources/Private/Templates/Doktypes.html create mode 100644 Resources/Private/Templates/ListTypeDetail.html create mode 100644 Resources/Private/Templates/ListTypes.html create mode 100644 Resources/Private/Templates/Main.html create mode 100644 Resources/Public/Icons/Extension.png create mode 100644 composer.json create mode 100644 ext_tables.php diff --git a/Classes/Configuration/TcaConfiguration.php b/Classes/Configuration/TcaConfiguration.php new file mode 100644 index 0000000..198f271 --- /dev/null +++ b/Classes/Configuration/TcaConfiguration.php @@ -0,0 +1,101 @@ +tca = $GLOBALS['TCA']; + } + + /** + * @return Doktype[] + */ + public function getDoktypes(): array + { + $doktypes = []; + + foreach ($this->tca['pages']['columns']['doktype']['config']['items'] as $doktypeItem) { + if ($doktypeItem[1] === '--div--') { + continue; + } + + $doktype = new Doktype(); + $doktype->setId((int)$doktypeItem[1]); + $doktype->setLabel($this->getTranslation($doktypeItem[0])); + $doktype->setIcon($doktypeItem[2] ?? ''); + + $doktypes[] = $doktype; + } + + return $doktypes; + } + + /** + * @return Ctype[] + */ + public function getCtypes(): array + { + $ctypes = []; + + foreach ($this->tca['tt_content']['columns']['CType']['config']['items'] as $ctypeItem) { + if ($ctypeItem[1] === '--div--') { + continue; + } + + $ctype = new Ctype(); + $ctype->setId($ctypeItem[1]); + $ctype->setLabel($this->getTranslation($ctypeItem[0])); + $ctype->setIcon($ctypeItem[2] ?? ''); + + $ctypes[] = $ctype; + } + + return $ctypes; + } + + /** + * @return Plugin[] + */ + public function getPlugins(): array + { + $plugins = []; + + foreach ($this->tca['tt_content']['columns']['list_type']['config']['items'] as $pluginItem) { + if ($pluginItem[1] === '') { + continue; + } + + $plugin = new Plugin(); + $plugin->setId($pluginItem[1]); + $plugin->setLabel($this->getTranslation($pluginItem[0])); + $plugin->setIcon($pluginItem[2] ?? ''); + + $plugins[] = $plugin; + } + + return $plugins; + } + + private function getTranslation(string $key): string + { + if (str_starts_with($key, 'LLL:')) { + return LocalizationUtility::translate($key); + } else { + return $key; + } + } +} diff --git a/Classes/Controller/ReportController.php b/Classes/Controller/ReportController.php new file mode 100644 index 0000000..d0a8216 --- /dev/null +++ b/Classes/Controller/ReportController.php @@ -0,0 +1,218 @@ +request = $request; + $this->moduleTemplate = $this->moduleTemplateFactory->create($request); + + + $action = $request->getQueryParams()['action'] ?? 'main'; + + $this->initializeView($action); + + switch ($action) { + case 'doktypes': + return $this->doktypesAction(); + break; + case 'ctypes': + return $this->ctypesAction(); + break; + case 'listTypes': + return $this->listTypesAction(); + break; + case 'doktypeDetail': + foreach ($this->tcaConfiguration->getDoktypes() as $doktype) { + if ($doktype->getId() === (int)$request->getQueryParams()['doktype']) { + return $this->doktypeDetailAction($doktype, $request->getQueryParams()['status']); + } + } + break; + case 'ctypeDetail': + foreach ($this->tcaConfiguration->getCtypes() as $ctype) { + if ($ctype->getId() === $request->getQueryParams()['ctype']) { + return $this->ctypeDetailAction($ctype, $request->getQueryParams()['status']); + } + } + break; + case 'listTypeDetail': + foreach ($this->tcaConfiguration->getPlugins() as $plugin) { + if ($plugin->getId() === $request->getQueryParams()['listType']) { + return $this->listTypeDetailAction($plugin, $request->getQueryParams()['status']); + } + } + break; + default: + return $this->mainAction(); + } + + // If we are here, there was a problem + return new RedirectResponse((string)$this->uriBuilder->buildUriFromRoute('tools_ContentUsage')); + } + + /** + * Sets up the Fluid View. + * + * @param string $templateName + */ + protected function initializeView(string $templateName): void + { + $this->view = GeneralUtility::makeInstance(StandaloneView::class); + $this->view->setTemplate($templateName); + $this->view->setTemplateRootPaths(['EXT:content_usage/Resources/Private/Templates']); + $this->view->setPartialRootPaths(['EXT:content_usage/Resources/Private/Partials']); + $this->view->setLayoutRootPaths(['EXT:content_usage/Resources/Private/Layouts']); + } + + public function mainAction(): ResponseInterface + { + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function doktypesAction(): ResponseInterface + { + $doktypes = $this->tcaConfiguration->getDoktypes(); + foreach ($doktypes as $doktype) { + $doktype->setTotalActivePages($this->pageRepository->countActiveByDoktype($doktype)); + $doktype->setTotalDisabledPages($this->pageRepository->countDisabledByDoktype($doktype)); + $doktype->setTotalDeletedPages($this->pageRepository->countDeletedByDoktype($doktype)); + } + + $this->view->assign('doktypes', $doktypes); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function ctypesAction(): ResponseInterface + { + $ctypes = $this->tcaConfiguration->getCtypes(); + foreach ($ctypes as $ctype) { + $ctype->setTotalActiveContents($this->contentRepository->countActiveByCtype($ctype)); + $ctype->setTotalDisabledContents($this->contentRepository->countDisabledByCtype($ctype)); + $ctype->setTotalDeletedContents($this->contentRepository->countDeletedByCtype($ctype)); + } + + $this->view->assign('ctypes', $ctypes); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function listTypesAction(): ResponseInterface + { + $plugins = $this->tcaConfiguration->getPlugins(); + foreach ($plugins as $plugin) { + $plugin->setTotalActiveContents($this->contentRepository->countActiveByPlugin($plugin)); + $plugin->setTotalDisabledContents($this->contentRepository->countDisabledByPlugin($plugin)); + $plugin->setTotalDeletedContents($this->contentRepository->countDeletedByPlugin($plugin)); + } + + $this->view->assign('plugins', $plugins); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function doktypeDetailAction(Doktype $doktype, string $status): ResponseInterface + { + match ($status) { + 'active' => $doktype->setActivePages($this->pageRepository->findActiveByDoktype($doktype)), + 'disabled' => $doktype->setDisabledPages($this->pageRepository->findDisabledByDoktype($doktype)), + 'deleted' => $doktype->setDeletedPages($this->pageRepository->findDeletedByDoktype($doktype)), + }; + + $this->view->assign('doktype', $doktype); + $this->view->assign('status', $status); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function ctypeDetailAction(Ctype $ctype, string $status): ResponseInterface + { + match ($status) { + 'active' => $ctype->setActiveContents($this->contentRepository->findActiveByCtype($ctype)), + 'disabled' => $ctype->setDisabledContents($this->contentRepository->findDisabledByCtype($ctype)), + 'deleted' => $ctype->setDeletedContents($this->contentRepository->findDeletedByCtype($ctype)), + }; + + $this->view->assign('ctype', $ctype); + $this->view->assign('status', $status); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + public function listTypeDetailAction(Plugin $plugin, string $status): ResponseInterface + { + match ($status) { + 'active' => $plugin->setActiveContents($this->contentRepository->findActiveByPlugin($plugin)), + 'disabled' => $plugin->setDisabledContents($this->contentRepository->findDisabledByPlugin($plugin)), + 'deleted' => $plugin->setDeletedContents($this->contentRepository->findDeletedByPlugin($plugin)), + }; + + $this->view->assign('plugin', $plugin); + $this->view->assign('status', $status); + + $this->moduleTemplate->setContent($this->view->render()); + + return new HtmlResponse($this->moduleTemplate->renderContent()); + } + + /** + * @return BackendUserAuthentication + */ + protected function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } +} diff --git a/Classes/Domain/Model/Content.php b/Classes/Domain/Model/Content.php new file mode 100644 index 0000000..93c7a2f --- /dev/null +++ b/Classes/Domain/Model/Content.php @@ -0,0 +1,70 @@ +header; + } + + public function setHeader(string $header): void + { + $this->header = $header; + } + + public function getCtype(): string + { + return $this->ctype; + } + + public function setCtype(string $ctype): void + { + $this->ctype = $ctype; + } + + public function getListType(): string + { + return $this->listType; + } + + public function setListType(string $listType): void + { + $this->listType = $listType; + } + + public function getSysLanguageUid(): int + { + return $this->sysLanguageUid; + } + + public function setSysLanguageUid(int $sysLanguageUid): void + { + $this->sysLanguageUid = $sysLanguageUid; + } + + public function getT3verWsid(): int + { + return $this->t3verWsid; + } + + public function setT3verWsid(int $t3verWsid): void + { + $this->t3verWsid = $t3verWsid; + } +} diff --git a/Classes/Domain/Model/Ctype.php b/Classes/Domain/Model/Ctype.php new file mode 100644 index 0000000..199e6bf --- /dev/null +++ b/Classes/Domain/Model/Ctype.php @@ -0,0 +1,149 @@ +id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getLabel(): string + { + return $this->label; + } + + public function setLabel(string $label): void + { + $this->label = $label; + } + + public function getIcon(): string + { + return $this->icon; + } + + public function setIcon(string $icon): void + { + $this->icon = $icon; + } + + public function getTotalActiveContents(): int + { + return $this->totalActiveContents; + } + + public function setTotalActiveContents(int $totalActiveContents): void + { + $this->totalActiveContents = $totalActiveContents; + } + + public function getTotalDisabledContents(): int + { + return $this->totalDisabledContents; + } + + public function setTotalDisabledContents(int $totalDisabledContents): void + { + $this->totalDisabledContents = $totalDisabledContents; + } + + public function getTotalDeletedContents(): int + { + return $this->totalDeletedContents; + } + + public function setTotalDeletedContents(int $totalDeletedContents): void + { + $this->totalDeletedContents = $totalDeletedContents; + } + + /** + * @return Content[] + */ + public function getActiveContents(): array + { + return $this->activeContents; + } + + /** + * @param Content[] $activeContents + * @return void + */ + public function setActiveContents(array $activeContents): void + { + $this->setTotalActiveContents(count($activeContents)); + $this->activeContents = $activeContents; + } + + /** + * @return Content[] + */ + public function getDisabledContents(): array + { + return $this->disabledContents; + } + + /** + * @param Content[] $disabledContents + * @return void + */ + public function setDisabledContents(array $disabledContents): void + { + $this->setTotalDisabledContents(count($disabledContents)); + $this->disabledContents = $disabledContents; + } + + /** + * @return Content[] + */ + public function getDeletedContents(): array + { + return $this->deletedContents; + } + + /** + * @param Content[] $deletedContents + * @return void + */ + public function setDeletedContents(array $deletedContents): void + { + $this->setTotalDeletedContents(count($deletedContents)); + $this->deletedContents = $deletedContents; + } +} diff --git a/Classes/Domain/Model/Doktype.php b/Classes/Domain/Model/Doktype.php new file mode 100644 index 0000000..2eb572f --- /dev/null +++ b/Classes/Domain/Model/Doktype.php @@ -0,0 +1,128 @@ +id; + } + + public function setId(int $id): void + { + $this->id = $id; + } + + public function getLabel(): string + { + return $this->label; + } + + public function setLabel(string $label): void + { + $this->label = $label; + } + + public function getIcon(): string + { + return $this->icon; + } + + public function setIcon(string $icon): void + { + $this->icon = $icon; + } + + public function getTotalActivePages(): int + { + return $this->totalActivePages; + } + + public function setTotalActivePages(int $totalActivePages): void + { + $this->totalActivePages = $totalActivePages; + } + + public function getTotalDisabledPages(): int + { + return $this->totalDisabledPages; + } + + public function setTotalDisabledPages(int $totalDisabledPages): void + { + $this->totalDisabledPages = $totalDisabledPages; + } + + public function getTotalDeletedPages(): int + { + return $this->totalDeletedPages; + } + + public function setTotalDeletedPages(int $totalDeletedPages): void + { + $this->totalDeletedPages = $totalDeletedPages; + } + + public function getActivePages(): array + { + return $this->activePages; + } + + public function setActivePages(array $activePages): void + { + $this->setTotalActivePages(count($activePages)); + $this->activePages = $activePages; + } + + public function getDisabledPages(): array + { + return $this->disabledPages; + } + + public function setDisabledPages(array $disabledPages): void + { + $this->setTotalDisabledPages(count($disabledPages)); + $this->disabledPages = $disabledPages; + } + + public function getDeletedPages(): array + { + return $this->deletedPages; + } + + public function setDeletedPages(array $deletedPages): void + { + $this->setTotalDeletedPages(count($deletedPages)); + $this->deletedPages = $deletedPages; + } +} diff --git a/Classes/Domain/Model/Page.php b/Classes/Domain/Model/Page.php new file mode 100644 index 0000000..1b6e40b --- /dev/null +++ b/Classes/Domain/Model/Page.php @@ -0,0 +1,58 @@ +title; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function getDoktype(): string + { + return $this->doktype; + } + + public function setDoktype(string $type): void + { + $this->doktype = $type; + } + + public function getSysLanguageUid(): int + { + return $this->sysLanguageUid; + } + + public function setSysLanguageUid(int $sysLanguageUid): void + { + $this->sysLanguageUid = $sysLanguageUid; + } + + public function getT3verWsid(): int + { + return $this->t3verWsid; + } + + public function setT3verWsid(int $t3verWsid): void + { + $this->t3verWsid = $t3verWsid; + } +} diff --git a/Classes/Domain/Model/Plugin.php b/Classes/Domain/Model/Plugin.php new file mode 100644 index 0000000..6ed58a8 --- /dev/null +++ b/Classes/Domain/Model/Plugin.php @@ -0,0 +1,149 @@ +id; + } + + public function setId(string $id): void + { + $this->id = $id; + } + + public function getLabel(): string + { + return $this->label; + } + + public function setLabel(string $label): void + { + $this->label = $label; + } + + public function getIcon(): string + { + return $this->icon; + } + + public function setIcon(string $icon): void + { + $this->icon = $icon; + } + + public function getTotalActiveContents(): int + { + return $this->totalActiveContents; + } + + public function setTotalActiveContents(int $totalActiveContents): void + { + $this->totalActiveContents = $totalActiveContents; + } + + public function getTotalDisabledContents(): int + { + return $this->totalDisabledContents; + } + + public function setTotalDisabledContents(int $totalDisabledContents): void + { + $this->totalDisabledContents = $totalDisabledContents; + } + + public function getTotalDeletedContents(): int + { + return $this->totalDeletedContents; + } + + public function setTotalDeletedContents(int $totalDeletedContents): void + { + $this->totalDeletedContents = $totalDeletedContents; + } + + /** + * @return Content[] + */ + public function getActiveContents(): array + { + return $this->activeContents; + } + + /** + * @param Content[] $activeContents + * @return void + */ + public function setActiveContents(array $activeContents): void + { + $this->setTotalActiveContents(count($activeContents)); + $this->activeContents = $activeContents; + } + + /** + * @return Content[] + */ + public function getDisabledContents(): array + { + return $this->disabledContents; + } + + /** + * @param Content[] $disabledContents + * @return void + */ + public function setDisabledContents(array $disabledContents): void + { + $this->setTotalDisabledContents(count($disabledContents)); + $this->disabledContents = $disabledContents; + } + + /** + * @return Content[] + */ + public function getDeletedContents(): array + { + return $this->deletedContents; + } + + /** + * @param Content[] $deletedContents + * @return void + */ + public function setDeletedContents(array $deletedContents): void + { + $this->setTotalDeletedContents(count($deletedContents)); + $this->deletedContents = $deletedContents; + } +} diff --git a/Classes/Domain/Repository/AbstractRepository.php b/Classes/Domain/Repository/AbstractRepository.php new file mode 100644 index 0000000..cf0e504 --- /dev/null +++ b/Classes/Domain/Repository/AbstractRepository.php @@ -0,0 +1,68 @@ +getQueryBuilderForTable($this->getTableName()); + $queryBuilder->getRestrictions()->removeAll(); + + $queryBuilder->from($this->getTableName()); + + match ($status) { + 'active' => $queryBuilder->where($this->getActiveConstraints($queryBuilder)), + 'disabled' => $queryBuilder->where($this->getDisabledConstraints($queryBuilder)), + 'deleted' => $queryBuilder->where($this->getDeletedConstraints($queryBuilder)), + }; + + return $queryBuilder; + } + + private function getActiveConstraints(QueryBuilder $queryBuilder): CompositeExpression|string + { + return $queryBuilder->expr()->and( + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->eq('hidden', 0), + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq('endtime', 0), + $queryBuilder->expr()->gt('endtime', time()), + ) + ); + } + + private function getDisabledConstraints(QueryBuilder $queryBuilder): CompositeExpression|string + { + return $queryBuilder->expr()->and( + $queryBuilder->expr()->eq('deleted', 0), + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq('hidden', 1), + $queryBuilder->expr()->and( + $queryBuilder->expr()->gt('endtime', 0), + $queryBuilder->expr()->lt('endtime', time()), + ) + ) + ); + } + + private function getDeletedConstraints(QueryBuilder $queryBuilder): CompositeExpression|string + { + return $queryBuilder->expr()->eq('deleted', 1); + } +} diff --git a/Classes/Domain/Repository/ContentRepository.php b/Classes/Domain/Repository/ContentRepository.php new file mode 100644 index 0000000..ba4fe37 --- /dev/null +++ b/Classes/Domain/Repository/ContentRepository.php @@ -0,0 +1,176 @@ +getQueryBuilder('active'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Ctype $ctype + * @return Content[] + */ + public function findActiveByCtype(Ctype $ctype): array + { + $queryBuilder = $this->getQueryBuilder('active'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDisabledByCtype(Ctype $ctype): int + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Ctype $ctype + * @return Content[] + */ + public function findDisabledByCtype(Ctype $ctype): array + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDeletedByCtype(Ctype $ctype): int + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Ctype $ctype + * @return Content[] + */ + public function findDeletedByCtype(Ctype $ctype): array + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForCtype($queryBuilder, $ctype); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countActiveByPlugin(Plugin $plugin): int + { + $queryBuilder = $this->getQueryBuilder('active'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Plugin $plugin + * @return Content[] + */ + public function findActiveByPlugin(Plugin $plugin): array + { + $queryBuilder = $this->getQueryBuilder('active'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDisabledByPlugin(Plugin $plugin): int + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Plugin $plugin + * @return Content[] + */ + public function findDisabledByPlugin(Plugin $plugin): array + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDeletedByPlugin(Plugin $plugin): int + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Plugin $plugin + * @return Content[] + */ + public function findDeletedByPlugin(Plugin $plugin): array + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForPlugin($queryBuilder, $plugin); + $queryBuilder->select('uid', 'pid', 'header', 'ctype', 'list_type'); + + return $this->dataMapper->map(Content::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + private function addConstraintsForCtype(QueryBuilder $queryBuilder, Ctype $ctype): void + { + $queryBuilder->andWhere( + $queryBuilder->expr()->eq( + 'Ctype', + $queryBuilder->createNamedParameter($ctype->getId()) + ) + ); + } + + private function addConstraintsForPlugin(QueryBuilder $queryBuilder, Plugin $plugin): void + { + $queryBuilder->andWhere( + $queryBuilder->expr()->and( + $queryBuilder->expr()->eq( + 'Ctype', + $queryBuilder->createNamedParameter('list') + ), + $queryBuilder->expr()->eq( + 'list_type', + $queryBuilder->createNamedParameter($plugin->getId()) + ) + ) + ); + } +} diff --git a/Classes/Domain/Repository/PageRepository.php b/Classes/Domain/Repository/PageRepository.php new file mode 100644 index 0000000..7ae1662 --- /dev/null +++ b/Classes/Domain/Repository/PageRepository.php @@ -0,0 +1,94 @@ +getQueryBuilder('active'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Doktype $doktype + * @return Page[] + */ + public function findActiveByDoktype(Doktype $doktype): array + { + $queryBuilder = $this->getQueryBuilder('active'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->select('uid', 'title', 'doktype'); + + return $this->dataMapper->map(Page::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDisabledByDoktype(Doktype $doktype): int + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Doktype $doktype + * @return Page[] + */ + public function findDisabledByDoktype(Doktype $doktype): array + { + $queryBuilder = $this->getQueryBuilder('disabled'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->select('uid', 'title', 'doktype'); + + return $this->dataMapper->map(Page::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + public function countDeletedByDoktype(Doktype $doktype): int + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->selectLiteral('count(*)'); + + return (int)$queryBuilder->executeQuery()->fetchNumeric()[0]; + } + + /** + * @param Doktype $doktype + * @return Page[] + */ + public function findDeletedByDoktype(Doktype $doktype): array + { + $queryBuilder = $this->getQueryBuilder('deleted'); + $this->addConstraintsForDoktype($queryBuilder, $doktype); + $queryBuilder->select('uid', 'title', 'doktype'); + + return $this->dataMapper->map(Page::class, $queryBuilder->executeQuery()->fetchAllAssociative()); + } + + private function addConstraintsForDoktype(QueryBuilder $queryBuilder, Doktype $doktype): void + { + $queryBuilder->andWhere( + $queryBuilder->expr()->eq( + 'doktype', + $queryBuilder->createNamedParameter($doktype->getId(), Connection::PARAM_INT) + ) + ); + } +} diff --git a/Classes/ViewHelpers/Be/LinkViewHelper.php b/Classes/ViewHelpers/Be/LinkViewHelper.php new file mode 100644 index 0000000..fd37e64 --- /dev/null +++ b/Classes/ViewHelpers/Be/LinkViewHelper.php @@ -0,0 +1,77 @@ +Go to web_ts + * + * ``Go to web_ts`` + */ +final class LinkViewHelper extends AbstractTagBasedViewHelper +{ + /** + * @var string + */ + protected $tagName = 'a'; + + public function initializeArguments(): void + { + parent::initializeArguments(); + $this->registerArgument('route', 'string', 'The name of the route', true); + $this->registerArgument('parameters', 'array', 'An array of parameters', false, []); + $this->registerArgument('referenceType', 'string', 'The type of reference to be generated (one of the constants)', false, UriBuilder::ABSOLUTE_PATH); + $this->registerArgument('anchor', 'string', 'Specifies the anchor', false, ''); + $this->registerTagAttribute('name', 'string', 'Specifies the name of an anchor'); + $this->registerTagAttribute( + 'rel', + 'string', + 'Specifies the relationship between the current document and the linked document' + ); + $this->registerTagAttribute( + 'rev', + 'string', + 'Specifies the relationship between the linked document and the current document' + ); + $this->registerTagAttribute('target', 'string', 'Specifies where to open the linked document'); + $this->registerUniversalTagAttributes(); + } + + public function render(): string + { + $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); + $route = $this->arguments['route']; + $parameters = $this->arguments['parameters']; + $referenceType = $this->arguments['referenceType']; + $anchor = $this->arguments['anchor']; + $uri = $uriBuilder->buildUriFromRoute($route, $parameters, $referenceType); + + if ($anchor) { + $uri .= '#' . $anchor; + } + + $this->tag->addAttribute('href', (string)$uri); + $this->tag->setContent((string)$this->renderChildren()); + $this->tag->forceClosingTag(true); + + return $this->tag->render(); + } +} diff --git a/Configuration/Extbase/Persistence/Classes.php b/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 0000000..aae791c --- /dev/null +++ b/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,15 @@ + [ + 'tableName' => 'pages', + ], + Content::class => [ + 'tableName' => 'tt_content', + ], +]; diff --git a/Configuration/Icons.php b/Configuration/Icons.php new file mode 100644 index 0000000..eb4244f --- /dev/null +++ b/Configuration/Icons.php @@ -0,0 +1,15 @@ + [ + 'provider' => BitmapIconProvider::class, + // The source bitmap file + 'source' => 'EXT:content_usage/Resources/Public/Icons/Extension.png', + // All icon providers provide the possibility to register an icon that spins + 'spinning' => false, + ], +]; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml new file mode 100644 index 0000000..2d112f1 --- /dev/null +++ b/Configuration/Services.yaml @@ -0,0 +1,10 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + GAYA\ContentUsage\: + resource: '../Classes/*' + + GAYA\ContentUsage\Controller\ReportController: + tags: ['backend.controller'] diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bb118a --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# ext:content-usage + +This TYPO3 extension analyse database to generate a report of content usage: + +- Doktypes +- Ctypes +- Plugins (list_Type) + +## Installation + +composer require gaya/typo3-content-usage + +## Content analysis + +### Doktypes + +List all doktypes declared on the TYPO3 instance and list all pages (actives, disabled, deleted) which are using these doktypes. + +### CType + +List all ctypes declared on the TYPO3 instance and list all contents (actives, disabled, deleted) which are using these CTypes. + +### Plugins + +List all plugins (list_type) declared on the TYPO3 instance and list all contents (actives, disabled, deleted) which are using these plugins. + +## Why + +This content reporting has been designed for maintainers: + +- to quickly find where a content type is used +- to help in a code cleanup phase by identifying unused content types diff --git a/Resources/Private/Language/backend.xlf b/Resources/Private/Language/backend.xlf new file mode 100644 index 0000000..bd41d25 --- /dev/null +++ b/Resources/Private/Language/backend.xlf @@ -0,0 +1,17 @@ + + + +
+ + + Content usage + + + Display content usage for pages and tt_contents. + + + Display reporting of content usage + + + + diff --git a/Resources/Private/Layouts/Module.html b/Resources/Private/Layouts/Module.html new file mode 100644 index 0000000..a1bbd79 --- /dev/null +++ b/Resources/Private/Layouts/Module.html @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/Resources/Private/Partials/BackButton.html b/Resources/Private/Partials/BackButton.html new file mode 100644 index 0000000..a4a8bb5 --- /dev/null +++ b/Resources/Private/Partials/BackButton.html @@ -0,0 +1,11 @@ + + +

+ + + +

+ + diff --git a/Resources/Private/Partials/Card/Header.html b/Resources/Private/Partials/Card/Header.html new file mode 100644 index 0000000..87c421c --- /dev/null +++ b/Resources/Private/Partials/Card/Header.html @@ -0,0 +1,20 @@ + + +
+ +
+ +
+
+
+ +

{title}

+
+ + {subtitle} + +
+
+ diff --git a/Resources/Private/Templates/CtypeDetail.html b/Resources/Private/Templates/CtypeDetail.html new file mode 100644 index 0000000..b0adf4e --- /dev/null +++ b/Resources/Private/Templates/CtypeDetail.html @@ -0,0 +1,66 @@ + + + + + +

Usage of ctype "{ctype.label}" ({status})

+ + + + + + +

Note: a content with a past publication date is classified as disabled.

+ +
+ +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
UidPidHeaderSys Language UidWorkspace Id
{content.uid}{content.pid}{content.header}{content.sysLanguageUid}{content.t3verWsid} + + + + + + + + + +
+
+
diff --git a/Resources/Private/Templates/Ctypes.html b/Resources/Private/Templates/Ctypes.html new file mode 100644 index 0000000..de244a0 --- /dev/null +++ b/Resources/Private/Templates/Ctypes.html @@ -0,0 +1,62 @@ + + + + + +

Content types analysis

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
CTypeLabelActive contentsDisabled contentsDeleted contents
+ + + + {ctype.id}{ctype.label} + {ctype.totalActiveContents} + + + + + {ctype.totalDisabledContents} + + + + + {ctype.totalDeletedContents} + + + +
+
+ + + +
diff --git a/Resources/Private/Templates/DoktypeDetail.html b/Resources/Private/Templates/DoktypeDetail.html new file mode 100644 index 0000000..d67f5e0 --- /dev/null +++ b/Resources/Private/Templates/DoktypeDetail.html @@ -0,0 +1,63 @@ + + + + + +

Usage of doktype "{doktype.label}" ({status})

+ + + + + + +

Note: a page with a past publication date is classified as disabled.

+ +
+ +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
UidTitleSys Language UidWorkspace Id
{page.uid}{page.title}{page.sysLanguageUid}{page.t3verWsid} + + + + + + + + + +
+
+
diff --git a/Resources/Private/Templates/Doktypes.html b/Resources/Private/Templates/Doktypes.html new file mode 100644 index 0000000..8a74f2e --- /dev/null +++ b/Resources/Private/Templates/Doktypes.html @@ -0,0 +1,62 @@ + + + + + +

Doktypes analysis

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
DoktypeLabelActive pagesDisabled pagesDeleted pages
+ + + + {doktype.id}{doktype.label} + {doktype.totalActivePages} + + + + + {doktype.totalDisabledPages} + + + + + {doktype.totalDeletedPages} + + + +
+
+ + + +
diff --git a/Resources/Private/Templates/ListTypeDetail.html b/Resources/Private/Templates/ListTypeDetail.html new file mode 100644 index 0000000..31120d4 --- /dev/null +++ b/Resources/Private/Templates/ListTypeDetail.html @@ -0,0 +1,66 @@ + + + + + +

Usage of plugin "{plugin.label}" ({status})

+ + + + + + +

Note: a content with a past publication date is classified as disabled.

+ +
+ +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
UidPidHeaderSys Language UidWorkspace Id
{content.uid}{content.pid}{content.header}{content.sysLanguageUid}{content.t3verWsid} + + + + + + + + + +
+
+
diff --git a/Resources/Private/Templates/ListTypes.html b/Resources/Private/Templates/ListTypes.html new file mode 100644 index 0000000..e39532a --- /dev/null +++ b/Resources/Private/Templates/ListTypes.html @@ -0,0 +1,62 @@ + + + + + +

Plugins analysis

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
PluginLabelActive contentsDisabled contentsDeleted contents
+ + + + {plugin.id}{plugin.label} + {plugin.totalActiveContents} + + + + + {plugin.totalDisabledContents} + + + + + {plugin.totalDeletedContents} + + + +
+
+ + + +
diff --git a/Resources/Private/Templates/Main.html b/Resources/Private/Templates/Main.html new file mode 100644 index 0000000..2533b6f --- /dev/null +++ b/Resources/Private/Templates/Main.html @@ -0,0 +1,47 @@ + + + + + +

Content usage analysis

+ +
+
+ +
+

This will reference all declared doktypes and find usage in database.

+
+ +
+
+ +
+

This will reference all declared CTypes and find usage in database.

+
+ +
+
+ +
+

This will reference all declared plugins (list_type) and find usage in database.

+
+ +
+
+ +
diff --git a/Resources/Public/Icons/Extension.png b/Resources/Public/Icons/Extension.png new file mode 100644 index 0000000000000000000000000000000000000000..37ca2bba13cb50e6292f9f660b27eaaecb3fbbd6 GIT binary patch literal 12336 zcmZv@XIzub6E=D$BoKNJpp?+1Hw8pWP&(43N(miAnl!0NKokKfN=KwOrGp?nC`fOD zAU!I*2+})meE#qGaDM0HOOiXgvoqJu&dy#td8x0fPDyr+3;+O1O$`-8000p_f&dsX z;mz_%i4y>b9cij4KX^L5l||~sX4<&Y{-NvjWB2++o%+XJ{_tNs{&ddVoU)Z>PPU}a zu0>N2ElacrJYp84FqV(DeTvOReLj_xKjhVg+2RxB`LPKOPnCH%&wg#j{kHn>(X0mU z)@V_5RUTXsAE$e?H52L~*0fOhexqW2Ce*Lp2E7tetXCXmo0iARct9*pLC+=(@cnK1 z&9?n?JAzvAX~6hQq6!@+=JN9L$U;yh(UeMD?#Qo=%Q92dFKR%?k8Lb1E&1c6Vh8*tt<-C4YnxKdz_+6^0gq}A ze~}*=8yfNW6>i0RA{HP6Y<80iMf!>6LsSi9$9co4`1JfWul6$ZYJcDNFnbFX=9I|I z!BEkDdAhmXp$riQk^JJrKu0Z2Oq?1{Kw$KvM}q5v4tC)QICaHc+H!z_whD5SfWR@y z?^WuNsR;CqFFkmf-IsbHcGkE%b9CA$P&FKgJMRHxk6C0CPkQy188G7x4x}1{P8!=g zdcW(ky?+~JpC&nj96a1f<#%vIs$y6(@|?W9a6Uf1ym?{1<&e{{>>+l?GL(7 zSC?lo7GQ$dZ)n9+sm9|0XGJNooz{Jlo^)L3(VS@=UDQWlgaq>r{oMO^ZT7`xIPQoG zYz{^rf8mHW{MRw)Y2(oF;xC`mn-Bp$Ddnrh8d4V2ZhIQlO5pG$?TY1@1wLttQ=_wg0Ye=PR@C+O>S&jD?KJri0F`O~sDq$&b}18<(T z?Iu@#BE!YNSZg57w|ANsE<8rH{*zxBcJSdHB*9Y};S6rS*z#m5gqQ*Mf1ONEwk?|j zx^z9G!d1LGnG5GmqniKgfWFw+<9i|S45HGFuOxg?<^696kES&~ckzfEI(_yTx=+93 z?!aIDPtsSJHrs_|x%G|t-#nm(IAyn6chCnPY(&UNtAMo5y>ib`p-S%dQ+63r*_-5# zNoT{(wqB+JL>5wV;RK_VXn%`u}UtUe@-H1`ljy@X&CcKZV^} zoO{DuirN1}&TWTHwjI2Nmc$d0A!b;Av<3%o@#4TJy5T{Yg%$*^%Uy%3ir1@Oq=82l zTuq0xvEd-c^k!~{ZU}H{^5lsc%*fi7e8n1^BSjzJ{|j^jWQquT$Lc=KF1yzfx8^bttKW5Dj(5%Hl+@l z#^cTX%)hWNn%7F1PHVAbyS5l9_w~_SjVx#*g_3`FGNP&CR>qFnxse`C(-wi&nk|e>-Zlz1_$GlZHpcwoMNYUlK zcg9vA-&x4_DO00^uI>EX?(LkHU-Z;ln`4##u!S-C6-`bbMMNW9NI3QN5T~Q@VGP=& z!jC`&SNjb&gU{=L^@T%$vl6AlQ=7bbJxMa-|eFI z2>hT-DVuNFCOSwa9bALneH`SlnXzeqE5~m*@&@=Jc;V{u$etUkDuN+}A^QNWz7H;Z z3h)CDNvM-U(Q|f|_UBDS88yR8Lf|)p5!2Ha?>~H~-G10U*zuvV?R1*$j(=w!TT=;C z{1R6Y2u4MdN8wk;S;iISRNmBTZ?5QeUkQt7QyS^?if4rh(&N5e7z>$xtI<}nUr_I! zpVi(&7Yz;$o_JWe+1vlVb#iII8I=qK1qZ)^0j7-gP_HY|48@_=i&(Ulsm$QqA@V@2@&B5F+~4j+Z@P6Sp!!ui<-=U zSajdSU7+0o^bG3?qPTho#X*R`C4o%j2!{LQs8wqizO*DDMpN9;O{{P_pcc5BTU^|o z28dsy0c+F(X+V-Q1Y>~VgH}EIF8H6qZ(CNr5W*G>4RS8n!})(==P5z*EC(JEq(vcH zjt{P^;m8|lOgMCbFTe9Y1u{iHLUx4yX9CYHe@)0}JRkT>QPu-RMMd|^>-`SazY7D5V_?~X^=%it9-8By zzD_4I9(jc;VHhY4aS;?~@(d)`q5S(Dopvr@6G&CwcBy!#NREKqCj~+QGn~V=(D+$1 z_2>Y^hAr){uDLt?t(LRyI)^2bGl}UdzB9lKxJGEIj&=Zrr@!R97WQZ5=IEu$cykXA zCi)=NXi$}-_;vL3rjP7tj5(GP2OhE20&-efW)-;;Y{TtjFGz#|wz;{vuMiC0nN(c4 z!xgvY8`SX;0Do(88oWk;UlRoDOaq?xGJW}oW`Sek?!qv(b}(`z2{}f9&J2(vMEpd8 z*bagpL*N)HIAFw61^D{<`BejHb~%1q(;o>K{NJa9pv{Ga=0reo45Wl{g@Gas6lisI zbJGDN-i35CGNLC(M*hElBOLW2L#(2hKZ8d`=jVsOHw?wM>OUP5d*}>&6?%q9Vb;v`yEr}grz+ALi%_q!pAU^N zo%2?W0w!alyqcc=GY$8P1RICRe*4z9^@p0Os;acvQhtM|*HWzVZ1<^m%od9Pd0|;@v9ehkGbWHx_dm3*1yKsI$OQ8CArfmd9eWcVqh}HYU z`}Z~4W(cB=!`N4^MoW!~;Vp5GiRc8YV1&V^r>6;-;yiuCeyX!|Y><>?;0z;^L8y+O zLv}o75~>(i^ZomG{}J`jUg;iyi}~6$i>j=YRBS_hR~P?2F)`g>jYX8VB3^YnY9#7)&_?9inG)jVJRRIIEDL}$Hx0!=iXZNrD%fv+5Yiw+O zB1G_OJ*c&Bg>{doB)i*SH{cklCIu2{PgJY0C3)m&2fMl0s1$^ zNcqH>^G7?}+{lMEB2ZDa5#^{h5Q{O`Zp47MzZ$EEeg6D8 zbRkg~M9q8mKM5^J6d_0V>NJhuP&yUo+r>G@Ky$8?_uKJ%UD7!(!_OkwQP1$!tw9GZ zd^BzRAspbS&yH0zgb7XLRyPAAKv^#q;4SmiV&Eg!Wn>s_SBX6cV$kd6$o)DDhrn;GVOCO67jQ#oLg*iaWU3{r=W^fwL zuoEH8C=Pqf%CZD7DQ_oTu1KwF9a&58=L(B&GRbY<_jSlie-=gudQNWt7%~z|?V|8I zR>}+z)TG1JR<gyK6Q@#4LL34;NW1BqCSC6LLAqmMK0hpgZ58M^X4gj zTQ+X7zJH%4w8wd%9vwFtN<>7YHc>gzt*Y0KgnSH9raoy5@meRx1^I@8p0e(D@|HE1 zmI^fM^@xse07+EJ|G9;4nR`ibd8W*dqfw`>5!2C>Tcdwc<1uzainmI-&aay4(FuE^ zi(x>Z!c%r-!9(i!h&Y#LcmxR4ro;(fyF*wtvc9YOk`QrO6||KD_|tB9ZKj`6F7!2dy(H#R9 z5HQMeBVrjt6Y=Mi_yhn&?PkA)nl1Qjq=-Bu%sUA+`~7FYSN^emy#26RD~pLHUIy zzYP;OM#~U_;W*g&0ol6qG>OmN-+LtFc5@(Qa%!>@x~#Re6&ruq!=qwl^Jhzj@f?=~ zU_uT!F%-zy=M>W%m`erW0c%f{)JaM6Ytx z(YFswXO7ob+YxCDfW8o9*Ep{8d-Qo2$Hqi??Y7X9IS)-dl-ov)kOC>vmbzQ6K=;Al z48487#4{*dJslE*c}U!;nr@EgZXa_|5{ohj12;$!UxX1u5CIg?mq3U|0(m-0pD{zl z>;*Hk!{7Ls2G6V5O2%tEChhY?Bvd?VKn&<_zFN**z}?*)8+6&Fp>+FzsZeo_;)OnR`t0|jcnG^F#ACjFf)}7{F&i9y{MWPIqJT9-r9@@Wb+aDobx!YnhG{P7z zRganIm;f%-19GrVz&;cIrR>EkKslEkn>D|2m=PkKo1LEdouHWdQ{ft*{qgLl(IE?c z$^WG@gprl_IAT?k^9<^b9cI1-u)$#(L>WamtG5NuOB{*cL`qUlRvLJ;RK(2 z`)RF=>*pp?ZC22*0dyHYKg&CQC*i8`+)V^DyLF~51CZxvB$BkEcJxMul)MvlPFBU9 zgEnZ?Ln?Ks+OxvAxOVV{_)cdTHENF_>;oQErP^6Kf&NG7#uY8oL>%ipip zg)s>UGgq?PN*bqPlNU?<&fKfx`AXLJ@GEk!m&r_&?t00c>A!djMH1&VGB{Y`o>N}m z++N)I`NfW{JO$ulqyA@*iiMD9=+aRSvC65~`Z@SuRZML@kTmBm4CtA{%$$w40TPYs z2}8(%YtJrI;^N{S`}ETx3nY5LH|}~t;OaLA?CAFkI1(@W)k7ShyN+K}EVe!-ncM(H zO}1Lj7$`CUEc9(MC(V0%3b{N{kF{Z4c}saQqS0mC2~hoKJ$n~1aixf;M31})Y8q6Y ziI~%zx6n!{gCX!sp;3*uZ;GtmCd4_cTMOSjRp{bmDx$BUWiyK$Nn@3`iqlzZ)T{=lobqr^-AyCC%*!l z+M15bj^TkJ{VSZNYf_eFjnO0@TVnF0;Uav8{GGRz-n~%Da(BzN9J|~;it>C+xW{YsT%CEw{F%c&Kf|?y=Sazsh7(n9VmCc%v4?Gvi_oyjjYI7 zB+me7@_8`V#oCU4iCsT7JP?vXcYx|_9c)8 zQ_6?ykp57vq_*NQui- zys1?Ht}XQJE)(wO5c_n%Cy{k4>g2s4GiOnv9vIRASSxkCcH8fCmDv>Z*u(l>vUXqc zf$-ZxW`TBjb<_w2PzA6?-36FCcc*iu<>om;$TkbADstmej73S?xMJ$(m9N#too<-O zSQwVRyW{SE?n~V`llGRbf$6&t^v%q}y@%BGKB`uO;d`{5JE%r6&w(rHy;XRDIo>Zz zM}32Ud6FF~@Q(aR7tehLK_?g=9Or5symd>F2>}Ub?{E;us99d951eyn@6a#GhgGIs zQbbXhW8~=c@8<|ElkCJuUVWxawep`DY%_(^z>&1!`KhIRKsYqwAr0c!xE+EgK?MZ# zP?WaIe&fb>H3Dh!w_s^l%?wuGVqQRz1y%)o=5%4vh7JM;TsfLdfFy9TYuf|q)I=(0 z>S@uVnt3rjMI`abfOY7BF&qg)$VS}1Di6=+1*;8^2`k=He;Z*~G`U#h91{bJ(^|;;7i|6RNBN z?tXUQ1@Jxb_klscPf(oy*R>r%zAPa@WW;tODHwRQ>nP)trLuxUf$N}@$mS@yY=2R$~2@7cYvf=3VI%PuO$x^C0z-frd z&tN;w6GLX9xU)ZOz=KRsV%zlYXbEDR_@uSpcjH8B_`oGq-aNnpM}9&eB6LYB!gJ_W zh>^tWjxE2vtN^j?2-dFeZ|xBK>v>Pa+aGyEp@7|koM{HYnun$k z=&Z8wRz;01#2>WQS*)Q8QX&0eBnF(<#V5k4ow*{#X8Crf}vz+Qsb^+&zA>Rtz$k#$pfss2g<`@+? ziY|o1gue9Pd~etpR8j@xDaZOsQHE{WJwrfp{K*LPz5zMF7HXGAvwxaN6^0q;>5C>L zgVrH`RpCfTR9wZ~b9^LZgae^_jc&E?nY9Cc>A-T^Na-P@-36S+y2FhNz_>|R)LDCV zK>y<=eDU2j7{k<8Z%LzebH>V)xwv84z?01Ogbr#+b@Z$w;k5V~5I=J_T_DZKke>0U zB6Di8YHW$?>(|;ly>osnAz4LZ4qzZOfL)V__hHV=;isRbz^=Un>otLP=6$1*i|$bl z0$~3S1;G*QZL=9}qigG>?8EoI?`|0!v6EEfbmO5;7sW5Ej*v{+m^bw|~=y zA|lDcFc&GO$bUu>k_8nQz(hCIe5Em-`Li$1N5*LD+qv@{H8`q?D<`}S5SA6}n?fpP z1$r2IJ&eB*Y{yZJ*wL9hF|LYdX5}4ia-Sgt;(tuU%q!3I1}DNgIc&rluOX}@XuT(3`BthJXM0O1?bjiHzuZ>F<@?x!TZ{s&X?q+oEf=!C^5<+y*u&VQjBh=t_n@vM2 zTueUvNJ4UjqXu~M<~ak=S+~%dtu}C+W$8+a+M5o_c$-nA)IQ>>>GyiRB_!`4-_+9) zRKlYQXy|OLZ*1L1#Ff=UM#jeMdAzzl9hbzHXy%GpX$8D9Qw%8YeDG2P8(`UzI3aAK zM#N2@0+G9+;2!Tg>(~D=lRJxsp$Z7WphYYG{BoqxX2AD|Sq1WmCm@-B6TQ~8X?pf& ztup^+vISNQl8_g@E2#|@smm*RJ$6BIh`FT?2#k^=VIfC) zuYuT)dG=Kc7*;$JCP#pxq)1{~?W>%*RIg^Obl1#pZ;AC5T|9y#BqM}|AEuU5%$_Y8 zWMphG%xe44i7OJ7UMCRtOuys;BNY?*Dq7@|^3H<$m)YiO8%hlY$Tc(#W#{;JD*C0T zvck0-QI)(HeKB|+7zM^kZRD{FGf1P?t1~`C>=9WMoEDE=HfmvdD0H1cKHDG64Ar*V zckeeF?dTuTC>#XE%lZX2@M1?qndyW;@_PeP!Wc;SU*fKj+p=lz3h&H}{?QxU_&Mo! zJXM*R)Z_pXcvNDZQBKj~TbFIEAcsuf#Z#t0JgDrm3D*F$@ZdN2-Zs~uPXW*QoRg80 z4xnF+w?PG#a>Uv$+C~aijec1dEb%R~s#*NHFZ*yGQy^qvO9(h%&+yipzi7&W!uQqS z2vL@+oPYF_!yV^Z;N&0{qE(=ay!G%$eITRzOx?FX44t=$Y)y#mcrnGnEGYR3E^Tu*M+l@=sPo2O;x6@ggv`v_dB1qWL}#CtPjlxnOQ& zqDuIEyMft=LieZPZM&=0tfgTulctN|bz2unByK4tKbAVgg@~%@a7KCxjKelBKI3}? zf+C^f@zajRzL!M@w-;VK237<+3u|fkeP3#7IVv8FaLP$2mg}6Yd|YD-I=)CDx&G+G zs#U6IeZx;u3MNHGVIKX2K7brYB8-WA%7_8-VWYM;oHj}6B@US3#epP8E%Vw(?6SGO zvw@li5ve2%H${d`h%fy-?;KFRGAfdHX_3T%cR=JQ&J!{uu?|EckbmfwxCq2M*=|6v zpjo3dZnW1`h+NhsfLQKmXV?)mRg>MZi-2J0Y;*rJ<&BG#vBp|VQZaj2`;xLRS@cU4_8LiSDT|~v%Rh#I1@}Q1 z6PN}^vNC7AtvLgK-Fp4DZScbk`IWwdj;zE&M8W{a{lS56 zMwRRY2HdVIW%*;>dk=F0hyK>N`T6SEu)iF8A-Ms25^uO}M}YP0>vRnV%sZtYo^_Ih z!=3!!PWqQLz;t3EqjB9f-r&P4_z;xu*TCtm4<(LUTI=iU(LqLJpO4;o6t^nmD;izk zmz3}GJQ+NsJgk@hYZ2hjMN6Mdph>AmDrT!}@BQ-wVq)S=M~UzvoXk6y{KKEUtgh|d zyZ9C=<ErJ=W^XJQEnT1$v|TKlLwI0aqgE34B@X$ajzM?T_icwbbm@7a_a-tLei~ zAHjVz_1fmSC1Ey3(bWJmpI%s7Lk#;iv$br2Dbp&BVzKB56 zf6KA`@}QD%X-AtQu=5r7)M2SZO2YEErSa!y>h|&UEJC4$M;*_OW91p)9*3FZt(M+c zgD`1bd;zx*wZ}sEOBEABD3f)@FS=N0;P$%I2m;kPLJ5(V=XrfD>FIEaXs8ca2HEN1j(;YKSxaY;8yVI9V!td_(%bpw zd%s&r;L?-)joFKD-a#!sJT<$BB4+dvWBN-K>y4r>3SYP)yvivUT7kF&ik;aoRgM02 z#&I)Kci*g4YFTmdpXKr;y%cc&<6gR6D|Y!>SJisSoyy?`gYA(|H>l-IhzVz`$_BQT zd^GL1aeZeoK4nMX0>sAcx-u3?Zpq%V-r{!{d)NA(k#MAeAq<3RtG*d}Q0?zZ1xkHj zbJR3rNyOeCvE8y5O910HVb@*k1Ni7}3wnx?W1#v;3|atTwks9q|JZ(`xmxNtp-XEY z(jxdQDIZ;Fp7ed5-6zE^WOaYS7E;_6-5o)M1pD<}GC+PpLZzW?_PTlC$U6;(ZWez> z6bC`-2!CIMj2T_e5GSEL0>r=@2?ve`-CFlYs&m7WNj~a;)OL#>S>YslS3kKDm@a z<-tFw4*VVQZN+gT_|<|e=tJ)(Mm#Co0^aNKow^}h{II&MMR{C0Y zQBPmQcB7q?LUa3zAlK(U{Q(oVKAdyVp3g!an3j86wl1r$HPq4)&6xG=vmyKzq%Z;h2Ref?S9=?{`XXGV_^=B zO7&}=B5GWAEL*WKC-|^pxo2Si!-~MMt51&CmBH_Vk9AT>j0kJsCGZgVWUqX z2H??tacG(C*giiy+cG$vxbk^g^f_tF)YMeR+S-~98ylPK=^c}nwo8kk8H&+%Gw%^Y z+%CQ%SSc*Bu@=Mzp6&M$Si8-MjW@e~GUj!qKw%_Hb51B)^CY~`iMk|&N(u(-#qqMt zH@|$N(idgOd5;$PG_8;GM*%*@Q7w=h(%q!~i=^F_Kdl1Aj)WWF$<&mTOT;>w^$Vnn z?D7rawY~o554@xDVbpRzuI;M*NGH@sI{CD&V;7&}hd$%D>i>SYiK_r7fUN^I{1;pe z7HnQO+Hi?X9mz;bdz8O_fBO2PkEzOqo&&RznZT72H3daTps(+Pf(tXJ0#$iFiyK72 zC&dwCrmu91J!-g)!!b)8${YSG5!o$#!J{KkuzvWwtH9qKv+Ls-?E-_drk=HVB^4Fc zZX$R3`}#!d{Y$=H`a3ym)@C(z$DOSw4>&Kq&PW`#vrPAUTBh*6C_HPc;!M~b^9y@! zZt{M?6?#dyF-L zK56(s>Lj0dJjsa`wCwz}@!BF6%r9F)hkPmLVZ2#)-G0Na|9h}LZ(``e)mDynFrU$+ z&Y(rQ_xpmWa(Oe0*Fm4k3Kl2gZ|yJeDgGuD^6a$a7whmHf5#`I7ajZd3hTssv~O*X zLO+hJEYi2wLW%^1=A3)7^JjFw3O~|+iPw)Xq7-b;UNXITqNUl((K*{fc`1{TVGF*rQh|JR8B^U>FN1?l(zz8F=?^6nf#W5V#cOlHvO;;Ec3~fz zHe;gJ*e4_oq)_sCaiTGShlxy9wGpZ)_{WI*gyB}>-YYALmALFC_Qbw8dViN@xG;I9 zci`gXz=h_)9s-+`bol#)Fqlo*rcjtMT{SA#(&BBnb@H!if;Rm@mSg1%>|E@Mi?EjupURMilmof z^Xm&QDr}BeJo3{0%(JikXi5tEx)iDl_98p?cI$Y%!kz9Y>l3r`b=6l|oDvj;QLf^` zrJ8G(e?(b633ckQHwT-wz>o^3{pyazRQIfwFg*{AkW^Y@u8pa?MWfRNzI{S3%b8lx#0~?iMDvUv!DmjMXX*%FQ0tQWYaoI9BLh{qZZl z)~-vY%Zs$)FAya{dU5x7cXmVWz%JVcMA+|96iaSr8%hB%U@MFhsK!^%QqZtqD&%rf zF2^NpEAaf^oXJiKW=^y|t2oglb-mMEBoj5-I5=xKfdo`^H{mB<9s9h-%*yL$5Rv?xo#EYI(vKXZ00Xsqh~GVNi0)B#<#sNg7(SpgM;hb{PH;OZq>9Nnq+g4_ zY?PaIn0@!gv-MDHF`~4n%dUcJk*q#vgJSon3CZ(hL9JoM*R@TQ95HxS#0K}Hz`g-g z=mBh85{ELHAhHa2!ptu~l+f=&sM~1as~vZQ8&J|@VYF`q+}4*7*9e{8THWTOsKjpY zF^XMKM0WNE((eYpiJI}Uc$4KH*3j?$!~NaN^37FsN|y##3C5VAhcr!5RfP`g#nQF(YvglKW6n7}=a$k&hD5?xOD#*CX=CW)4Hu)k} zRAZOh?;lpL+Mr+rV z3ICsdd+^snRdxtZy`u>`neaCcb4ZhFKpe(sZP@)&Wym6Th8RHq`|P(|oKl6Mq|T3f z0T+)md-q6p6+DqCd1Ha#-Gxudknd-%FC4WjQbP?Bf+Q2h05+@QcNH(%AM71j4;>pZ z895(EdVkhe>@?Pj0RHA;oAmL(LEejvqAftU;)X?vS z>qD>9TU&|u)1zfck*3N$w=g~0#=OBcR3XYk94V62Ib%B(4YoC5&)1uD8J8WKd&~6i z)0he{X558lmb&-Lpjyj@OUrJg0)7kelRv+bhtK@nBw0SCfBD^ zN=5`-XA2nMMG=GJ(rfjD+PEMqvBc)_mN?0MMtoz`ruy>FyPO^~tIo|jur7#qk|8q# z0T|EBmRDhuZ#EjC3|yaD6G}MD*}wG!Wapo<|KchXmtcI%*T6gJ6G&?;e)jWW1mT|mfTpUhO2u7N`2PX=S%{(l literal 0 HcmV?d00001 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f970f6d --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "gaya/typo3-content-usage", + "description": "Content usage analysis", + "type": "typo3-cms-extension", + "license": "GPL-2.0+", + "version": "11.0.0", + "keywords": [ + "typo3", + "content", + "usage", + "report", + "doktype", + "ctype", + "list_type" + ], + "authors": [ + { + "name": "GAYA", + "email": "contact@gaya.fr", + "homepage": "https://www.gaya.fr", + "role": "maintainer" + } + ], + "require": { + "php": "^8.2", + "typo3/cms-core": "^11.4.0", + "typo3/cms-extbase": "^11.4.0" + }, + "autoload": { + "psr-4": { + "GAYA\\ContentUsage\\": "Classes/" + } + }, + "extra": { + "typo3/cms": { + "extension-key": "content_usage" + } + } +} diff --git a/ext_tables.php b/ext_tables.php new file mode 100644 index 0000000..e92ddea --- /dev/null +++ b/ext_tables.php @@ -0,0 +1,22 @@ + ReportController::class . '::processRequest', + 'access' => 'user,group', + 'name' => 'tools_ContentUsage', + 'iconIdentifier' => 'module-content-usage', + 'labels' => 'LLL:EXT:content_usage/Resources/Private/Language/backend.xlf', + ] +);