a94099059cc8ea43a4e424298d8d879872239595
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / RowUpdater / RteLinkSyntaxUpdater.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Install\Updates\RowUpdater;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
19 use TYPO3\CMS\Core\LinkHandling\Exception\UnknownUrnException;
20 use TYPO3\CMS\Core\LinkHandling\LinkService;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
23
24 /**
25 * Move '<link ...' syntax to '<a href' in rte fields
26 */
27 class RteLinkSyntaxUpdater implements RowUpdaterInterface
28 {
29 /**
30 * Table list with field list that may have links them
31 *
32 * @var array
33 */
34 protected $tableFieldListToConsider = [];
35
36 /**
37 * @var array Table names that should be ignored.
38 */
39 protected $blackListedTables = [
40 'sys_log',
41 'sys_history',
42 'sys_template',
43 ];
44
45 /**
46 * Regular expressions to match the <link ...>content</link> inside
47 * @var array
48 */
49 protected $regularExpressions = [
50 'default' => '#(?\'tag\'<link\\s+(?\'typolink\'[^>]+)>)(?\'content\'(?:(?!</link>).)*)</link>#msi',
51 'flex' => '#(?\'tag\'&lt;link\\s+(?\'typolink\'.+?)&gt;)(?\'content\'(?:(?!&lt;/link&gt;).)*)&lt;/link&gt;#msi'
52 ];
53
54 /**
55 * Get title
56 *
57 * @return string Title
58 */
59 public function getTitle(): string
60 {
61 return 'Scan for old "<link>" syntax in richtext and text fields and update to "<a href>"';
62 }
63
64 /**
65 * Return true if a table may have RTE fields
66 *
67 * @param string $tableName Table name to check
68 * @return bool True if this table potentially has RTE fields
69 */
70 public function hasPotentialUpdateForTable(string $tableName): bool
71 {
72 if (!is_array($GLOBALS['TCA'][$tableName])) {
73 throw new \RuntimeException(
74 'Globals TCA of ' . $tableName . ' must be an array',
75 1484173035
76 );
77 }
78 $result = false;
79 if (in_array($tableName, $this->blackListedTables, true)) {
80 return $result;
81 }
82 $tcaOfTable = $GLOBALS['TCA'][$tableName];
83 if (!is_array($tcaOfTable['columns'])) {
84 return $result;
85 }
86 foreach ($tcaOfTable['columns'] as $fieldName => $fieldConfiguration) {
87 if (isset($fieldConfiguration['config']['type'])
88 && in_array($fieldConfiguration['config']['type'], ['input', 'text', 'flex'], true)
89 ) {
90 $result = true;
91 if (!is_array($this->tableFieldListToConsider[$tableName])) {
92 $this->tableFieldListToConsider[$tableName] = [];
93 }
94 $this->tableFieldListToConsider[$tableName][] = $fieldName;
95 }
96 }
97 return $result;
98 }
99
100 /**
101 * Update "<link" tags in RTE fields
102 *
103 * @param string $tableName Table name
104 * @param array $row Given row data
105 * @return array Modified row data
106 */
107 public function updateTableRow(string $tableName, array $row): array
108 {
109 if (!is_array($this->tableFieldListToConsider)) {
110 throw new \RuntimeException(
111 'Parent should not call me with a table name I do not consider relevant for update',
112 1484173650
113 );
114 }
115 $fieldsToScan = $this->tableFieldListToConsider[$tableName];
116 foreach ($fieldsToScan as $fieldName) {
117 $row[$fieldName] = $this->transformLinkTagsIfFound(
118 $row[$fieldName],
119 $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'] === 'flex'
120 );
121 }
122 return $row;
123 }
124
125 /**
126 * Finds all <link> tags and calls the typolink codec service and the link service (twice) to get a string
127 * representation of the href part, and then builds an anchor tag.
128 *
129 * @param mixed $content The content to process
130 * @param bool $isFlexformField If true the content is htmlspecialchar()'d and must be treated as such
131 * @return mixed the modified content
132 */
133 protected function transformLinkTagsIfFound($content, $isFlexformField)
134 {
135 if (is_string($content)
136 && !empty($content)
137 && (stripos($content, '<link') !== false || stripos($content, '&lt;link') !== false)
138 ) {
139 $content = preg_replace_callback(
140 $this->regularExpressions[$isFlexformField ? 'flex' : 'default'],
141 function ($matches) use ($isFlexformField) {
142 $typoLink = $isFlexformField ? htmlspecialchars_decode($matches['typolink']) : $matches['typolink'];
143 $typoLinkParts = GeneralUtility::makeInstance(TypoLinkCodecService::class)->decode($typoLink);
144 $anchorTagAttributes = [
145 'target' => $typoLinkParts['target'],
146 'class' => $typoLinkParts['class'],
147 'title' => $typoLinkParts['title'],
148 ];
149
150 $link = $typoLinkParts['url'];
151 if (!empty($typoLinkParts['additionalParams'])) {
152 $link .= (strpos($link, '?') === false ? '?' : '&') . ltrim($typoLinkParts['additionalParams'], '&');
153 }
154
155 try {
156 $linkService = GeneralUtility::makeInstance(LinkService::class);
157 // Ensure the old syntax is converted to the new t3:// syntax, if necessary
158 $linkParts = $linkService->resolve($link);
159 $anchorTagAttributes['href'] = $linkService->asString($linkParts);
160 $newLink = '<a ' . GeneralUtility::implodeAttributes($anchorTagAttributes, true) . '>' .
161 ($isFlexformField ? htmlspecialchars_decode($matches['content']) : $matches['content']) .
162 '</a>';
163 if ($isFlexformField) {
164 $newLink = htmlspecialchars($newLink);
165 }
166 } catch (UnknownLinkHandlerException $e) {
167 $newLink = $matches[0];
168 } catch (UnknownUrnException $e) {
169 $newLink = $matches[0];
170 }
171
172 return $newLink;
173 },
174 $content
175 );
176 }
177 return $content;
178 }
179 }