[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / TtContentUploadsUpdateWizard.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 /**
18 * Upgrade wizard which goes through all files referenced in the tt_content.media filed
19 * and creates sys_file records as well as sys_file_reference records for the individual usages.
20 *
21 * @author Steffen Ritter <steffen.ritter@typo3.org>
22 */
23 class TtContentUploadsUpdateWizard extends AbstractUpdate {
24
25 const FOLDER_ContentUploads = '_migrated/content_uploads';
26
27 /**
28 * @var string
29 */
30 protected $title = 'Migrate file relations of tt_content "uploads"';
31
32 /**
33 * @var string
34 */
35 protected $targetDirectory;
36
37 /**
38 * @var \TYPO3\CMS\Core\Resource\ResourceFactory
39 */
40 protected $fileFactory;
41
42 /**
43 * @var \TYPO3\CMS\Core\Resource\Index\FileIndexRepository
44 */
45 protected $fileIndexRepository;
46
47 /**
48 * @var \TYPO3\CMS\Core\Resource\ResourceStorage
49 */
50 protected $storage;
51
52 /**
53 * Initialize all required repository and factory objects.
54 *
55 * @throws \RuntimeException
56 */
57 protected function init() {
58 $fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
59 /** @var $storageRepository \TYPO3\CMS\Core\Resource\StorageRepository */
60 $storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
61 $storages = $storageRepository->findAll();
62 foreach ($storages as $storage) {
63 $storageRecord = $storage->getStorageRecord();
64 $configuration = $storage->getConfiguration();
65 $isLocalDriver = $storageRecord['driver'] === 'Local';
66 $isOnFileadmin = !empty($configuration['basePath']) && \TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($configuration['basePath'], $fileadminDirectory);
67 if ($isLocalDriver && $isOnFileadmin) {
68 $this->storage = $storage;
69 break;
70 }
71 }
72 if (!isset($this->storage)) {
73 throw new \RuntimeException('Local default storage could not be initialized - might be due to missing sys_file* tables.');
74 }
75 $this->fileFactory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
76 $this->fileIndexRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\Index\\FileIndexRepository');
77 $this->targetDirectory = PATH_site . $fileadminDirectory . self::FOLDER_ContentUploads . '/';
78 }
79
80 /**
81 * Checks if an update is needed
82 *
83 * @param string &$description The description for the update
84 * @return boolean TRUE if an update is needed, FALSE otherwise
85 */
86 public function checkForUpdate(&$description) {
87 $updateNeeded = FALSE;
88 // Fetch records where the field media does not contain a plain integer value
89 // * check whether media field is not empty
90 // * then check whether media field does not contain a reference count (= not integer)
91 $mapping = $this->getTableColumnMapping();
92 $sql = $GLOBALS['TYPO3_DB']->SELECTquery(
93 'COUNT(' . $mapping['mapFieldNames']['uid'] . ')',
94 $mapping['mapTableName'],
95 '1=1'
96 );
97 $whereClause = $this->getDbalCompliantUpdateWhereClause();
98 $sql = str_replace('WHERE 1=1', $whereClause, $sql);
99 $resultSet = $GLOBALS['TYPO3_DB']->sql_query($sql);
100 $notMigratedRowsCount = 0;
101 if ($resultSet !== FALSE) {
102 list($notMigratedRowsCount) = $GLOBALS['TYPO3_DB']->sql_fetch_row($resultSet);
103 $notMigratedRowsCount = (int)$notMigratedRowsCount;
104 $GLOBALS['TYPO3_DB']->sql_free_result($resultSet);
105 }
106 if ($notMigratedRowsCount > 0) {
107 $description = 'There are Content Elements of type "upload" which are referencing files that are not using ' . ' the File Abstraction Layer. This wizard will move the files to fileadmin/' . self::FOLDER_ContentUploads . ' and index them.';
108 $updateNeeded = TRUE;
109 }
110 return $updateNeeded;
111 }
112
113 /**
114 * Performs the database update.
115 *
116 * @param array &$dbQueries Queries done in this update
117 * @param mixed &$customMessages Custom messages
118 * @return boolean TRUE on success, FALSE on error
119 */
120 public function performUpdate(array &$dbQueries, &$customMessages) {
121 $this->init();
122 $records = $this->getRecordsFromTable('tt_content');
123 $this->checkPrerequisites();
124 foreach ($records as $singleRecord) {
125 $this->migrateRecord($singleRecord);
126 }
127 return TRUE;
128 }
129
130 /**
131 * Ensures a new folder "fileadmin/content_upload/" is available.
132 *
133 * @return void
134 */
135 protected function checkPrerequisites() {
136 if (!$this->storage->hasFolder(self::FOLDER_ContentUploads)) {
137 $this->storage->createFolder(self::FOLDER_ContentUploads, $this->storage->getRootLevelFolder());
138 }
139 }
140
141 /**
142 * Processes the actual transformation from CSV to sys_file_references
143 *
144 * @param array $record
145 * @return void
146 */
147 protected function migrateRecord(array $record) {
148 $collections = array();
149 if (trim($record['select_key'])) {
150 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_file_collection', array(
151 'pid' => $record['pid'],
152 'title' => $record['select_key'],
153 'storage' => $this->storage->getUid(),
154 'folder' => ltrim('fileadmin/', $record['select_key'])
155 ));
156 $collections[] = $GLOBALS['TYPO3_DB']->sql_insert_id();
157 }
158 $files = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $record['media'], TRUE);
159 $descriptions = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('
160 ', $record['imagecaption']);
161 $titleText = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('
162 ', $record['titleText']);
163 $i = 0;
164 foreach ($files as $file) {
165 if (file_exists(PATH_site . 'uploads/media/' . $file)) {
166 \TYPO3\CMS\Core\Utility\GeneralUtility::upload_copy_move(PATH_site . 'uploads/media/' . $file, $this->targetDirectory . $file);
167 $fileObject = $this->storage->getFile(self::FOLDER_ContentUploads . '/' . $file);
168 $this->fileIndexRepository->add($fileObject);
169 $dataArray = array(
170 'uid_local' => $fileObject->getUid(),
171 'tablenames' => 'tt_content',
172 'uid_foreign' => $record['uid'],
173 // the sys_file_reference record should always placed on the same page
174 // as the record to link to, see issue #46497
175 'pid' => $record['pid'],
176 'fieldname' => 'media',
177 'sorting_foreign' => $i
178 );
179 if (isset($descriptions[$i])) {
180 $dataArray['description'] = $descriptions[$i];
181 }
182 if (isset($titleText[$i])) {
183 $dataArray['alternative'] = $titleText[$i];
184 }
185 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_file_reference', $dataArray);
186 unlink(PATH_site . 'uploads/media/' . $file);
187 }
188 $i++;
189 }
190 $this->cleanRecord($record, $i, $collections);
191 }
192
193 /**
194 * Removes the old fields from the database-record
195 *
196 * @param array $record
197 * @param integer $fileCount
198 * @param array $collectionUids
199 * @return void
200 */
201 protected function cleanRecord(array $record, $fileCount, array $collectionUids) {
202 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('tt_content', 'uid = ' . $record['uid'], array(
203 'media' => $fileCount,
204 'imagecaption' => '',
205 'titleText' => '',
206 'altText' => '',
207 'select_key' => '',
208 'file_collections' => implode(',', $collectionUids)
209 ));
210 }
211
212 /**
213 * Retrieve every record which needs to be processed
214 *
215 * @return array
216 */
217 protected function getRecordsFromTable() {
218 $mapping = $this->getTableColumnMapping();
219 $reverseFieldMapping = array_flip($mapping['mapFieldNames']);
220
221 $fields = array();
222 foreach (array('uid', 'pid', 'select_key', 'media', 'imagecaption', 'titleText') as $columnName) {
223 $fields[] = $mapping['mapFieldNames'][$columnName];
224 }
225 $fields = implode(',', $fields);
226
227 $sql = $GLOBALS['TYPO3_DB']->SELECTquery(
228 $fields,
229 $mapping['mapTableName'],
230 '1=1'
231 );
232 $whereClause = $this->getDbalCompliantUpdateWhereClause();
233 $sql = str_replace('WHERE 1=1', $whereClause, $sql);
234 $resultSet = $GLOBALS['TYPO3_DB']->sql_query($sql);
235 $records = array();
236 if (!$GLOBALS['TYPO3_DB']->sql_error()) {
237 while (($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($resultSet)) !== FALSE) {
238 // Mapping back column names to native TYPO3 names
239 $record = array();
240 foreach ($reverseFieldMapping as $columnName => $finalColumnName) {
241 $record[$finalColumnName] = $row[$columnName];
242 }
243 $records[] = $record;
244 }
245 $GLOBALS['TYPO3_DB']->sql_free_result($resultSet);
246 }
247 return $records;
248 }
249
250 /**
251 * Returns a DBAL-compliant where clause to be used for the update where clause.
252 * We have DBAL-related code here because the SQL parser is not able to properly
253 * parse this complex condition but we know that it is compatible with the DBMS
254 * we support in TYPO3 Core.
255 *
256 * @return string
257 */
258 protected function getDbalCompliantUpdateWhereClause() {
259 $mapping = $this->getTableColumnMapping();
260 $this->quoteIdentifiers($mapping);
261
262 $where = sprintf(
263 'WHERE %s <> \'\' AND CAST(CAST(%s AS DECIMAL) AS CHAR) <> %s OR (%s = \'uploads\' AND %s != \'\')',
264 $mapping['mapFieldNames']['media'],
265 $mapping['mapFieldNames']['media'],
266 $mapping['mapFieldNames']['media'],
267 $mapping['mapFieldNames']['CType'],
268 $mapping['mapFieldNames']['select_key']
269 );
270
271 return $where;
272 }
273
274 /**
275 * Returns the table and column mapping.
276 *
277 * @return array
278 */
279 protected function getTableColumnMapping() {
280 $mapping = array(
281 'mapTableName' => 'tt_content',
282 'mapFieldNames' => array(
283 'uid' => 'uid',
284 'pid' => 'pid',
285 'media' => 'media',
286 'imagecaption' => 'imagecaption',
287 'titleText' => 'titleText',
288 'CType' => 'CType',
289 'select_key' => 'select_key',
290 )
291 );
292
293 if ($GLOBALS['TYPO3_DB'] instanceof \TYPO3\CMS\Dbal\Database\DatabaseConnection) {
294 if (!empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['mapping']['tt_content'])) {
295 $mapping = array_merge_recursive($mapping, $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['dbal']['mapping']['tt_content']);
296 }
297 }
298
299 return $mapping;
300 }
301
302 /**
303 * Quotes identifiers for DBAL-compliant query.
304 *
305 * @param array &$mapping
306 * @return void
307 */
308 protected function quoteIdentifiers(array &$mapping) {
309 if ($GLOBALS['TYPO3_DB'] instanceof \TYPO3\CMS\Dbal\Database\DatabaseConnection) {
310 if (!$GLOBALS['TYPO3_DB']->runningNative() && !$GLOBALS['TYPO3_DB']->runningADOdbDriver('mysql')) {
311 $mapping['mapTableName'] = '"' . $mapping['mapTableName'] . '"';
312 foreach ($mapping['mapFieldNames'] as $key => &$value) {
313 $value = '"' . $value . '"';
314 }
315 }
316 }
317 }
318
319 }