Skip to content

Commit

Permalink
[FEATURE] Add TYPO3 v13 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
twoldanski committed Sep 20, 2024
1 parent 128698b commit eccba3a
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 186 deletions.
51 changes: 44 additions & 7 deletions Classes/ContentObject/JsonContentContentObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
use FriendsOfTYPO3\Headless\Json\JsonEncoder;
use FriendsOfTYPO3\Headless\Json\JsonEncoderInterface;
use FriendsOfTYPO3\Headless\Utility\HeadlessUserInt;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentContentObject;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
Expand All @@ -23,6 +26,7 @@
use function count;
use function is_array;
use function json_decode;
use function json_encode;
use function str_contains;
use function trim;

Expand Down Expand Up @@ -102,11 +106,18 @@ class JsonContentContentObject extends ContentContentObject
{
private HeadlessUserInt $headlessUserInt;
private JsonEncoderInterface $jsonEncoder;
/**
* @var mixed|object|\Psr\Log\LoggerAwareInterface|\TYPO3\CMS\Core\SingletonInterface|TimeTracker|(TimeTracker&\Psr\Log\LoggerAwareInterface)|(TimeTracker&\TYPO3\CMS\Core\SingletonInterface)|null
*/
private TimeTracker $timeTracker;
private EventDispatcherInterface $eventDispatcher;

public function __construct()
{
$this->headlessUserInt = GeneralUtility::makeInstance(HeadlessUserInt::class);
$this->jsonEncoder = GeneralUtility::makeInstance(JsonEncoder::class);
$this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
$this->eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
}

/**
Expand Down Expand Up @@ -199,6 +210,8 @@ protected function groupContentElementsByColPos(array $contentElements, array $c
*/
private function prepareValue(array $conf): array
{
$t3v13andAbove = (new Typo3Version())->getMajorVersion() >= 13;

$frontendController = $this->getTypoScriptFrontendController();
$theValue = [];
$originalRec = $frontendController->currentRecord;
Expand Down Expand Up @@ -233,20 +246,44 @@ private function prepareValue(array $conf): array
$tmpValue = '';

do {
$records = $this->cObj->getRecords($conf['table'], $conf['select.']);
if ($t3v13andAbove) {
$modifyRecordsEvent = $this->eventDispatcher->dispatch(
new \TYPO3\CMS\Frontend\ContentObject\Event\ModifyRecordsAfterFetchingContentEvent(
$this->cObj->getRecords($conf['table'], $conf['select.']),
json_encode($theValue, JSON_THROW_ON_ERROR),
$slide,
$slideCollect,
$slideCollectReverse,
$slideCollectFuzzy,
$conf
)
);

$records = $modifyRecordsEvent->getRecords();
$theValue = json_decode($modifyRecordsEvent->getFinalContent(), true, 512, JSON_THROW_ON_ERROR);
$slide = $modifyRecordsEvent->getSlide();
$slideCollect = $modifyRecordsEvent->getSlideCollect();
$slideCollectReverse = $modifyRecordsEvent->getSlideCollectReverse();
$slideCollectFuzzy = $modifyRecordsEvent->getSlideCollectFuzzy();
$conf = $modifyRecordsEvent->getConfiguration();
} else {
$records = $this->cObj->getRecords($conf['table'], $conf['select.']);
}
$cobjValue = [];
if (!empty($records)) {
$this->getTimeTracker()->setTSlogMessage('NUMROWS: ' . count($records));
$this->timeTracker->setTSlogMessage('NUMROWS: ' . count($records));

$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class, $frontendController);
$cObj->setParent($this->cObj->data, $this->cObj->currentRecord);
$this->cObj->currentRecordNumber = 0;

foreach ($records as $row) {
// Call hook for possible manipulation of database row for cObj->data
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] ?? [] as $className) {
$_procObj = GeneralUtility::makeInstance($className);
$_procObj->modifyDBRow($row, $conf['table']);
if (!$t3v13andAbove) {
// Call hook for possible manipulation of database row for cObj->data
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content_content.php']['modifyDBRow'] ?? [] as $className) {
$_procObj = GeneralUtility::makeInstance($className);
$_procObj->modifyDBRow($row, $conf['table']);
}
}
$registerField = $conf['table'] . ':' . ($row['uid'] ?? 0);
if (!($frontendController->recordRegister[$registerField] ?? false)) {
Expand Down Expand Up @@ -282,7 +319,7 @@ private function prepareValue(array $conf): array
}
$again = (string)$conf['select.']['pidInList'] !== '';
}
} while ($again && $slide && ((string)$tmpValue === '' && $slideCollectFuzzy || $slideCollect));
} while ($again && $slide && (((string)$tmpValue === '' && $slideCollectFuzzy) || $slideCollect));

$theValue = $this->groupContentElementsByColPos($theValue, $conf);
// Restore
Expand Down
113 changes: 11 additions & 102 deletions Classes/Middleware/ShortcutAndMountPointRedirect.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,16 @@
namespace FriendsOfTYPO3\Headless\Middleware;

use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use FriendsOfTYPO3\Headless\Utility\UrlUtility;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Http\ImmediateResponseException;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\ErrorController;

use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;

use function is_array;
use function parse_url;

class ShortcutAndMountPointRedirect implements MiddlewareInterface, LoggerAwareInterface
class ShortcutAndMountPointRedirect extends \TYPO3\CMS\Frontend\Middleware\ShortcutAndMountPointRedirect
{
use LoggerAwareTrait;

public function __construct(private readonly HeadlessMode $headlessMode) {}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
Expand All @@ -46,96 +33,18 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $handler->handle($request);
}

$exposeInformation = $GLOBALS['TYPO3_CONF_VARS']['FE']['exposeRedirectInformation'] ?? false;

// Check for shortcut page and mount point redirect
try {
$redirectToUri = $this->getRedirectUri($request);
} catch (ImmediateResponseException $e) {
return $e->getResponse();
}
if ($redirectToUri !== null && $redirectToUri !== (string)$request->getUri()) {
/** @var PageArguments $pageArguments */
$pageArguments = $request->getAttribute('routing', null);
$message = 'TYPO3 Shortcut/Mountpoint' . ($exposeInformation ? ' at page with ID ' . $pageArguments->getPageId() : '');

if ($this->isHeadlessEnabled($request)) {
$parsed = parse_url($redirectToUri);
if (is_array($parsed)) {
$path = $parsed['path'] ?? '/';
return new JsonResponse(['redirectUrl' => $path, 'statusCode' => 307]);
}
}

return new RedirectResponse(
$redirectToUri,
307,
['X-Redirect-By' => $message]
);
}

// See if the current page is of doktype "External URL", if so, do a redirect as well.
$controller = $request->getAttribute('frontend.controller');
if ((int)$controller->page['doktype'] === PageRepository::DOKTYPE_LINK) {
$externalUrl = $this->prefixExternalPageUrl(
$controller->page['url'],
$request->getAttribute('normalizedParams')->getSiteUrl()
);
$message = 'TYPO3 External URL' . ($exposeInformation ? ' at page with ID ' . $controller->page['uid'] : '');
if (!empty($externalUrl)) {
if ($this->isHeadlessEnabled($request)) {
return new JsonResponse(['redirectUrl' => $externalUrl, 'statusCode' => 303]);
}
$coreResponse = parent::process($request, $handler);

return new RedirectResponse(
$externalUrl,
303,
['X-Redirect-By' => $message]
);
}
$this->logger->error(
'Page of type "External URL" could not be resolved properly',
[
'page' => $controller->page,
]
);
return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$request,
'Page of type "External URL" could not be resolved properly',
$controller->getPageAccessFailureReasons(PageAccessFailureReasons::INVALID_EXTERNAL_URL)
);
if ($coreResponse instanceof RedirectResponse && $this->isHeadlessEnabled($request)) {
return new JsonResponse([
'redirectUrl' => GeneralUtility::makeInstance(UrlUtility::class)
->withRequest($request)
->prepareRelativeUrlIfPossible($coreResponse->getHeader('location')[0] ?? ''),
'statusCode' => $coreResponse->getStatusCode(),
]);
}

return $handler->handle($request);
}

protected function getRedirectUri(ServerRequestInterface $request): ?string
{
$controller = $request->getAttribute('frontend.controller');
$redirectToUri = $controller->getRedirectUriForShortcut($request);
return $redirectToUri ?? $controller->getRedirectUriForMountPoint($request);
}

/**
* Returns the redirect URL for the input page row IF the doktype is set to 3.
*
* @param string $redirectTo The page row to return URL type for
* @param string $sitePrefix if no protocol or relative path given, the site prefix is added
* @return string The URL from based on the external page URL given with a prefix.
*/
protected function prefixExternalPageUrl(string $redirectTo, string $sitePrefix): string
{
$uI = parse_url($redirectTo);
// If relative path, prefix Site URL
// If it's a valid email without protocol, add "mailto:"
if (!($uI['scheme'] ?? false)) {
if (GeneralUtility::validEmail($redirectTo)) {
$redirectTo = 'mailto:' . $redirectTo;
} elseif (!str_starts_with($redirectTo, '/')) {
$redirectTo = $sitePrefix . $redirectTo;
}
}
return $redirectTo;
return $coreResponse;
}

private function isHeadlessEnabled(ServerRequestInterface $request): bool
Expand Down
67 changes: 18 additions & 49 deletions Classes/Seo/CanonicalGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,73 +12,42 @@
namespace FriendsOfTYPO3\Headless\Seo;

use FriendsOfTYPO3\Headless\Utility\HeadlessMode;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Domain\Page;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Seo\Event\ModifyUrlForCanonicalTagEvent;
use TYPO3\CMS\Seo\Canonical\CanonicalGenerator as CoreCanonicalGenerator;

use function htmlspecialchars;
use function json_encode;

/**
* Overridden core version with headless implementation
* Decorate Core version with headless flavor
*
* @codeCoverageIgnore
*/
class CanonicalGenerator extends \TYPO3\CMS\Seo\Canonical\CanonicalGenerator
class CanonicalGenerator
{
protected TypoScriptFrontendController $typoScriptFrontendController;
protected PageRepository $pageRepository;
protected EventDispatcherInterface $eventDispatcher;

public function handle(array &$params): string
{
if ($this->typoScriptFrontendController->config['config']['disableCanonical'] ?? false) {
return '';
}
$canonical = GeneralUtility::makeInstance(CoreCanonicalGenerator::class)->generate($params);

$event = new ModifyUrlForCanonicalTagEvent('', $params['request'], new Page($params['page']));
$event = $this->eventDispatcher->dispatch($event);
$href = $event->getUrl();

if (empty($href) && (int)$this->typoScriptFrontendController->page['no_index'] === 1) {
if ($canonical === '') {
return '';
}

if (empty($href)) {
// 1) Check if page has canonical URL set
$href = $this->checkForCanonicalLink();
}
if (empty($href)) {
// 2) Check if page show content from other page
$href = $this->checkContentFromPid();
}
if (empty($href)) {
// 3) Fallback, create canonical URL
$href = $this->checkDefaultCanonical();
}
if (GeneralUtility::makeInstance(HeadlessMode::class)->withRequest($params['request'])->isEnabled()) {
$canonical = [
'href' => $this->processCanonical($canonical),
'rel' => 'canonical',
];

if (!empty($href)) {
if (GeneralUtility::makeInstance(HeadlessMode::class)->withRequest($params['request'])->isEnabled()) {
$canonical = [
'href' => htmlspecialchars($href),
'rel' => 'canonical',
];
$params['_seoLinks'][] = $canonical;
$canonical = json_encode($canonical);
}

$params['_seoLinks'][] = $canonical;
$canonical = json_encode($canonical);
} else {
$canonical = '<link ' . GeneralUtility::implodeAttributes([
'rel' => 'canonical',
'href' => $href,
], true) . '/>' . LF;
$this->typoScriptFrontendController->additionalHeaderData[] = $canonical;
}
return $canonical;
}

return $canonical;
}
return '';
protected function processCanonical(string $canonical): string
{
return htmlspecialchars(GeneralUtility::get_tag_attributes($canonical)['href'] ?? '');
}
}
2 changes: 1 addition & 1 deletion Classes/Seo/MetaHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function process(ServerRequestInterface $request, TypoScriptFrontendContr
GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
}

$content['seo']['title'] = $controller->generatePageTitle();
$content['seo']['title'] = $controller->generatePageTitle($request);

$this->generateMetaTagsFromTyposcript(
$controller->pSetup['meta.'] ?? [],
Expand Down
Loading

0 comments on commit eccba3a

Please sign in to comment.