92b40c679dd17abadd0fc6fa5a6ea93d312a93f2
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / ProcessedFileChecksumUpdate.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\ConnectionPool;
18 use TYPO3\CMS\Core\Resource\ProcessedFile;
19 use TYPO3\CMS\Core\Resource\ResourceFactory;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Updates the checksum of sys_file_processedfile records to avoid regeneration of the thumbnails
24 */
25 class ProcessedFileChecksumUpdate extends AbstractUpdate
26 {
27 /**
28 * @var string
29 */
30 protected $title = '[Optional] Update sys_file_processedfile records to match new checksum calculation.';
31
32 /**
33 * Checks if an update is needed
34 *
35 * @param string &$description The description for the update
36 * @return bool Whether an update is needed (TRUE) or not (FALSE)
37 */
38 public function checkForUpdate(&$description)
39 {
40 if ($this->isWizardDone()) {
41 return false;
42 }
43
44 $join = 'sys_file_processedfile LEFT JOIN sys_registry ON CAST(entry_key AS CHAR) = CAST(sys_file_processedfile.uid AS CHAR) AND entry_namespace = \'ProcessedFileChecksumUpdate\'';
45 $count = $this->getDatabaseConnection()->exec_SELECTcountRows('*', $join, '(entry_key IS NULL AND sys_file_processedfile.identifier <> \'\') OR sys_file_processedfile.width IS NULL');
46 if (!$count) {
47 return false;
48 }
49
50 $description = 'The checksum calculation for processed files (image thumbnails) has been changed with TYPO3 CMS 7.3 and 6.2.13.
51 This means that your processed files need to be updated, if you update from versions <strong>below TYPO3 CMS 7.3 or 6.2.13</strong>.<br />
52 This can either happen on demand, when the processed file is first needed, or by executing this wizard, which updates all processed images at once.<br />
53 <strong>Important:</strong> If you have lots of processed files, you should prefer using this wizard, otherwise this might cause a lot of work for your server.';
54
55 return true;
56 }
57
58 /**
59 * Performs the update
60 *
61 * @param array &$databaseQueries Queries done in this update
62 * @param mixed &$customMessages Custom messages
63 * @return bool
64 */
65 public function performUpdate(array &$databaseQueries, &$customMessages)
66 {
67 $db = $this->getDatabaseConnection();
68 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
69 $fileConnection = $connectionPool->getConnectionByName('sys_file_processedfile');
70 $registryConnection = $connectionPool->getConnectionForTable('sys_registry');
71
72 // remove all invalid records which hold NULL values
73 $queryBuilder = $fileConnection->createQueryBuilder();
74 $queryBuilder->delete('sys_file_processedfile')
75 ->orWhere(
76 $queryBuilder->expr()->isNull('width'),
77 $queryBuilder->expr()->isNull('height')
78 )
79 ->execute();
80
81 $factory = GeneralUtility::makeInstance(ResourceFactory::class);
82
83 $join = 'sys_file_processedfile LEFT JOIN sys_registry ON entry_key = CAST(sys_file_processedfile.uid AS CHAR) AND entry_namespace = \'ProcessedFileChecksumUpdate\'';
84 $res = $db->exec_SELECTquery('sys_file_processedfile.*', $join, 'entry_key IS NULL AND sys_file_processedfile.identifier <> \'\'');
85 while ($processedFileRow = $db->sql_fetch_assoc($res)) {
86 try {
87 $storage = $factory->getStorageObject($processedFileRow['storage']);
88 } catch (\InvalidArgumentException $e) {
89 $storage = null;
90 }
91 if (!$storage) {
92 // invalid storage, delete record, we can't take care of the associated file
93 $fileConnection->delete('sys_file_processedfile', ['uid' => (int)$processedFileRow['uid']]);
94 continue;
95 }
96
97 if ($storage->getDriverType() !== 'Local') {
98 // non-local storage, we can't treat this, skip the record and mark it done
99 $registryConnection->insert(
100 'sys_registry',
101 [
102 'entry_namespace' => 'ProcessedFileChecksumUpdate',
103 'entry_key' => $processedFileRow['uid']
104 ]
105 );
106 continue;
107 }
108
109 $configuration = $storage->getConfiguration();
110 if ($configuration['pathType'] === 'relative') {
111 $absoluteBasePath = PATH_site . $configuration['basePath'];
112 } else {
113 $absoluteBasePath = $configuration['basePath'];
114 }
115 $filePath = rtrim($absoluteBasePath, '/') . '/' . ltrim($processedFileRow['identifier'], '/');
116
117 try {
118 $originalFile = $factory->getFileObject($processedFileRow['original']);
119 } catch (\Exception $e) {
120 // no original file there anymore, delete local file
121 @unlink($filePath);
122 $fileConnection->delete('sys_file_processedfile', ['uid' => (int)$processedFileRow['uid']]);
123 continue;
124 }
125
126 $processedFileObject = new ProcessedFile($originalFile, '', array(), $processedFileRow);
127
128 // calculate new checksum and name
129 $newChecksum = $processedFileObject->calculateChecksum();
130
131 // if the checksum already matches, there is nothing to do
132 if ($newChecksum !== $processedFileRow['checksum']) {
133 $newName = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['name']);
134 $newIdentifier = str_replace($processedFileRow['checksum'], $newChecksum, $processedFileRow['identifier']);
135 $newFilePath = str_replace($processedFileRow['checksum'], $newChecksum, $filePath);
136
137 // rename file
138 if (@rename($filePath, $newFilePath)) {
139 // save result back into database
140 $fields = array(
141 'tstamp' => time(),
142 'identifier' => $newIdentifier,
143 'name' => $newName,
144 'checksum' => $newChecksum
145 );
146 $db->exec_UPDATEquery('sys_file_processedfile', 'uid=' . $processedFileRow['uid'], $fields);
147 }
148 // if the rename of the file failed, keep the record, but do not bother with it again
149 }
150
151 $registryConnection->insert(
152 'sys_registry',
153 [
154 'entry_namespace' => 'ProcessedFileChecksumUpdate',
155 'entry_key' => $processedFileRow['uid']
156 ]
157 );
158 }
159
160 $registryConnection->delete('sys_registry', ['entry_namespace' => 'ProcessedFileChecksumUpdate']);
161 $this->markWizardAsDone();
162 return true;
163 }
164 }