[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / RteFileLinksUpdateWizard.php
1 <?php
2 namespace TYPO3\CMS\Install\Updates;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Upgrade wizard that rewrites all file links to FAL references.
21 *
22 * The content string and the reference index (sys_refindex) are updated accordingly.
23 */
24 class RteFileLinksUpdateWizard extends AbstractUpdate {
25
26 /**
27 * Title of the update wizard
28 * @var string
29 */
30 protected $title = 'Migrate all file links of RTE-enabled fields to FAL';
31
32 /**
33 * @var string Path the to fileadmin directory
34 */
35 protected $fileAdminDir;
36
37 /**
38 * The default storage
39 * @var \TYPO3\CMS\Core\Resource\ResourceStorage
40 */
41 protected $storage;
42
43 /**
44 * @var \TYPO3\CMS\Core\Html\RteHtmlParser
45 */
46 protected $rteHtmlParser;
47
48 /**
49 * Count of converted links
50 * @var integer
51 */
52 protected $convertedLinkCounter = 0;
53
54 /**
55 * Is DBAL installed or not (if not, we can use transactions)
56 * @var boolean
57 */
58 protected $isDbalInstalled = FALSE;
59
60 /**
61 * Array to store file conversion errors
62 * @var array
63 */
64 protected $errors = array();
65
66 /**
67 * List of update queries
68 * @var array
69 */
70 protected $queries = array();
71
72 /**
73 * Initialize some objects
74 *
75 * @return void
76 */
77 public function init() {
78 $this->rteHtmlParser = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\RteHtmlParser');
79 /** @var $storageRepository \TYPO3\CMS\Core\Resource\StorageRepository */
80 $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
81 $storages = $storageRepository->findAll();
82 $this->storage = $storages[0];
83 $this->fileAdminDir = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'];
84 // Check if DBAL is installed or not
85 $this->isDbalInstalled = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal');
86 }
87
88 /**
89 * Checks if an update is needed
90 *
91 * @param string $description The description for the update
92 * @return boolean TRUE if an update is needed, FALSE otherwise
93 */
94 public function checkForUpdate(&$description) {
95 $description = 'This update wizard goes through all file links in all rich-text fields and changes them to FAL references.';
96 $description .= 'If the process times out, please run it again.';
97 // Issue warning about sys_refindex needing to be up to date
98 /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $message */
99 $message = GeneralUtility::makeInstance(
100 'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
101 'This script bases itself on the references contained in the general reference index (sys_refindex). It is strongly advised to update it before running this wizard.',
102 'Updating the reference index',
103 \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
104 );
105 $description .= $message->render();
106
107 // Confirm activation only if some old-style links are found
108 $oldRecords = $this->findOldLinks();
109 if (count($oldRecords) > 0) {
110 $description .= '<br />There are currently <strong>' . count($oldRecords) . '</strong> links to update.<br />';
111 return TRUE;
112 }
113
114 // No update needed, disable the wizard
115 return FALSE;
116 }
117
118 /**
119 * Performs the database update.
120 *
121 * @param array $dbQueries queries done in this update
122 * @param mixed $customMessages custom messages
123 * @return boolean TRUE on success, FALSE on error
124 */
125 public function performUpdate(array &$dbQueries, &$customMessages) {
126 $this->init();
127
128 // Make sure we have a storage
129 if (!$this->storage) {
130 $customMessages = 'No file resource storage found';
131 return FALSE;
132 }
133
134 // Get the references and migrate them
135 $records = $this->findOldLinks();
136 foreach ($records as $singleRecord) {
137 $this->migrateRecord($singleRecord);
138 }
139 $dbQueries = $this->queries;
140
141 if (count($this->errors) > 0) {
142 $customMessages .= implode(PHP_EOL, $this->errors);
143 if ($this->convertedLinkCounter == 0) {
144 // no links converted only missing files: UPDATE was not successful
145 return FALSE;
146 }
147 }
148
149 if ($this->convertedLinkCounter > 0) {
150 $customMessages = $this->convertedLinkCounter . ' links converted.' . PHP_EOL . $customMessages;
151 } else {
152 $customMessages .= 'No file links found';
153 }
154 return TRUE;
155 }
156
157 /**
158 * Processes each record and updates the database
159 *
160 * @param array $reference Reference to a record from sys_refindex
161 * @return void
162 */
163 protected function migrateRecord(array $reference) {
164 // Get the current record based on the sys_refindex information
165 $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
166 'uid, ' . $reference['field'],
167 $reference['tablename'],
168 'uid = ' . $reference['recuid']
169 );
170 if ($record !== NULL) {
171 $this->convertFileLinks($reference, $record);
172 } else {
173 // Original record could not be found (happens if sys_refindex is not up to date), issue error
174 $this->errors[] = 'Original record not found for reference to element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . '. Not migrated.';
175 }
176 }
177
178 /**
179 * The actual transformation of the links
180 * pretty similar to TS_links_rte in RteHtmlParser
181 *
182 * @param array $reference sys_refindex information
183 * @param array $record Original record pointed to by the sys_refindex reference
184 * @return void
185 */
186 protected function convertFileLinks(array $reference, array $record) {
187 // First of all, try to get the referenced file. Continue only if found.
188 try {
189 $fileObject = $this->fetchReferencedFile($reference['ref_string'], $reference);
190 } catch (\InvalidArgumentException $exception) {
191 $fileObject = NULL;
192 $this->errors[] = $reference['ref_string'] . ' could not be replaced. File does not exist.';
193 }
194 if ($fileObject instanceof \TYPO3\CMS\Core\Resource\AbstractFile) {
195 // Next, match the reference path in the content to be sure it's present inside a <link> tag
196 $content = $record[$reference['field']];
197 $regularExpression = '$<((link|LINK) ' . str_replace(' ', '%20', $reference['ref_string']) . ').*>$';
198 $matches = array();
199 $result = preg_match($regularExpression, $content, $matches);
200 if ($result) {
201 // Replace the file path with the file reference
202 $modifiedContent = str_replace(
203 $matches[1],
204 'link file:' . $fileObject->getUid(),
205 $record[$reference['field']]
206 );
207 // Save the changes and stop looping
208 $this->saveChanges($modifiedContent, $reference, $fileObject);
209 $this->convertedLinkCounter++;
210 } else {
211 $this->errors[] = $reference['ref_string'] . ' not found in referenced element (uid: ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . '). Reference index was probably out of date.';
212 }
213 }
214 }
215
216 /**
217 * Tries to fetch the file object corresponding to the given path.
218 *
219 * @param string $path Path to a file (starting with "fileadmin/")
220 * @param array $reference Corresponding sys_refindex entry
221 * @return null|\TYPO3\CMS\Core\Resource\FileInterface
222 */
223 protected function fetchReferencedFile($path, array $reference) {
224 $fileObject = NULL;
225 if (@file_exists(PATH_site . '/' . $path)) {
226 try {
227 $fileObject = $this->storage->getFile(
228 '/' . str_replace(
229 $this->fileAdminDir,
230 '',
231 $path
232 )
233 );
234 } catch (\TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException $notFoundException) {
235 // This should really not happen, since we are testing existence of the file just before
236 $this->errors[] = $path . ' not found (referenced in element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . ')';
237 }
238 } else {
239 // Nothing to be done if file not found
240 $this->errors[] = $path . ' not found (referenced in element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . ')';
241 }
242 return $fileObject;
243 }
244
245 /**
246 * Saves the modified content to the database and updates the sys_refindex accordingly.
247 *
248 * @param string $modifiedText Original content with the file links replaced
249 * @param array $reference sys_refindex record
250 * @param \TYPO3\CMS\Core\Resource\AbstractFile $file
251 * @return void
252 */
253 protected function saveChanges($modifiedText, array $reference, $file) {
254
255 // If DBAL is not installed, we can start a transaction before saving
256 // This ensures that a possible time out doesn't break the database integrity
257 // by occurring between the two needed DB writes.
258 if (!$this->isDbalInstalled) {
259 $GLOBALS['TYPO3_DB']->sql_query('START TRANSACTION');
260 }
261
262 // Save the changed field
263 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
264 $reference['tablename'],
265 'uid = ' . $reference['recuid'],
266 array(
267 $reference['field'] => $modifiedText
268 )
269 );
270 $this->queries[] = htmlspecialchars(str_replace(LF, ' ', $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery));
271
272 // Finally, update the sys_refindex table as well
273 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
274 'sys_refindex',
275 'hash = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($reference['hash'], 'sys_refindex'),
276 array(
277 'ref_table' => 'sys_file',
278 'ref_uid' => $file->getUid(),
279 'ref_string' => ''
280 )
281 );
282 $this->queries[] = str_replace(LF, ' ', $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery);
283
284 // Confirm the transaction
285 if (!$this->isDbalInstalled) {
286 $GLOBALS['TYPO3_DB']->sql_query('COMMIT');
287 }
288 }
289
290 /**
291 * Use sys_refindex to find all links to "old" files in typolink tags.
292 *
293 * This will find any RTE-enabled field.
294 *
295 * @return array Entries from sys_refindex
296 */
297 protected function findOldLinks() {
298 $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
299 'hash, tablename, recuid, field, ref_table, ref_uid, ref_string',
300 'sys_refindex',
301 'softref_key = \'typolink_tag\' AND ref_table = \'_FILE\' '
302 );
303 return $records;
304 }
305
306 }