3da81d0529be83dbbabdc7022398c6ed47bbc3df
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / ReferenceIntegrityUpdateWizard.php
1 <?php
2 namespace TYPO3\CMS\Install\Updates;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2013 Benni Mack <benni@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 /**
28 * Performs certain DB updates in order to ensure that the DB fields
29 * are set properly.
30 * 1) Ensure that there are no sys_file_reference records with PID=0
31 * where the connected parent records (e.g. a tt_content record) are
32 * not on PID=0.
33 * 2) Make sure that all sys_file_references point to tables that still
34 * exist.
35 *
36 * @author Benni Mack <benni@typo3.org>
37 */
38 class ReferenceIntegrityUpdateWizard extends AbstractUpdate {
39
40 /**
41 * @var string
42 */
43 protected $title = 'Ensures the database integrity for File Abstraction records';
44
45 /**
46 * Checks if an update is needed
47 *
48 * @param string &$description The description for the update
49 * @return boolean TRUE if an update is needed, FALSE otherwise
50 */
51 public function checkForUpdate(&$description) {
52 $description = 'Checks if there are file references that are on the root level. ' .
53 'This could have happened due to a misconfigured previous migration. ' .
54 'This migration will also remove references to tables that no longer exist.';
55 return count($this->getRequiredUpdates()) > 0;
56 }
57
58 /**
59 * Performs the database update.
60 *
61 * @param array &$dbQueries Queries done in this update
62 * @param mixed &$customMessages Custom messages
63 * @return boolean TRUE on success, FALSE on error
64 */
65 public function performUpdate(array &$dbQueries, &$customMessages) {
66 $updates = $this->getRequiredUpdates();
67 if (isset($updates['referenceToMissingTables'])) {
68 foreach ($updates['referenceToMissingTables'] as $missingTable) {
69 $deleteQuery = $GLOBALS['TYPO3_DB']->DELETEquery(
70 'sys_file_reference',
71 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($missingTable, 'sys_file_reference')
72 );
73 $GLOBALS['TYPO3_DB']->sql_query($deleteQuery);
74 $dbQueries[] = $deleteQuery;
75 }
76 }
77 if (isset($updates['improperConnectedFileReferences'])) {
78 foreach ($updates['improperConnectedFileReferences'] as $fileReferenceRecord) {
79 if ($fileReferenceRecord['newpid'] > 0) {
80 $updateQuery = $GLOBALS['TYPO3_DB']->UPDATEquery(
81 'sys_file_reference',
82 'uid=' . (int)$fileReferenceRecord['uid'],
83 array('pid' => $fileReferenceRecord['newpid'])
84 );
85 $GLOBALS['TYPO3_DB']->sql_query($updateQuery);
86 $dbQueries[] = $updateQuery;
87 }
88 }
89 }
90 return TRUE;
91 }
92
93 /**
94 * Determine all DB updates that need to be done
95 *
96 * @return array
97 */
98 protected function getRequiredUpdates() {
99 $requiredUpdates = array();
100 $referenceToMissingTables = $this->getFileReferencesPointingToMissingTables();
101 if (count($referenceToMissingTables) > 0) {
102 $requiredUpdates['referenceToMissingTables'] = $referenceToMissingTables;
103 }
104 $improperConnectedFileReferences = $this->getImproperConnectedFileReferences($referenceToMissingTables);
105 if (count($improperConnectedFileReferences) > 0) {
106 $requiredUpdates['improperConnectedFileReferences'] = $improperConnectedFileReferences;
107 }
108 return $requiredUpdates;
109 }
110
111 /**
112 * A list of tables that are referenced by sys_file_reference that are no longer existing
113 *
114 * @return array
115 */
116 protected function getFileReferencesPointingToMissingTables() {
117 $existingTables = array_flip(array_keys($GLOBALS['TYPO3_DB']->admin_get_tables()));
118 $missingTables = array();
119 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT tablenames', 'sys_file_reference', '');
120 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
121 $thisTablename = $row['tablenames'];
122 if (!isset($existingTables[$thisTablename])) {
123 $missingTables[] = $thisTablename;
124 }
125 }
126 return $missingTables;
127 }
128
129 /**
130 * Fetches a list of all sys_file_references that have PID=0
131 *
132 * @return mixed
133 */
134 protected function getFileReferencesOnRootlevel() {
135 return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
136 'uid, pid, uid_local AS fileuid, uid_foreign AS targetuid, tablenames AS targettable',
137 'sys_file_reference',
138 'pid=0 AND deleted=0'
139 );
140 }
141
142 /**
143 * Fetches all sys_file_reference records that are on PID=0 BUT their counter parts (the target record)
144 * is NOT on pid=0
145 *
146 * @param array $skipTables Table names to skip checking
147 * @return array
148 */
149 protected function getImproperConnectedFileReferences(array $skipTables = array()) {
150 $improperConnectedReferences = array();
151 // fetch all references on root level
152 $sysFileReferences = $this->getFileReferencesOnRootlevel();
153 foreach ($sysFileReferences as $fileReferenceRecord) {
154 $tableName = $fileReferenceRecord['targettable'];
155 if (in_array($tableName, $skipTables)) {
156 continue;
157 }
158 // if the target table is pages (e.g. when adding a file reference to the pages->media
159 // record, then the
160 $whereClause = 'uid=' . (int)$fileReferenceRecord['targetuid'];
161 if ($fileReferenceRecord['targettable'] === 'pages') {
162 $isPageReference = TRUE;
163 } else {
164 $isPageReference = FALSE;
165 $whereClause .= ' AND pid<>0';
166 }
167 // check the target table, if the target record is NOT on the rootlevel
168 $targetRecord = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
169 'uid, pid',
170 $tableName,
171 $whereClause
172 );
173 // only add the file reference if the target record is not on PID=0
174 if ($targetRecord !== NULL) {
175 $fileReferenceRecord['newpid'] = ($isPageReference ? $targetRecord['uid'] : $targetRecord['pid']);
176 $improperConnectedReferences[] = $fileReferenceRecord;
177 }
178 }
179 return $improperConnectedReferences;
180 }
181 }