Bug #87574

Redirects to records (linkHandler) don't work

Added by Ben Robinson about 2 months ago. Updated 17 days ago.

Status:
Needs Feedback
Priority:
Should have
Assignee:
-
Category:
Link Handling, Site Handling & Routing
Target version:
-
Start date:
2019-01-29
Due date:
% Done:

0%

TYPO3 Version:
9
PHP Version:
Tags:
Complexity:
Is Regression:
Sprint Focus:

Description

A redirect (sysext:redirects) to a record (f.e. t3://record?identifier=tx_news&uid=1) results in the error:

Core: Exception handler (WEB): Uncaught TYPO3 Exception: Call to a member function getMountPointInfo() on string | Error thrown in file /html/typo3/typo3_src-9.5.4/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php in line 111. Requested URL: [...]

TSconfig

TCEMAIN.linkHandler{
    tx_news{
        handler = TYPO3\CMS\Recordlist\LinkHandler\RecordLinkHandler
        label = News
        scanAfter = page
        displayBefore = file
        configuration {
            table = tx_news_domain_model_news
            storagePid = 35
            hidePageTree = 0
            pageTreeMountPoints = 35
        }
    }
}

Setup

config.recordLinks{
    tx_news{
        typolink{
            parameter = 39
            additionalParams.data = field:uid
            additionalParams.wrap = &tx_news_pi1[controller]=News&tx_news_pi1[action]=detail&tx_news_pi1[news]=|
            useCacheHash = 1
        }
    }
}

I have tested this setup with links in regular content like ...

<a href="t3://record?identifier=tx_news&amp;uid=1">Link</a>

... and those are working fine.


Related issues

Related to TYPO3 Core - Bug #82798: Linkhandler for records works only when BE user is logged in Closed 2017-10-18

History

#1 Updated by Ben Robinson 25 days ago

  • Related to Bug #82798: Linkhandler for records works only when BE user is logged in added

#2 Updated by Ben Robinson 25 days ago

I found out, that the "News"-Tab is only available in the linkwizard of the redirects module, if the TSconfig is included via ext_tables.php

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig('<INCLUDE_TYPOSCRIPT: source="DIR:EXT:user_site/Configuration/TsConfig/Page">');

If it is included in the TSconfig in the page properties of the rootpage ...

<INCLUDE_TYPOSCRIPT: source="DIR:EXT:user_site/Configuration/TsConfig/Page">

... the tab is not even visible.

Summary

TSconfig via ext_tables.php:
  • Error Call to a member function getMountPointInfo() on string
  • Issue 82798 occurs (Links to records broken if cache is generated by someone who is not logged into BE). Can someone please reopen this issue?
TSconfig via page properties:
  • Tab not even available in link wizard of redirects module

#3 Updated by Benni Mack 24 days ago

Hey Ben,

thank you very much for your report!

Could you please try with latest 9.5.x-dev? we've improved this area since 9.5.4, and hopefully this is now solved.

#4 Updated by Benni Mack 24 days ago

  • Status changed from New to Needs Feedback

#5 Updated by Ben Robinson 24 days ago

Hi Benni,

I have just tested it with 9.5.5-dev. It's all the same as with 9.5.4:

If the TSconfig-file is included in the page properties of the rootpage:

  • The redirect does not work. Error in FE: Call to a member function getMountPointInfo() on string
  • It is not possible to create new redirects to records, because the „News“-tab is missing in the link wizard of the redirect module

If the TSconfig-file is included via ext_tables.php

  • The redirect does not work. Error inn FE: Call to a member function getMountPointInfo() on string
  • It is possible to create redirects to to records
  • Issue 82798 still occurs (I dont have permissions to reopen the issue. Could you please?)

#6 Updated by Daniel Dorndorf 18 days ago

I have the same issue...

Also in Backend the redirect module just throws an exception:

PHP Warning: Invalid argument supplied for foreach() in /public/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php line 4399

It seems in backend the rootLine in the TypoScriptFrontendController is missing, which is needed to determine its page ts.
In Frontend the PageRepository is not properly initalized, even though It would have, you would run into the same error as in the backend, that the controller cannot fetch the page ts.

DatabaseRecordLinkBuilder relies on the method getPagesTSconfig which expects to have a rootline.
I faced this issue also when building a rest api via middleware using the lib.parseFunc_rte, I tried using the way the htmlviewhelper but wrote an own frontend emulating function, maybe it helps fixing this:

$rootPageId = null;

/** @var \TYPO3\CMS\Core\Site\Entity\Site $site */
foreach (ObjectUtility::makeInstance(SiteFinder::class)->getAllSites() as $site) {
    if (GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY') === $site->getBase()->getHost()) {
        $rootPageId = $site->getRootPageId();
    }
}

if (null === $rootPageId) {
    throw new RteFrontendSimulationException('Can\'t determine the rootpage uid to use');
}

/** @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $typoScriptFrontendController */
$typoScriptFrontendController = ObjectUtility::makeInstance(
    TypoScriptFrontendController::class,
    null,
    $rootPageId,
    1
);

/** @var array $rootline */
$rootline = ObjectUtility::makeInstance(RootlineUtility::class, $rootPageId)->get();

$typoScriptFrontendController->rootLine = $rootline;
$typoScriptFrontendController->sys_page = ObjectUtility::makeInstance(PageRepository::class);

$GLOBALS['TSFE'] = $typoScriptFrontendController;

/** @var \TYPO3\CMS\Core\TypoScript\TemplateService $templateService */
$templateService = ObjectUtility::makeInstance(TemplateService::class);
$templateService->start($rootline);

$GLOBALS['TSFE']->tmpl = $templateService;

#7 Updated by Daniel Dorndorf 18 days ago

You can build a workaround with that by xclass the databaseRecordLinkBuilder

<?php
declare(strict_types=1);

namespace Vendor\Extension\XClass;

use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Page\PageRepository;
use TYPO3\CMS\Frontend\Typolink\DatabaseRecordLinkBuilder as CoreDatabaseRecordLinkBuilder;

/**
 * @package Extension
 * @subpackage XClass
 */
class DatabaseRecordLinkBuilder extends CoreDatabaseRecordLinkBuilder
{
    /**
     * @return TypoScriptFrontendController
     */
    public function getTypoScriptFrontendController(): TypoScriptFrontendController
    {
        $rootPageId = null;

        /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);

        /** @var \TYPO3\CMS\Core\Site\Entity\Site $site */
        foreach ($objectManager->get(SiteFinder::class)->getAllSites() as $site) {
            if (GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY') === $site->getBase()->getHost()) {
                $rootPageId = $site->getRootPageId();
            }
        }

        if (!$this->typoScriptFrontendController instanceof TypoScriptFrontendController) {
            /** @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $typoScriptFrontendController */
            $this->typoScriptFrontendController = $objectManager->get(
                TypoScriptFrontendController::class,
                null,
                $rootPageId,
                1
            );
        }

        if (false === is_array($this->typoScriptFrontendController->rootLine)) {
            $this->typoScriptFrontendController->rootLine = $objectManager->get(RootlineUtility::class, $rootPageId)->get();
        }

        if (!$this->typoScriptFrontendController->sys_page instanceof PageRepository) {
            $this->typoScriptFrontendController->sys_page = $objectManager->get(PageRepository::class);
        }

        $GLOBALS['TSFE'] = $this->typoScriptFrontendController;

        if (!$GLOBALS['TSFE']->tmpl instanceof TemplateService) {
            /** @var \TYPO3\CMS\Core\TypoScript\TemplateService $templateService */
            $templateService = $objectManager->get(TemplateService::class);
            $templateService->start($this->typoScriptFrontendController->rootLine);

            $GLOBALS['TSFE']->tmpl = $templateService;
        }

        return $this->typoScriptFrontendController;
    }
}

#8 Updated by Daniel Dorndorf 18 days ago

Just got further into it and found out another problem that databaseRecordLinkBuilder isn't returning anything at all, but the RedirectService is expecting an array with key url.

public/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php:30
public/typo3/sysext/redirects/Classes/Service/RedirectService.php:248

#9 Updated by Daniel Dorndorf 18 days ago

Here the xclass workaround for the faulty redirectservice

<?php
declare(strict_types=1);

namespace Vendor\Extension\XClass;

use Psr\Http\Message\UriInterface;
use TYPO3\CMS\Core\Http\Uri;
use TYPO3\CMS\Core\Site\Entity\SiteInterface;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3\CMS\Frontend\Page\PageRepository;
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
use TYPO3\CMS\Redirects\Service\RedirectService as CoreRedirectService;

/**
 * @package Extension
 * @subpackage XClass
 */
class RedirectService extends CoreRedirectService
{
    /**
     * @param array $redirectRecord
     * @param \TYPO3\CMS\Core\Site\Entity\SiteInterface|null $site
     * @param array $linkDetails
     * @param array $queryParams
     * @return \Psr\Http\Message\UriInterface|null
     */
    protected function getUriFromCustomLinkDetails(array $redirectRecord, ?SiteInterface $site, array $linkDetails, array $queryParams): ?UriInterface
    {
        if (!isset($linkDetails['type'], $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']])) {
            return null;
        }

        /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);

        $controller = $this->bootFrontendController($site, $queryParams);
        /** @var \TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder $linkBuilder */
        $linkBuilder = $objectManager->get(
            $GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder'][$linkDetails['type']],
            $controller->cObj,
            $controller
        );
        try {
            $configuration = [
                'parameter' => (string) $redirectRecord['target'],
                'forceAbsoluteUrl' => true,
            ];
            if ($redirectRecord['force_https']) {
                $configuration['forceAbsoluteUrl.']['scheme'] = 'https';
            }
            if ($redirectRecord['keep_query_parameters']) {
                $configuration['additionalParams'] = HttpUtility::buildQueryString($queryParams, '&');
            }

            list($url) = $linkBuilder->build($linkDetails, '', '', $configuration);

            return new Uri($url);
        } catch (UnableToLinkException $exception) {
            try {
                $linkBuilderReflection = new \ReflectionClass($linkBuilder);
                $contentObjectRendererPropertyReflection = $linkBuilderReflection->getProperty('contentObjectRenderer');
                $contentObjectRendererPropertyReflection->setAccessible(true);
                /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObjectRenderer */
                $contentObjectRenderer = $contentObjectRendererPropertyReflection->getValue($linkBuilder);

                $url = $contentObjectRenderer->lastTypoLinkUrl;

                if (false === empty($url)) {
                    return new Uri($url);
                }
            } catch (\ReflectionException $e) {
                // nothing
            }

            return null;
        }
    }

    /**
     * @param \TYPO3\CMS\Core\Site\Entity\SiteInterface|null $site
     * @param array $queryParams
     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
     */
    protected function bootFrontendController(?SiteInterface $site, array $queryParams): TypoScriptFrontendController
    {
        $typoScriptFrontendController = parent::bootFrontendController($site, $queryParams);

        /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);

        if (false === is_array($typoScriptFrontendController->rootLine)) {
            $typoScriptFrontendController->rootLine = $objectManager->get(
                RootlineUtility::class,
                $typoScriptFrontendController->id
            )->get();
        }

        if (!$typoScriptFrontendController->sys_page instanceof PageRepository) {
            $typoScriptFrontendController->sys_page = $objectManager->get(PageRepository::class);
        }

        $GLOBALS['TSFE'] = $typoScriptFrontendController;

        if (!$GLOBALS['TSFE']->tmpl instanceof TemplateService) {
            /** @var \TYPO3\CMS\Core\TypoScript\TemplateService $templateService */
            $templateService = $objectManager->get(TemplateService::class);
            $templateService->start($typoScriptFrontendController->rootLine);

            $GLOBALS['TSFE']->tmpl = $templateService;
        }

        return $typoScriptFrontendController;
    }
}

#10 Updated by Benni Mack 18 days ago

Hey Daniel,

great input here. Thanks!

could you provide a patch for this change? (Except for booting up objectManager, can you elaborate why you needed to do this?)

Thanks.
Benni.

#11 Updated by Daniel Dorndorf 17 days ago

Hi Benni,

you are propably right, GeneralUtility::makeInstance should do the job.

For a patch I'm not 100% sure if it does not cause problems anywhere else.

In the DatabaseRecordLinkBuilder it always throws an exception but I dont know why this was necessary.
I'm missing some background information here :-/

In my extension I wrote a trait and xclassed all linkbuilder and the redirectservice so the module and the redirects are working, not sure if this causes problems somewhere else.

Also available in: Atom PDF