[BUGIFX] Missing update wizard for RTE file links 02/22002/4
authorFrancois Suter <francois@typo3.org>
Sat, 6 Jul 2013 11:15:44 +0000 (13:15 +0200)
committerFrancois Suter <francois@typo3.org>
Tue, 9 Jul 2013 06:54:08 +0000 (08:54 +0200)
File links made inside a RTE-enabled field are currently not
migrated to FAL references. This patch adds such an update
wizard.

Resolves: #49233
Releases: 6.1, 6.2
Change-Id: I12b6d9da87b734c0a9d5f884d5067e3c9b08efc3
Reviewed-on: https://review.typo3.org/22002
Reviewed-by: Nico de Haen
Tested-by: Nico de Haen
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Sebastian Michaelsen
Tested-by: Sebastian Michaelsen
Reviewed-by: Francois Suter
Tested-by: Francois Suter
typo3/sysext/install/Classes/Updates/RteFileLinksUpdateWizard.php [new file with mode: 0644]
typo3/sysext/install/ext_localconf.php

diff --git a/typo3/sysext/install/Classes/Updates/RteFileLinksUpdateWizard.php b/typo3/sysext/install/Classes/Updates/RteFileLinksUpdateWizard.php
new file mode 100644 (file)
index 0000000..2c5787b
--- /dev/null
@@ -0,0 +1,322 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Francois Suter <francois@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Upgrade wizard that rewrites all file links to FAL references.
+ *
+ * The content string and the reference index (sys_refindex) are updated accordingly.
+ */
+class RteFileLinksUpdateWizard extends \TYPO3\CMS\Install\Updates\AbstractUpdate {
+
+       /**
+        * Title of the update wizard
+        * @var string
+        */
+       protected $title = 'Migrate all file links of RTE-enabled fields to FAL';
+
+       /**
+        * @var string Path the to fileadmin directory
+        */
+       protected $fileAdminDir;
+
+       /**
+        * The default storage
+        * @var \TYPO3\CMS\Core\Resource\ResourceStorage
+        */
+       protected $storage;
+
+       /**
+        * @var \TYPO3\CMS\Core\Html\RteHtmlParser
+        */
+       protected $rteHtmlParser;
+
+       /**
+        * Count of converted links
+        * @var integer
+        */
+       protected $convertedLinkCounter = 0;
+
+       /**
+        * Is DBAL installed or not (if not, we can use transactions)
+        * @var boolean
+        */
+       protected $isDbalInstalled = FALSE;
+
+       /**
+        * Array to store file conversion errors
+        * @var array
+        */
+       protected $errors = array();
+
+       /**
+        * List of update queries
+        * @var array
+        */
+       protected $queries = array();
+
+       /**
+        * Initialize some objects
+        *
+        * @return void
+        */
+       public function init() {
+               $this->rteHtmlParser = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\RteHtmlParser');
+               /** @var $storageRepository \TYPO3\CMS\Core\Resource\StorageRepository */
+               $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
+               $storages = $storageRepository->findAll();
+               $this->storage = $storages[0];
+               $this->fileAdminDir = $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'];
+               // Check if DBAL is installed or not
+               $this->isDbalInstalled = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal');
+       }
+
+       /**
+        * Checks if an update is needed
+        *
+        * @param string $description The description for the update
+        * @return boolean TRUE if an update is needed, FALSE otherwise
+        */
+       public function checkForUpdate(&$description) {
+               $description = 'This update wizard goes through all file links in all rich-text fields and changes them to FAL references.';
+               $description .= 'If the process times out, please run it again.';
+               // Issue warning about sys_refindex needing to be up to date
+               /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $message */
+               $message = GeneralUtility::makeInstance(
+                       'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
+                       '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.',
+                       'Updating the reference index',
+                       \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
+               );
+               $description .= $message->render();
+
+               // Confirm activation only if some old-style links are found
+               $oldRecords = $this->findOldLinks();
+               if (count($oldRecords) > 0) {
+                       $description .= '<br />There are currently <strong>' . count($oldRecords) . '</strong> links to update.<br />';
+                       return TRUE;
+               }
+
+               // No update needed, disable the wizard
+               return FALSE;
+       }
+
+       /**
+        * Performs the database update.
+        *
+        * @param array $dbQueries queries done in this update
+        * @param mixed $customMessages custom messages
+        * @return boolean TRUE on success, FALSE on error
+        */
+       public function performUpdate(array &$dbQueries, &$customMessages) {
+               $this->init();
+
+               // Make sure we have a storage
+               if (!$this->storage) {
+                       $customMessages = 'No file resource storage found';
+                       return FALSE;
+               }
+
+               // Get the references and migrate them
+               $records = $this->findOldLinks();
+               foreach ($records as $singleRecord) {
+                       $this->migrateRecord($singleRecord);
+               }
+               $dbQueries = $this->queries;
+
+               if (count($this->errors) > 0) {
+                       foreach ($this->errors as $errorMessage) {
+                               $message = GeneralUtility::makeInstance(
+                                       'TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
+                                       $errorMessage,
+                                       '',
+                                       \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
+                               );
+                               /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $message */
+                               $customMessages .= '<br />' . $message->render();
+                       }
+                       if ($this->convertedLinkCounter == 0) {
+                                       // no links converted only missing files: UPDATE was not successful
+                               return FALSE;
+                       }
+               }
+
+               if ($this->convertedLinkCounter > 0) {
+                       $customMessages = $this->convertedLinkCounter . ' links converted.<br />' . $customMessages;
+               } else {
+                       $customMessages .= 'No file links found';
+               }
+               return TRUE;
+       }
+
+       /**
+        *  Processes each record and updates the database
+        *
+        * @param array $reference Reference to a record from sys_refindex
+        * @return void
+        */
+       protected function migrateRecord(array $reference) {
+               // Get the current record based on the sys_refindex information
+               $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
+                       'uid, ' . $reference['field'],
+                       $reference['tablename'],
+                       'uid = ' . $reference['recuid']
+               );
+               if ($record !== NULL) {
+                       $this->convertFileLinks($reference, $record);
+               } else {
+                       // Original record could not be found (happens if sys_refindex is not up to date), issue error
+                       $this->errors[] = 'Original record not found for reference to element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . '. Not migrated.';
+               }
+       }
+
+       /**
+        * The actual transformation of the links
+        * pretty similar to TS_links_rte in RteHtmlParser
+        *
+        * @param array $reference sys_refindex information
+        * @param array $record Original record pointed to by the sys_refindex reference
+        * @return void
+        */
+       protected function convertFileLinks(array $reference, array $record) {
+               // First of all, try to get the referenced file. Continue only if found.
+               $fileObject = $this->fetchReferencedFile($reference['ref_string'], $reference);
+               if ($fileObject instanceof \TYPO3\CMS\Core\Resource\AbstractFile) {
+                       // Next, match the reference path in the content to be sure it's present inside a <link> tag
+                       $content = $record[$reference['field']];
+                       $regularExpression = '$<(link ' . $reference['ref_string'] . ').*>$';
+                       $matches = array();
+                       $result = preg_match($regularExpression, $content, $matches);
+                       if ($result) {
+                               // Replace the file path with the file reference
+                               $modifiedContent = str_replace(
+                                       $matches[1],
+                                       'link file:' . $fileObject->getUid(),
+                                       $record[$reference['field']]
+                               );
+                               // Save the changes and stop looping
+                               $this->saveChanges($modifiedContent, $reference, $fileObject);
+                               $this->convertedLinkCounter++;
+                       } else {
+                               $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.';
+                       }
+               }
+       }
+
+       /**
+        * Tries to fetch the file object corresponding to the given path.
+        *
+        * @param string $path Path to a file (starting with "fileadmin/")
+        * @param array $reference Corresponding sys_refindex entry
+        * @return null|\TYPO3\CMS\Core\Resource\FileInterface
+        */
+       protected function fetchReferencedFile($path, array $reference) {
+               $fileObject = NULL;
+               if (@file_exists(PATH_site . '/' . $path)) {
+                       try {
+                               $fileObject = $this->storage->getFile(
+                                       '/' . str_replace(
+                                               $this->fileAdminDir,
+                                               '',
+                                               $path
+                                       )
+                               );
+                       } catch (\TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException $notFoundException) {
+                               // This should really not happen, since we are testing existence of the file just before
+                               $this->errors[] = $path . ' not found (referenced in element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . ')';
+                       }
+               } else {
+                       // Nothing to be done if file not found
+                       $this->errors[] = $path . ' not found (referenced in element ' . $reference['recuid'] . ' of table ' . $reference['tablename'] . ' in field ' . $reference['field'] . ')';
+               }
+               return $fileObject;
+       }
+
+       /**
+        * Saves the modified content to the database and updates the sys_refindex accordingly.
+        *
+        * @param string $modifiedText Original content with the file links replaced
+        * @param array $reference sys_refindex record
+        * @param \TYPO3\CMS\Core\Resource\AbstractFile $file
+        * @return void
+        */
+       protected function saveChanges($modifiedText, array $reference, $file) {
+
+               // If DBAL is not installed, we can start a transaction before saving
+               // This ensures that a possible time out doesn't break the database integrity
+               // by occurring between the two needed DB writes.
+               if (!$this->isDbalInstalled) {
+                       $GLOBALS['TYPO3_DB']->sql_query('START TRANSACTION');
+               }
+
+               // Save the changed field
+               $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
+                       $reference['tablename'],
+                       'uid = ' . $reference['recuid'],
+                       array(
+                               $reference['field'] => $modifiedText
+                       )
+               );
+               $this->queries[] = htmlspecialchars(str_replace(LF, ' ', $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery));
+
+               // Finally, update the sys_refindex table as well
+               $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
+                       'sys_refindex',
+                       'hash = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($reference['hash'], 'sys_refindex'),
+                       array(
+                               'ref_table'  => 'sys_file',
+                               'ref_uid'    => $file->getUid(),
+                               'ref_string' => ''
+                       )
+               );
+               $this->queries[] = str_replace(LF, ' ', $GLOBALS['TYPO3_DB']->debug_lastBuiltQuery);
+
+               // Confirm the transaction
+               if (!$this->isDbalInstalled) {
+                       $GLOBALS['TYPO3_DB']->sql_query('COMMIT');
+               }
+       }
+
+       /**
+        * Use sys_refindex to find all links to "old" files in typolink tags.
+        *
+        * This will find any RTE-enabled field.
+        *
+        * @return array Entries from sys_refindex
+        */
+       protected function findOldLinks() {
+               $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'hash, tablename, recuid, field, ref_table, ref_uid, ref_string',
+                       'sys_refindex',
+                       'softref_key = \'typolink_tag\' AND ref_table = \'_FILE\' '
+               );
+               return $records;
+       }
+
+}
+
+?>
\ No newline at end of file
index b5522c5..ec39175 100644 (file)
@@ -42,6 +42,7 @@ $TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['update']['referenceIntegrity'] =
 
 $TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['update']['sysext_file_filemounts'] = 'TYPO3\\CMS\\Install\\Updates\\FilemountUpdateWizard';
 $TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['update']['sysext_file_rtemagicimages'] = 'TYPO3\\CMS\\Install\\Updates\\RteMagicImagesUpdateWizard';
+$TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['update']['sysext_file_rtefilelinks'] = 'TYPO3\\CMS\\Install\\Updates\\RteFileLinksUpdateWizard';
 
 // Version 4.7: Migrate the flexforms of MediaElement
 $TYPO3_CONF_VARS['SC_OPTIONS']['ext/install']['update']['mediaElementFlexform'] = 'TYPO3\\CMS\\Install\\CoreUpdates\\MediaFlexformUpdate';