[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / TceformsUpdateWizard.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\Database\DatabaseConnection;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Upgrade wizard which goes through all files referenced in the tt_content.image filed
22 * and creates sys_file records as well as sys_file_reference records for the individual usages.
23 *
24 * @author Ingmar Schlecht <ingmar@typo3.org>
25 */
26 class TceformsUpdateWizard extends AbstractUpdate {
27
28 /**
29 * Number of records fetched per database query
30 * Used to prevent memory overflows for huge databases
31 */
32 const RECORDS_PER_QUERY = 1000;
33
34 /**
35 * @var string
36 */
37 protected $title = 'Migrate all file relations from tt_content.image and pages.media';
38
39 /**
40 * @var \TYPO3\CMS\Core\Resource\ResourceStorage
41 */
42 protected $storage;
43
44 /**
45 * @var \TYPO3\CMS\Core\Log\Logger
46 */
47 protected $logger;
48
49 /**
50 * @var DatabaseConnection
51 */
52 protected $database;
53
54 /**
55 * Table fields to migrate
56 * @var array
57 */
58 protected $tables = array(
59 'tt_content' => array(
60 'image' => array(
61 'sourcePath' => 'uploads/pics/',
62 // Relative to fileadmin
63 'targetPath' => '_migrated/pics/',
64 'titleTexts' => 'titleText',
65 'captions' => 'imagecaption',
66 'links' => 'image_link',
67 'alternativeTexts' => 'altText'
68 )
69 ),
70 'pages' => array(
71 'media' => array(
72 'sourcePath' => 'uploads/media/',
73 // Relative to fileadmin
74 'targetPath' => '_migrated/media/'
75 )
76 ),
77 'pages_language_overlay' => array(
78 'media' => array(
79 'sourcePath' => 'uploads/media/',
80 // Relative to fileadmin
81 'targetPath' => '_migrated/media/'
82 )
83 )
84 );
85
86 /**
87 * Constructor
88 */
89 public function __construct() {
90 /** @var $logManager \TYPO3\CMS\Core\Log\LogManager */
91 $logManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Log\\LogManager');
92 $this->logger = $logManager->getLogger(__CLASS__);
93 $this->database = $GLOBALS['TYPO3_DB'];
94 }
95
96 /**
97 * Initialize the storage repository.
98 */
99 public function init() {
100 /** @var $storageRepository \TYPO3\CMS\Core\Resource\StorageRepository */
101 $storageRepository = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
102 $storages = $storageRepository->findAll();
103 $this->storage = $storages[0];
104 }
105
106 /**
107 * Checks if an update is needed
108 *
109 * @param string &$description The description for the update
110 * @return boolean TRUE if an update is needed, FALSE otherwise
111 */
112 public function checkForUpdate(&$description) {
113 $description = 'This update wizard goes through all files that are referenced in the tt_content.image and '
114 . 'pages.media / pages_language_overlay.media field and adds the files to the new File Index.<br />'
115 . 'It also moves the files from uploads/ to the fileadmin/_migrated/ path.<br /><br />'
116 . 'This update wizard can be called multiple times in case it didn\'t finish after running once.';
117
118 if ($this->versionNumber < 6000000) {
119 // Nothing to do
120 return FALSE;
121 }
122
123 $finishedFields = $this->getFinishedFields();
124 if (count($finishedFields) === 0) {
125 // Nothing done yet, so there's plenty of work left
126 return TRUE;
127 }
128
129 $numberOfFieldsToMigrate = 0;
130 foreach ($this->tables as $table => $tableConfiguration) {
131 // find all additional fields we should get from the database
132 foreach (array_keys($tableConfiguration) as $fieldToMigrate) {
133 $fieldKey = $table . ':' . $fieldToMigrate;
134 if (!in_array($fieldKey, $finishedFields)) {
135 $numberOfFieldsToMigrate++;
136 }
137 }
138 }
139 return $numberOfFieldsToMigrate > 0;
140 }
141
142 /**
143 * Performs the database update.
144 *
145 * @param array &$dbQueries Queries done in this update
146 * @param mixed &$customMessages Custom messages
147 * @return boolean TRUE on success, FALSE on error
148 */
149 public function performUpdate(array &$dbQueries, &$customMessages) {
150 if ($this->versionNumber < 6000000) {
151 // Nothing to do
152 return TRUE;
153 }
154 $this->init();
155 $finishedFields = $this->getFinishedFields();
156 foreach ($this->tables as $table => $tableConfiguration) {
157 // find all additional fields we should get from the database
158 foreach ($tableConfiguration as $fieldToMigrate => $fieldConfiguration) {
159 $fieldKey = $table . ':' . $fieldToMigrate;
160 if (in_array($fieldKey, $finishedFields)) {
161 // this field was already migrated
162 continue;
163 }
164 $fieldsToGet = array($fieldToMigrate);
165 if (isset($fieldConfiguration['titleTexts'])) {
166 $fieldsToGet[] = $fieldConfiguration['titleTexts'];
167 }
168 if (isset($fieldConfiguration['alternativeTexts'])) {
169 $fieldsToGet[] = $fieldConfiguration['alternativeTexts'];
170 }
171 if (isset($fieldConfiguration['captions'])) {
172 $fieldsToGet[] = $fieldConfiguration['captions'];
173 }
174 if (isset($fieldConfiguration['links'])) {
175 $fieldsToGet[] = $fieldConfiguration['links'];
176 }
177
178 do {
179 $records = $this->getRecordsFromTable($table, $fieldToMigrate, $fieldsToGet, self::RECORDS_PER_QUERY);
180 foreach ($records as $record) {
181 $this->migrateField($table, $record, $fieldToMigrate, $fieldConfiguration, $customMessages);
182 }
183 } while (count($records) === self::RECORDS_PER_QUERY);
184
185 // add the field to the "finished fields" if things didn't fail above
186 if (is_array($records)) {
187 $finishedFields[] = $fieldKey;
188 }
189 }
190 }
191 $this->markWizardAsDone(implode(',', $finishedFields));
192 return TRUE;
193 }
194
195 /**
196 * We write down the fields that were migrated. Like this: tt_content:media
197 * so you can check whether a field was already migrated
198 *
199 * @return array
200 */
201 protected function getFinishedFields() {
202 $className = 'TYPO3\\CMS\\Install\\Updates\\TceformsUpdateWizard';
203 return isset($GLOBALS['TYPO3_CONF_VARS']['INSTALL']['wizardDone'][$className])
204 ? explode(',', $GLOBALS['TYPO3_CONF_VARS']['INSTALL']['wizardDone'][$className])
205 : array();
206 }
207
208 /**
209 * Get records from table where the field to migrate is not empty (NOT NULL and != '')
210 * and also not numeric (which means that it is migrated)
211 *
212 * @param string $table
213 * @param string $fieldToMigrate
214 * @param array $relationFields
215 * @param int $limit Maximum number records to select
216 * @throws \RuntimeException
217 * @return array
218 */
219 protected function getRecordsFromTable($table, $fieldToMigrate, $relationFields, $limit) {
220 $fields = implode(',', array_merge($relationFields, array('uid', 'pid')));
221 $deletedCheck = isset($GLOBALS['TCA'][$table]['ctrl']['delete'])
222 ? ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['delete'] . '=0'
223 : '';
224 $where = $fieldToMigrate . ' IS NOT NULL'
225 . ' AND ' . $fieldToMigrate . ' != \'\''
226 . ' AND CAST(CAST(' . $fieldToMigrate . ' AS DECIMAL) AS CHAR) <> ' . $fieldToMigrate
227 . $deletedCheck;
228 $result = $this->database->exec_SELECTgetRows($fields, $table, $where, '', '', $limit);
229 if ($result === NULL) {
230 throw new \RuntimeException('Database query failed. Error was: ' . $this->database->sql_error());
231 }
232 return $result;
233 }
234
235 /**
236 * Migrates a single field.
237 *
238 * @param string $table
239 * @param array $row
240 * @param string $fieldname
241 * @param array $fieldConfiguration
242 * @param string $customMessages
243 * @return array A list of performed database queries
244 * @throws \Exception
245 */
246 protected function migrateField($table, $row, $fieldname, $fieldConfiguration, &$customMessages) {
247 $titleTextContents = array();
248 $alternativeTextContents = array();
249 $captionContents = array();
250 $linkContents = array();
251
252 $fieldItems = GeneralUtility::trimExplode(',', $row[$fieldname], TRUE);
253 if (empty($fieldItems) || is_numeric($row[$fieldname])) {
254 return array();
255 }
256 if (isset($fieldConfiguration['titleTexts'])) {
257 $titleTextField = $fieldConfiguration['titleTexts'];
258 $titleTextContents = explode(LF, $row[$titleTextField]);
259 }
260
261 if (isset($fieldConfiguration['alternativeTexts'])) {
262 $alternativeTextField = $fieldConfiguration['alternativeTexts'];
263 $alternativeTextContents = explode(LF, $row[$alternativeTextField]);
264 }
265 if (isset($fieldConfiguration['captions'])) {
266 $captionField = $fieldConfiguration['captions'];
267 $captionContents = explode(LF, $row[$captionField]);
268 }
269 if (isset($fieldConfiguration['links'])) {
270 $linkField = $fieldConfiguration['links'];
271 $linkContents = explode(LF, $row[$linkField]);
272 }
273 $fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
274 $queries = array();
275 $i = 0;
276
277 if (!PATH_site) {
278 throw new \Exception('PATH_site was undefined.');
279 }
280
281 $storageUid = (int)$this->storage->getUid();
282
283 foreach ($fieldItems as $item) {
284 $fileUid = NULL;
285 $sourcePath = PATH_site . $fieldConfiguration['sourcePath'] . $item;
286 $targetDirectory = PATH_site . $fileadminDirectory . $fieldConfiguration['targetPath'];
287 $targetPath = $targetDirectory . $item;
288
289 // maybe the file was already moved, so check if the original file still exists
290 if (file_exists($sourcePath)) {
291 if (!is_dir($targetDirectory)) {
292 GeneralUtility::mkdir_deep($targetDirectory);
293 }
294
295 // see if the file already exists in the storage
296 $fileSha1 = sha1_file($sourcePath);
297
298 $existingFileRecord = $this->database->exec_SELECTgetSingleRow(
299 'uid',
300 'sys_file',
301 'sha1=' . $this->database->fullQuoteStr($fileSha1, 'sys_file') . ' AND storage=' . $storageUid
302 );
303 // the file exists, the file does not have to be moved again
304 if (is_array($existingFileRecord)) {
305 $fileUid = $existingFileRecord['uid'];
306 } else {
307 // just move the file (no duplicate)
308 rename($sourcePath, $targetPath);
309 }
310 }
311
312 if ($fileUid === NULL) {
313 // get the File object if it hasn't been fetched before
314 try {
315 // if the source file does not exist, we should just continue, but leave a message in the docs;
316 // ideally, the user would be informed after the update as well.
317 $file = $this->storage->getFile($fieldConfiguration['targetPath'] . $item);
318 $fileUid = $file->getUid();
319
320 } catch (\Exception $e) {
321
322 // no file found, no reference can be set
323 $this->logger->notice(
324 'File ' . $fieldConfiguration['sourcePath'] . $item . ' does not exist. Reference was not migrated.',
325 array('table' => $table, 'record' => $row, 'field' => $fieldname)
326 );
327
328 $format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
329 $message = sprintf($format, $fieldConfiguration['sourcePath'] . $item, $table, $row['uid'], $fieldname);
330 $customMessages .= PHP_EOL . $message;
331
332 continue;
333 }
334 }
335
336 if ($fileUid > 0) {
337 $fields = array(
338 // TODO add sorting/sorting_foreign
339 'fieldname' => $fieldname,
340 'table_local' => 'sys_file',
341 // the sys_file_reference record should always placed on the same page
342 // as the record to link to, see issue #46497
343 'pid' => ($table === 'pages' ? $row['uid'] : $row['pid']),
344 'uid_foreign' => $row['uid'],
345 'uid_local' => $fileUid,
346 'tablenames' => $table,
347 'crdate' => time(),
348 'tstamp' => time()
349 );
350 if (isset($titleTextField)) {
351 $fields['title'] = trim($titleTextContents[$i]);
352 }
353 if (isset($alternativeTextField)) {
354 $fields['alternative'] = trim($alternativeTextContents[$i]);
355 }
356 if (isset($captionField)) {
357 $fields['description'] = trim($captionContents[$i]);
358 }
359 if (isset($linkField)) {
360 $fields['link'] = trim($linkContents[$i]);
361 }
362 $this->database->exec_INSERTquery('sys_file_reference', $fields);
363 $queries[] = str_replace(LF, ' ', $this->database->debug_lastBuiltQuery);
364 ++$i;
365 }
366 }
367
368 // Update referencing table's original field to now contain the count of references,
369 // but only if all new references could be set
370 if ($i === count($fieldItems)) {
371 $this->database->exec_UPDATEquery($table, 'uid=' . $row['uid'], array($fieldname => $i));
372 $queries[] = str_replace(LF, ' ', $this->database->debug_lastBuiltQuery);
373 }
374 return $queries;
375 }
376 }