Skip to content

Commit

Permalink
[BUGFIX] Process t3:// link resources correctly
Browse files Browse the repository at this point in the history
* t3://email?email=oliver@typo3.org (be greedy about missing mailto:)
* t3://file?identifier=1:/logo.png (not implemented since no integer)

Besides that according test cases are added in order to ensure the
basic behavior of link handling in a TypoScript frontend rendering
scenario using t3:// link resources.

Resolves: #88960
Releases: master, 9.5, 8.7
Change-Id: I9a1f47f2eaaacc4368a1ca3e1a4006a8248e654e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61498
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Oliver Klee <typo3-coding@oliverklee.de>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
  • Loading branch information
ohader authored and NeoBlack committed Aug 15, 2019
1 parent 59abeaa commit 89a71d6
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 13 deletions.
5 changes: 4 additions & 1 deletion typo3/sysext/core/Classes/LinkHandling/EmailLinkHandler.php
Expand Up @@ -41,6 +41,9 @@ public function asString(array $parameters): string
*/
public function resolveHandlerData(array $data): array
{
return ['email' => substr($data['email'], 7)];
if (stripos($data['email'], 'mailto:') === 0) {
return ['email' => substr($data['email'], 7)];
}
return ['email' => $data['email']];
}
}
24 changes: 18 additions & 6 deletions typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php
Expand Up @@ -15,6 +15,7 @@
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;

Expand Down Expand Up @@ -70,19 +71,30 @@ public function asString(array $parameters): string
*/
public function resolveHandlerData(array $data): array
{
if (isset($data['uid'])) {
$fileId = $data['uid'];
} else {
$fileId = $data['identifier'];
}
try {
$file = $this->getResourceFactory()->getFileObject($fileId);
$file = $this->resolveFile($data);
} catch (FileDoesNotExistException $e) {
$file = null;
}
return ['file' => $file];
}

/**
* @param array $data
* @return FileInterface|null
* @throws FileDoesNotExistException
*/
protected function resolveFile(array $data): ?FileInterface
{
if (isset($data['uid'])) {
return $this->getResourceFactory()->getFileObject($data['uid']);
}
if (isset($data['identifier'])) {
return $this->getResourceFactory()->getFileObjectFromCombinedIdentifier($data['identifier']);
}
return null;
}

/**
* Initializes the resource factory (only once)
*
Expand Down
21 changes: 15 additions & 6 deletions typo3/sysext/core/Tests/Unit/LinkHandling/EmailLinkHandlerTest.php
Expand Up @@ -28,23 +28,32 @@ class EmailLinkHandlerTest extends UnitTestCase
public function resolveParametersForNonFilesDataProvider()
{
return [
'email without protocol' => [
[
'email' => 'one@example.com'
],
[
'email' => 'one@example.com'
],
'mailto:one@example.com'
],
'email with protocol' => [
[
'email' => 'mailto:one@love.com'
'email' => 'mailto:one@example.com'
],
[
'email' => 'one@love.com'
'email' => 'one@example.com'
],
'mailto:one@love.com'
'mailto:one@example.com'
],
'email with protocol 2' => [
[
'email' => 'mailto:info@typo3.org'
'email' => 'mailto:info@example.org'
],
[
'email' => 'info@typo3.org'
'email' => 'info@example.org'
],
'mailto:info@typo3.org'
'mailto:info@example.org'
],
];
}
Expand Down
Expand Up @@ -84,6 +84,7 @@ public function resolveFileReferencesToSplitParameters($input, $expected, $final
// fake methods to return proper objects
$fileObject = new File(['identifier' => $expected['file'], 'name' => 'foobar.txt'], $storage);
$factory->expects($this->any())->method('getFileObject')->with($expected['file'])->willReturn($fileObject);
$factory->expects($this->any())->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])->willReturn($fileObject);
$expected['file'] = $fileObject;

/** @var FileLinkHandler|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $subject */
Expand Down
250 changes: 250 additions & 0 deletions typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkTest.php
@@ -0,0 +1,250 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\Index\Indexer;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\TypoScript\TemplateService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures\LinkHandlingController;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\ArrayValueInstruction;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\TypoScriptInstruction;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;

/**
* Test case for build URLs with TypoLink via Frontend Request.
*/
class TypoLinkTest extends AbstractTestCase
{
/**
* @var string
*/
private $siteTitle = 'A Company that Manufactures Everything Inc';

/**
* @var InternalRequestContext
*/
private $internalRequestContext;

protected $pathsToProvideInTestInstance = [
'typo3/sysext/backend/Resources/Public/Images/Logo.png' => 'fileadmin/logo.png'
];

public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
static::initializeDatabaseSnapshot();
}

public static function tearDownAfterClass(): void
{
static::destroyDatabaseSnapshot();
parent::tearDownAfterClass();
}

protected function setUp(): void
{
parent::setUp();

// these settings are forwarded to the frontend sub-request as well
$this->internalRequestContext = (new InternalRequestContext())
->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);

$this->writeSiteConfiguration(
'acme-com',
$this->buildSiteConfiguration(1000, 'https://acme.com/'),
[
$this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
$this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
$this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
]
);

$this->withDatabaseSnapshot(function () {
$this->setUpDatabase();
});
}

protected function setUpDatabase()
{
$backendUser = $this->setUpBackendUserFromFixture(1);
Bootstrap::initializeLanguageObject();

$scenarioFile = __DIR__ . '/Fixtures/SlugScenario.yaml';
$factory = DataHandlerFactory::fromYamlFile($scenarioFile);
$writer = DataHandlerWriter::withBackendUser($backendUser);
$writer->invokeFactory($factory);
static::failIfArrayIsNotEmpty(
$writer->getErrors()
);

// @todo Provide functionality of assigning TSconfig to Testing Framework
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('pages');
/** @var $connection \TYPO3\CMS\Core\Database\Connection */
$connection->update(
'pages',
['TSconfig' => implode(chr(10), [
'TCEMAIN.linkHandler.content {',
' configuration.table = tt_content',
'}',
])],
['uid' => 1000]
);

$this->setUpFileStorage();
$this->setUpFrontendRootPage(
1000,
[
'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
],
[
'title' => 'ACME Root',
'sitetitle' => $this->siteTitle,
]
);
}

/**
* @todo Provide functionality of creating and indexing fileadmin/ in Testing Framework
*/
private function setUpFileStorage()
{
$storageRepository = new StorageRepository();
$storageId = $storageRepository->createLocalStorage(
'fileadmin/ (auto-created)',
'fileadmin/',
'relative',
'Default storage created in TypoLinkTest',
true
);
$storage = $storageRepository->findByUid($storageId);
(new Indexer($storage))->processChangesInStorages();
}

protected function tearDown(): void
{
unset($this->internalRequestContext);
parent::tearDown();
}

/**
* @return array
*/
public function linkIsGeneratedDataProvider(): array
{
$instructions = [
[
't3://email?email=mailto:user@example.org&other=other#other',
'<a href="mailto:user@example.org">user@example.org</a>',
],
[
't3://email?email=user@example.org&other=other#other',
'<a href="mailto:user@example.org">user@example.org</a>',
],
[
't3://file?uid=1&type=1&other=other#other',
'<a href="/fileadmin/logo.png">fileadmin/logo.png</a>',
],
[
't3://file?identifier=1:/logo.png&other=other#other',
'<a href="/fileadmin/logo.png">fileadmin/logo.png</a>',
],
[
't3://file?identifier=fileadmin/logo.png&other=other#other',
'<a href="/fileadmin/logo.png">fileadmin/logo.png</a>',
],
[
't3://folder?identifier=fileadmin&other=other#other',
'<a href="/fileadmin/">fileadmin/</a>',
],
[
't3://page?uid=1200&type=1&param-a=a&param-b=b#fragment',
'<a href="/features?param-a=a&amp;param-b=b&amp;type=1&amp;cHash=92aa5284d0ad18f7934fe94b52f6c1a5#fragment">EN: Features</a>',
],
[
't3://record?identifier=content&uid=10001&other=other#fragment',
'<a href="/features#c10001">EN: Features</a>',
],
[
't3://url?url=https://typo3.org&other=other#other',
'<a href="https://typo3.org">https://typo3.org</a>',
],
];
return $this->keysFromTemplate($instructions, '%1$s;');
}

/**
* @param string $parameter
* @param string $expectation
*
* @test
* @dataProvider linkIsGeneratedDataProvider
*/
public function linkIsGenerated(string $parameter, string $expectation)
{
$sourcePageId = 1100;

$response = $this->executeFrontendRequest(
(new InternalRequest('https://acme.us/'))
->withPageId($sourcePageId)
->withInstructions([
(new TypoScriptInstruction(TemplateService::class))->withTypoScript([
'config.' => [
'recordLinks.' => [
'content.' => [
'forceLink' => 1,
'typolink.' => [
'parameter' => 1200,
'section.' => [
'data' => 'field:uid',
'wrap' => 'c|',
],
],
],
],
],
]),
$this->createTypoLinkInstruction([
'parameter' => $parameter,
])
]),
$this->internalRequestContext
);

static::assertSame($expectation, (string)$response->getBody());
}

/**
* @param array $typoLink
* @return ArrayValueInstruction
*/
private function createTypoLinkInstruction(array $typoLink): ArrayValueInstruction
{
return (new ArrayValueInstruction(LinkHandlingController::class))
->withArray([
'10' => 'TEXT',
'10.' => [
'typolink.' => $typoLink
]
]);
}
}

0 comments on commit 89a71d6

Please sign in to comment.