Commit cb1fe688 authored by Paul Ilea's avatar Paul Ilea

[FEATURE] Add automated redirect handling for page slug updates

parent d1a06a73
......@@ -80,6 +80,13 @@ class Route extends AbstractEntity {
*/
protected $tstamp = 0;
/**
* Route object constructor
*/
public function __construct() {
$this->categories = new ObjectStorage();
}
/**
* @return boolean
*/
......
......@@ -99,4 +99,55 @@ class RouteRepository extends Repository {
return $query->execute($raw);
}
/**
* Queries the redirect records based on category and destination URL
*
* @param int $pageUid
* @param int $categoryUid
* @param string $destinationUrl
* @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
*/
public function findByCategoryAndDestination($pageUid, $categoryUid, $destinationUrl) {
$query = $this->createQuery();
$querySettings = $query->getQuerySettings();
$querySettings->setStoragePageIds([$pageUid]);
$query->setQuerySettings($querySettings);
$query->matching(
$query->logicalAnd(
[
$query->contains('categories', (int) $categoryUid),
$query->equals('destination_url', \trim($destinationUrl))
]
)
);
return $query->execute();
}
/**
* Queries the redirect records based on category and destination URL
*
* @param int $pageUid
* @param string $sourceUrl
* @return bool
*/
public function sourceUrlRouteExists($pageUid, $sourceUrl): bool {
$query = $this->createQuery();
$querySettings = $query->getQuerySettings();
$querySettings->setStoragePageIds([$pageUid]);
$query->setQuerySettings($querySettings);
$query->matching($query->equals('source_url', \trim($sourceUrl)));
$query->setLimit(1);
return \count($query->execute(TRUE)) > 0;
}
/**
* Commits the object updates to the database
*/
public function persistAll() {
$this->persistenceManager->persistAll();
}
}
<?php
namespace SGalinski\SgRoutes\Hook;
use SGalinski\SgRoutes\Domain\Model\Category;
use SGalinski\SgRoutes\Domain\Model\Route;
use SGalinski\SgRoutes\Domain\Repository\CategoryRepository;
use SGalinski\SgRoutes\Domain\Repository\RouteRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Routing\SiteMatcher;
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager;
use TYPO3\CMS\Extbase\Object\ObjectManager;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
class PageDataHandlerHook {
/**
* Called after a record was edited or added.
*
* @param string $status DataHandler operation status, either 'new' or 'update'
* @param string $table The DB table the operation was carried out on
* @param mixed $id The record's uid for update records, a string to look the record's uid up after it has been created
* @param array $fieldArray Array of changed fields and their new values
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
* @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
*/
public function processDatamap_postProcessFieldArray(string $status, string $table, $id, array $fieldArray) {
if ($table !== 'pages' || $status === 'new' || !isset($fieldArray['slug'])) {
return;
}
/** @var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** @var TypoScriptService $typoScriptService */
$typoScriptService = $objectManager->get(TypoScriptService::class);
/** @var BackendConfigurationManager $configurationManager */
$configurationManager = $objectManager->get(BackendConfigurationManager::class);
$pageSetup = $configurationManager->getTypoScriptSetup();
$moduleSettings = [];
if (\is_array($pageSetup['module.']['tx_sgroutes.']['settings.'] ?? FALSE)) {
$moduleSettings = $typoScriptService->convertTypoScriptArrayToPlainArray(
$pageSetup['module.']['tx_sgroutes.']['settings.']
);
}
$automatedRoutingCategoryUid = (int) ($moduleSettings['automatedRoutingCategory'] ?? 0);
if ($automatedRoutingCategoryUid <= 0) {
return;
}
$categoryRepository = $objectManager->get(CategoryRepository::class);
/** @var Category $automatedRoutingCategory */
$automatedRoutingCategory = $categoryRepository->findByUid($automatedRoutingCategoryUid);
if (!$automatedRoutingCategory) {
return;
}
$oldValues = BackendUtility::getRecord($table, $id, 'slug, sys_language_uid, l10n_parent');
$pageLanguageUid = (int) $oldValues['sys_language_uid'];
$parentPageId = (int) $id;
if ($pageLanguageUid > 0) {
$parentPageId = (int) $oldValues['l10n_parent'];
}
$siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
$site = $siteMatcher->matchByPageId($parentPageId);
$language = ($pageLanguageUid < 0) ? $site->getDefaultLanguage() : $site->getLanguageById($pageLanguageUid);
$base = $language->getBase();
$oldDestinationUrl = rtrim($base->getPath(), '/') . '/' . ltrim($oldValues['slug'], '/');
$newDestinationUrl = rtrim($base->getPath(), '/') . '/' . ltrim($fieldArray['slug'], '/');
if ($oldDestinationUrl === $newDestinationUrl) {
return;
}
$routeRepository = $objectManager->get(RouteRepository::class);
$routeExists = $routeRepository->sourceUrlRouteExists($site->getRootPageId(), $oldDestinationUrl);
$existingSlugRoutes = $routeRepository->findByCategoryAndDestination(
$site->getRootPageId(), $automatedRoutingCategoryUid, $oldDestinationUrl
);
if (\count($existingSlugRoutes)) {
/** @var Route $route */
foreach ($existingSlugRoutes as $route) {
if ($route->getSourceUrl() === $newDestinationUrl) {
$routeRepository->remove($route);
} else {
$route->setDestinationUrl($newDestinationUrl);
$route->setTstamp($GLOBALS['EXEC_TIME']);
$routeRepository->update($route);
}
}
$routeRepository->persistAll();
}
if ($routeExists) {
return;
}
$newRoute = new Route();
$newRoute->setPid($site->getRootPageId());
$newRoute->addCategory($automatedRoutingCategory);
$newRoute->setSourceUrl($oldDestinationUrl);
$newRoute->setDestinationUrl($newDestinationUrl);
$newRoute->setDescription(
'Automated slug update routing for page uid ' . $parentPageId . '(' . $language->getTwoLetterIsoCode() . ')'
);
$newRoute->setCrdate($GLOBALS['EXEC_TIME']);
$newRoute->setTstamp($GLOBALS['EXEC_TIME']);
$routeRepository->add($newRoute);
$routeRepository->persistAll();
}
}
<?php
namespace SGalinski\SgRoutes\Hook;
/***************************************************************
* Copyright notice
*
* (c) sgalinski Internet Services (https://www.sgalinski.de)
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use SGalinski\SgRoutes\Domain\Repository\RouteRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Routing\SiteMatcher;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
/**
* Backend edid form hook
*/
class PageLayoutController {
/**
* Sets the default value for the pages author field
* to the name of the logged-in user
*
* @return string
* @throws \InvalidArgumentException
* @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public function addRoutesWarnigMessage(): string {
$out = '';
$pageId = (int) GeneralUtility::_GP('id');
if ($pageId <= 0) {
return $out;
}
$sourceUrls = [];
$siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
$site = $siteMatcher->matchByPageId($pageId);
$pageRow = BackendUtility::getRecord('pages', $pageId, 'slug');
foreach ($site->getLanguages() as $language) {
$languageId = $language->getLanguageId();
$languageBase = $language->getBase();
if ($languageId === 0) {
$sourceUrls[] = rtrim($languageBase->getPath(), '/') . '/' . ltrim($pageRow['slug'], '/');
} else {
$translatedRowSearch = BackendUtility::getRecordLocalization('pages', $pageId, $languageId);
if (\count($translatedRowSearch)) {
$translatedRow = $translatedRowSearch[0];
$sourceUrls[] = rtrim($languageBase->getPath(), '/') . '/' . ltrim($translatedRow['slug'], '/');
}
}
}
$urlsWithRedirect = [];
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$routeRepository = $objectManager->get(RouteRepository::class);
foreach ($sourceUrls as $sourceUrl) {
if ($routeRepository->sourceUrlRouteExists($site->getRootPageId(), $sourceUrl)) {
$urlsWithRedirect[] = $sourceUrl;
}
}
if (\count($urlsWithRedirect)) {
$urlsString = '<strong>' . implode('</strong>, <strong>', $urlsWithRedirect) . '</strong>';
$message = LocalizationUtility::translate('backend.pageSlugConfilct', 'SgRoutes', [$urlsString]);
$out = '<div class="alert alert-info" role="alert">' . $message . '</div>';
}
return $out;
}
}
......@@ -18,5 +18,9 @@ module.tx_sgroutes {
10 = EXT:sg_routes/Resources/Private/Backend/Layouts/
}
}
settings {
automatedRoutingCategory =
}
}
......@@ -18,4 +18,8 @@ module.tx_sgroutes {
10 = {$module.tx_sgroutes.view.layoutRootPaths.10}
}
}
settings {
automatedRoutingCategory = {$module.tx_sgroutes.settings.automatedRoutingCategory}
}
}
......@@ -145,6 +145,10 @@
<source>Page Not Found Handling</source>
<target>Handling: "Seite nicht gefunden"</target>
</trans-unit>
<trans-unit id="backend.pageSlugConfilct">
<source>Possible redirect conflict detected for the following URL(s): %s. Please check the redirect configuration for these URL(s) using the "Redirects" Module.</source>
<target>Möglicher Weiterleitungskonflikt für die folgende(n) URL(s) festgestellt: %s. Bitte überprüfen Sie die Weiterleitungskonfiguration für diese URL(s) mit dem Modul "Weiterleitungen".</target>
</trans-unit>
<trans-unit id="backend.permanently" approved="yes">
<source>301 - Permanent Redirect</source>
<target>301 - Permanente Weiterleitung</target>
......
......@@ -105,6 +105,9 @@
<trans-unit id="backend.pageNotFoundHandling.header">
<source>Page Not Found Handling</source>
</trans-unit>
<trans-unit id="backend.pageSlugConfilct">
<source>Possible redirect conflict detected for the following URL(s): %s. Please check the redirect configuration for these URL(s) using the "Redirects" Module.</source>
</trans-unit>
<trans-unit id="backend.permanently">
<source>301 - Permanent Redirect</source>
</trans-unit>
......
......@@ -58,5 +58,15 @@ call_user_func(
$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'];
}
}
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =
SGalinski\SgRoutes\Hook\PageDataHandlerHook::class;
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'])) {
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'] = [];
}
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'][] =
SGalinski\SgRoutes\Hook\PageLayoutController::class . '->addRoutesWarnigMessage';
}, 'sg_routes'
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment