TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Frontend\Typolink;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Page\DefaultJavaScriptAssetTrait;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Builds a TypoLink to an email address, also takes care of additional functionality for the time being
|
||||
* such as the infamous config.spamProtectedEmailAddresses option.
|
||||
*/
|
||||
class EmailLinkBuilder implements LoggerAwareInterface, TypolinkBuilderInterface
|
||||
{
|
||||
use DefaultJavaScriptAssetTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly LinkService $linkService,
|
||||
) {}
|
||||
|
||||
public function buildLink(
|
||||
array $linkDetails,
|
||||
array $configuration,
|
||||
ServerRequestInterface $request,
|
||||
string $linkText = '',
|
||||
): LinkResultInterface {
|
||||
[$url, $linkText, $attributes] = $this->processEmailLink($linkDetails['email'], $linkText, $linkDetails, $request);
|
||||
return (new LinkResult(LinkService::TYPE_EMAIL, $url))
|
||||
->withTarget($linkDetails['target'] ?? '')
|
||||
->withLinkConfiguration($configuration)
|
||||
->withLinkText($linkText)
|
||||
->withAttributes($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a href attribute for given $mailAddress.
|
||||
* The function uses spamProtectEmailAddresses for encoding the mailto statement.
|
||||
* If spamProtectEmailAddresses is disabled, it'll just return a string like "mailto:user@example.tld".
|
||||
*
|
||||
* Returns an array with three items (numeric index)
|
||||
* #0: $mailToUrl (string), ready to be inserted into the href attribute of the <a> tag
|
||||
* #1: $linkText (string), content between starting and ending `<a>` tag
|
||||
* #2: $attributes (array<string, string>), additional attributes for `<a>` tag
|
||||
*
|
||||
* @param string $mailAddress Email address
|
||||
* @param string $linkText Link text, default will be the email address.
|
||||
* @return array{0: string, 1: string, 2: array<string, string>} A numerical array with three items
|
||||
* @internal this method is not part of TYPO3's public API
|
||||
*/
|
||||
public function processEmailLink(string $mailAddress, string $linkText, array $linkDetails, ServerRequestInterface $request): array
|
||||
{
|
||||
$linkText = $linkText ?: htmlspecialchars($mailAddress);
|
||||
$attributes = [];
|
||||
if ($linkDetails !== []) {
|
||||
// Ensure to add also additional query parameters to the string
|
||||
$mailToUrl = $this->linkService->asString($linkDetails);
|
||||
} else {
|
||||
$mailToUrl = 'mailto:' . $mailAddress;
|
||||
}
|
||||
|
||||
// no processing happened, therefore, the default processing kicks in
|
||||
$frontendTypoScriptConfigArray = $request->getAttribute('frontend.typoscript')?->getConfigArray();
|
||||
$spamProtectEmailAddresses = (int)($frontendTypoScriptConfigArray['spamProtectEmailAddresses'] ?? 0);
|
||||
$spamProtectEmailAddresses = MathUtility::forceIntegerInRange($spamProtectEmailAddresses, -10, 10);
|
||||
|
||||
if ($spamProtectEmailAddresses !== 0) {
|
||||
$mailToUrl = $this->encryptEmail($mailToUrl, $spamProtectEmailAddresses);
|
||||
$attributes = [
|
||||
'data-mailto-token' => $mailToUrl,
|
||||
'data-mailto-vector' => $spamProtectEmailAddresses,
|
||||
];
|
||||
$mailToUrl = '#';
|
||||
$this->addDefaultFrontendJavaScript($request);
|
||||
$atLabel = '(at)';
|
||||
if (($atLabelFromConfig = trim($frontendTypoScriptConfigArray['spamProtectEmailAddresses_atSubst'] ?? '')) !== '') {
|
||||
$atLabel = $atLabelFromConfig;
|
||||
}
|
||||
$spamProtectedMailAddress = str_replace('@', $atLabel, htmlspecialchars($mailAddress));
|
||||
if ($frontendTypoScriptConfigArray['spamProtectEmailAddresses_lastDotSubst'] ?? false) {
|
||||
$lastDotLabel = trim($frontendTypoScriptConfigArray['spamProtectEmailAddresses_lastDotSubst']);
|
||||
$lastDotLabel = $lastDotLabel ?: '(dot)';
|
||||
$spamProtectedMailAddress = preg_replace('/\\.([^\\.]+)$/', $lastDotLabel . '$1', $spamProtectedMailAddress);
|
||||
if ($spamProtectedMailAddress === null) {
|
||||
$this->logger->debug('Error replacing the last dot in email address "{email}"', ['email' => $spamProtectedMailAddress]);
|
||||
$spamProtectedMailAddress = '';
|
||||
}
|
||||
}
|
||||
$linkText = str_ireplace(htmlspecialchars($mailAddress), $spamProtectedMailAddress, $linkText);
|
||||
}
|
||||
|
||||
return [$mailToUrl, $linkText, $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption of email addresses for <A>-tags See the spam protection setup in TS 'config.'
|
||||
*
|
||||
* @param string $string Input string to en/decode: "mailto:some@example.com
|
||||
* @param int $offset a number between -10 and 10, taken from config.spamProtectEmailAddresses
|
||||
* @return string encoded version of $string
|
||||
*/
|
||||
protected function encryptEmail(string $string, int $offset): string
|
||||
{
|
||||
$out = '';
|
||||
// like str_rot13() but with a variable offset and a wider character range
|
||||
$len = strlen($string);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$charValue = ord($string[$i]);
|
||||
// 0-9 . , - + / :
|
||||
if ($charValue >= 43 && $charValue <= 58) {
|
||||
$out .= $this->encryptCharcode($charValue, 43, 58, $offset);
|
||||
} elseif ($charValue >= 64 && $charValue <= 90) {
|
||||
// A-Z @
|
||||
$out .= $this->encryptCharcode($charValue, 64, 90, $offset);
|
||||
} elseif ($charValue >= 97 && $charValue <= 122) {
|
||||
// a-z
|
||||
$out .= $this->encryptCharcode($charValue, 97, 122, $offset);
|
||||
} else {
|
||||
$out .= $string[$i];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption (or decryption) of a single character.
|
||||
* Within the given range the character is shifted with the supplied offset.
|
||||
*
|
||||
* @param int $n Ordinal of input character
|
||||
* @param int $start Start of range
|
||||
* @param int $end End of range
|
||||
* @param int $offset Offset
|
||||
* @return string encoded/decoded version of character
|
||||
*/
|
||||
protected function encryptCharcode(int $n, int $start, int $end, int $offset): string
|
||||
{
|
||||
$n = $n + $offset;
|
||||
if ($offset > 0 && $n > $end) {
|
||||
$n = $start + ($n - $end - 1);
|
||||
} elseif ($offset < 0 && $n < $start) {
|
||||
$n = $end - ($start - $n - 1);
|
||||
}
|
||||
return chr($n);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user