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 tag * #1: $linkText (string), content between starting and ending `` tag * #2: $attributes (array), additional attributes for `` 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} 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 -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); } }