d61628be2ea2af842a4d7312664108c299dc9ae7
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Integrity / DatabaseIntegrityCheck.php
1 <?php
2 namespace TYPO3\CMS\Core\Integrity;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
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 2 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 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Backend\Utility\BackendUtility;
31
32 /**
33 * This class holds functions used by the TYPO3 backend to check the integrity of the database (The DBint module, 'lowlevel' extension)
34 *
35 * Depends on: Depends on \TYPO3\CMS\Core\Database\RelationHandler
36 *
37 * @todo Need to really extend this class when the tcemain library has been updated and the whole API is better defined. There are some known bugs in this library. Further it would be nice with a facility to not only analyze but also clean up!
38 * @see SC_mod_tools_dbint_index::func_relations(), SC_mod_tools_dbint_index::func_records()
39 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
40 */
41 class DatabaseIntegrityCheck {
42
43 /**
44 * @var boolean If set, genTree() includes deleted pages. This is default.
45 * @todo Define visibility
46 */
47 public $genTree_includeDeleted = TRUE;
48
49 /**
50 * @var boolean If set, genTree() includes versionized pages/records. This is default.
51 * @todo Define visibility
52 */
53 public $genTree_includeVersions = TRUE;
54
55 /**
56 * @var boolean If set, genTree() includes records from pages.
57 * @todo Define visibility
58 */
59 public $genTree_includeRecords = FALSE;
60
61 /**
62 * @var string Extra where-clauses for the tree-selection
63 * @todo Define visibility
64 */
65 public $perms_clause = '';
66
67 /**
68 * @var int If set, genTree() generates HTML, that visualizes the tree.
69 * @todo Define visibility
70 */
71 public $genTree_makeHTML = 0;
72
73 // Internal
74 /**
75 * @var array Will hold id/rec pairs from genTree()
76 * @todo Define visibility
77 */
78 public $page_idArray = array();
79
80 /**
81 * @var array
82 * @todo Define visibility
83 */
84 public $rec_idArray = array();
85
86 /**
87 * @var string Will hold the HTML-code visualising the tree. genTree()
88 * @todo Define visibility
89 */
90 public $genTree_HTML = '';
91
92 /**
93 * @var string
94 * @todo Define visibility
95 */
96 public $backPath = '';
97
98 // Internal
99 /**
100 * @var array
101 * @todo Define visibility
102 */
103 public $checkFileRefs = array();
104
105 /**
106 * @var array From the select-fields
107 * @todo Define visibility
108 */
109 public $checkSelectDBRefs = array();
110
111 /**
112 * @var array From the group-fields
113 * @todo Define visibility
114 */
115 public $checkGroupDBRefs = array();
116
117 /**
118 * @var array Statistics
119 * @todo Define visibility
120 */
121 public $recStats = array(
122 'allValid' => array(),
123 'published_versions' => array(),
124 'deleted' => array()
125 );
126
127 /**
128 * @var array
129 * @todo Define visibility
130 */
131 public $lRecords = array();
132
133 /**
134 * @var string
135 * @todo Define visibility
136 */
137 public $lostPagesList = '';
138
139 /**
140 * Generates a list of Page-uid's that corresponds to the tables in the tree.
141 * This list should ideally include all records in the pages-table.
142 *
143 * @param integer $theID a pid (page-record id) from which to start making the tree
144 * @param string $depthData HTML-code (image-tags) used when this function calls itself recursively.
145 * @param boolean $versions Internal variable, don't set from outside!
146 * @return void
147 * @todo Define visibility
148 */
149 public function genTree($theID, $depthData, $versions = FALSE) {
150 if ($versions) {
151 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,title,doktype,deleted,t3ver_wsid,t3ver_id,t3ver_count' . (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('cms') ? ',hidden' : ''), 'pages', 'pid=-1 AND t3ver_oid=' . (int)$theID . ' ' . (!$this->genTree_includeDeleted ? 'AND deleted=0' : '') . $this->perms_clause, '', 'sorting');
152 } else {
153 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,title,doktype,deleted' . (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('cms') ? ',hidden' : ''), 'pages', 'pid=' . (int)$theID . ' ' . (!$this->genTree_includeDeleted ? 'AND deleted=0' : '') . $this->perms_clause, '', 'sorting');
154 }
155 // Traverse the records selected:
156 $a = 0;
157 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
158 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
159 // Prepare the additional label used in the HTML output in case of versions:
160 if ($versions) {
161 $versionLabel = '[v1.' . $row['t3ver_id'] . '; WS#' . $row['t3ver_wsid'] . ']';
162 } else {
163 $versionLabel = '';
164 }
165 $a++;
166 $newID = $row['uid'];
167 // Build HTML output:
168 if ($this->genTree_makeHTML) {
169 $this->genTree_HTML .= LF . '<div><span class="nobr">';
170 $PM = 'join';
171 $LN = $a == $c ? 'blank' : 'line';
172 $BTM = $a == $c ? 'bottom' : '';
173 $this->genTree_HTML .= $depthData . '<img' . \TYPO3\CMS\Backend\Utility\IconUtility::skinImg($this->backPath, ('gfx/ol/' . $PM . $BTM . '.gif'), 'width="18" height="16"') . ' align="top" alt="" />' . $versionLabel . \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord('pages', $row) . htmlspecialchars(($row['uid'] . ': ' . \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs(strip_tags($row['title']), 50))) . '</span></div>';
174 }
175 // Register various data for this item:
176 $this->page_idArray[$newID] = $row;
177 $this->recStats['all_valid']['pages'][$newID] = $newID;
178 if ($row['deleted']) {
179 $this->recStats['deleted']['pages'][$newID] = $newID;
180 }
181 if ($versions && $row['t3ver_count'] >= 1) {
182 $this->recStats['published_versions']['pages'][$newID] = $newID;
183 }
184 if ($row['deleted']) {
185 $this->recStats['deleted']++;
186 }
187 if ($row['hidden']) {
188 $this->recStats['hidden']++;
189 }
190 $this->recStats['doktype'][$row['doktype']]++;
191 // Create the HTML code prefix for recursive call:
192 $genHTML = $depthData . '<img' . \TYPO3\CMS\Backend\Utility\IconUtility::skinImg($this->backPath, ('gfx/ol/' . $LN . '.gif'), 'width="18" height="16"') . ' align="top" alt="" />' . $versionLabel;
193 // If all records should be shown, do so:
194 if ($this->genTree_includeRecords) {
195 foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
196 if ($tableName != 'pages') {
197 $this->genTree_records($newID, $this->genTree_HTML ? $genHTML : '', $tableName);
198 }
199 }
200 }
201 // Add sub pages:
202 $this->genTree($newID, $this->genTree_HTML ? $genHTML : '');
203 // If versions are included in the tree, add those now:
204 if ($this->genTree_includeVersions) {
205 $this->genTree($newID, $this->genTree_HTML ? $genHTML : '', TRUE);
206 }
207 }
208 $GLOBALS['TYPO3_DB']->sql_free_result($res);
209 }
210
211 /**
212 * @param integer $theID a pid (page-record id) from which to start making the tree
213 * @param string $depthData HTML-code used when this function calls itself recursively.
214 * @param string $table Table to get the records from
215 * @param boolean $versions Internal variable, don't set from outside!
216 * @return void
217 * @todo Define visibility
218 */
219 public function genTree_records($theID, $depthData, $table = '', $versions = FALSE) {
220 if ($versions) {
221 // Select all records from table pointing to this page:
222 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
223 BackendUtility::getCommonSelectFields($table),
224 $table,
225 'pid=-1 AND t3ver_oid=' . (int)$theID . (!$this->genTree_includeDeleted ? BackendUtility::deleteClause($table) : '')
226 );
227 } else {
228 // Select all records from table pointing to this page:
229 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
230 BackendUtility::getCommonSelectFields($table),
231 $table,
232 'pid=' . (int)$theID . (!$this->genTree_includeDeleted ? BackendUtility::deleteClause($table) : '')
233 );
234 }
235 // Traverse selected:
236 $a = 0;
237 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
238 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
239 // Prepare the additional label used in the HTML output in case of versions:
240 if ($versions) {
241 $versionLabel = '[v1.' . $row['t3ver_id'] . '; WS#' . $row['t3ver_wsid'] . ']';
242 } else {
243 $versionLabel = '';
244 }
245 $a++;
246 $newID = $row['uid'];
247 // Build HTML output:
248 if ($this->genTree_makeHTML) {
249 $this->genTree_HTML .= LF . '<div><span class="nobr">';
250 $PM = 'join';
251 $LN = $a == $c ? 'blank' : 'line';
252 $BTM = $a == $c ? 'bottom' : '';
253 $this->genTree_HTML .= $depthData . '<img' . \TYPO3\CMS\Backend\Utility\IconUtility::skinImg($this->backPath, ('gfx/ol/' . $PM . $BTM . '.gif'), 'width="18" height="16"') . ' align="top" alt="" />' . $versionLabel . \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord($table, $row, array('title' => $table)) . htmlspecialchars(($row['uid'] . ': ' . BackendUtility::getRecordTitle($table, $row))) . '</span></div>';
254 }
255 // Register various data for this item:
256 $this->rec_idArray[$table][$newID] = $row;
257 $this->recStats['all_valid'][$table][$newID] = $newID;
258 if ($row['deleted']) {
259 $this->recStats['deleted'][$table][$newID] = $newID;
260 }
261 if ($versions && $row['t3ver_count'] >= 1 && $row['t3ver_wsid'] == 0) {
262 $this->recStats['published_versions'][$table][$newID] = $newID;
263 }
264 // Select all versions of this record:
265 if ($this->genTree_includeVersions && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
266 $genHTML = $depthData . '<img' . \TYPO3\CMS\Backend\Utility\IconUtility::skinImg($this->backPath, ('gfx/ol/' . $LN . '.gif'), 'width="18" height="16"') . ' align="top" alt="" />';
267 $this->genTree_records($newID, $genHTML, $table, TRUE);
268 }
269 }
270 $GLOBALS['TYPO3_DB']->sql_free_result($res);
271 }
272
273 /**
274 * Generates tree and returns statistics
275 *
276 * @param integer $root
277 * @return array Record statistics
278 * @deprecated and unused since 6.0, will be removed two versions later
279 * @todo Define visibility
280 */
281 public function genTreeStatus($root = 0) {
282 \TYPO3\CMS\Core\Utility\GeneralUtility::logDeprecatedFunction();
283 $this->genTree_includeDeleted = TRUE;
284 // if set, genTree() includes deleted pages. This is default.
285 $this->genTree_includeVersions = TRUE;
286 // if set, genTree() includes verisonized pages/records. This is default.
287 $this->genTree_includeRecords = TRUE;
288 // if set, genTree() includes records from pages.
289 $this->perms_clause = '';
290 // extra where-clauses for the tree-selection
291 $this->genTree_makeHTML = 0;
292 // if set, genTree() generates HTML, that visualizes the tree.
293 $this->genTree($root, '');
294 return $this->recStats;
295 }
296
297 /**
298 * Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
299 *
300 * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
301 * @return void
302 * @todo Define visibility
303 */
304 public function lostRecords($pid_list) {
305 $this->lostPagesList = '';
306 if ($pid_list) {
307 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
308 $pid_list_tmp = $pid_list;
309 if (!isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
310 // Remove preceding "-1," for non-versioned tables
311 $pid_list_tmp = preg_replace('/^\\-1,/', '', $pid_list_tmp);
312 }
313 $garbage = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'], $table, 'pid NOT IN (' . $pid_list_tmp . ')');
314 $lostIdList = array();
315 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($garbage)) {
316 $this->lRecords[$table][$row['uid']] = array(
317 'uid' => $row['uid'],
318 'pid' => $row['pid'],
319 'title' => strip_tags(BackendUtility::getRecordTitle($table, $row))
320 );
321 $lostIdList[] = $row['uid'];
322 }
323 $GLOBALS['TYPO3_DB']->sql_free_result($garbage);
324 if ($table == 'pages') {
325 $this->lostPagesList = implode(',', $lostIdList);
326 }
327 }
328 }
329 }
330
331 /**
332 * Fixes lost record from $table with uid $uid by setting the PID to zero.
333 * If there is a disabled column for the record that will be set as well.
334 *
335 * @param string $table Database tablename
336 * @param integer $uid The uid of the record which will have the PID value set to 0 (zero)
337 * @return boolean TRUE if done.
338 * @todo Define visibility
339 */
340 public function fixLostRecord($table, $uid) {
341 if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords[$table][$uid]) && $GLOBALS['BE_USER']->user['admin']) {
342 $updateFields = array();
343 $updateFields['pid'] = 0;
344 // If possible a lost record restored is hidden as default
345 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) {
346 $updateFields[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] = 1;
347 }
348 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateFields);
349 return TRUE;
350 } else {
351 return FALSE;
352 }
353 }
354
355 /**
356 * Counts records from $GLOBALS['TCA']-tables that ARE attached to an existing page.
357 *
358 * @param string $pid_list list of pid's (page-record uid's). This list is probably made by genTree()
359 * @return array an array with the number of records from all $GLOBALS['TCA']-tables that are attached to a PID in the pid-list.
360 * @todo Define visibility
361 */
362 public function countRecords($pid_list) {
363 $list = array();
364 $list_n = array();
365 if ($pid_list) {
366 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
367 $pid_list_tmp = $pid_list;
368 if (!isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
369 // Remove preceding "-1," for non-versioned tables
370 $pid_list_tmp = preg_replace('/^\\-1,/', '', $pid_list_tmp);
371 }
372 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid IN (' . $pid_list_tmp . ')');
373 if ($count) {
374 $list[$table] = $count;
375 }
376 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, 'pid IN (' . $pid_list_tmp . ')' . BackendUtility::deleteClause($table));
377 if ($count) {
378 $list_n[$table] = $count;
379 }
380 }
381 }
382 return array('all' => $list, 'non_deleted' => $list_n);
383 }
384
385 /**
386 * Finding relations in database based on type 'group' (files or database-uid's in a list)
387 *
388 * @param string $mode $mode = file, $mode = db, $mode = '' (all...)
389 * @return array An array with all fields listed that somehow are references to other records (foreign-keys) or files
390 * @todo Define visibility
391 */
392 public function getGroupFields($mode) {
393 $result = array();
394 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
395 $cols = $GLOBALS['TCA'][$table]['columns'];
396 foreach ($cols as $field => $config) {
397 if ($config['config']['type'] == 'group') {
398 if ((!$mode || $mode == 'file') && $config['config']['internal_type'] == 'file' || (!$mode || $mode == 'db') && $config['config']['internal_type'] == 'db') {
399 $result[$table][] = $field;
400 }
401 }
402 if ((!$mode || $mode == 'db') && $config['config']['type'] == 'select' && $config['config']['foreign_table']) {
403 $result[$table][] = $field;
404 }
405 }
406 if ($result[$table]) {
407 $result[$table] = implode(',', $result[$table]);
408 }
409 }
410 return $result;
411 }
412
413 /**
414 * Finds all fields that hold filenames from uploadfolder
415 *
416 * @param string $uploadfolder Path to uploadfolder
417 * @return array An array with all fields listed that have references to files in the $uploadfolder
418 * @todo Define visibility
419 */
420 public function getFileFields($uploadfolder) {
421 $result = array();
422 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
423 $cols = $GLOBALS['TCA'][$table]['columns'];
424 foreach ($cols as $field => $config) {
425 if ($config['config']['type'] == 'group' && $config['config']['internal_type'] == 'file' && $config['config']['uploadfolder'] == $uploadfolder) {
426 $result[] = array($table, $field);
427 }
428 }
429 }
430 return $result;
431 }
432
433 /**
434 * Returns an array with arrays of table/field pairs which are allowed to hold references to the input table name - according to $GLOBALS['TCA']
435 *
436 * @param string $theSearchTable Table name
437 * @return array
438 * @todo Define visibility
439 */
440 public function getDBFields($theSearchTable) {
441 $result = array();
442 foreach ($GLOBALS['TCA'] as $table => $tableConf) {
443 $cols = $GLOBALS['TCA'][$table]['columns'];
444 foreach ($cols as $field => $config) {
445 if ($config['config']['type'] == 'group' && $config['config']['internal_type'] == 'db') {
446 if (trim($config['config']['allowed']) == '*' || strstr($config['config']['allowed'], $theSearchTable)) {
447 $result[] = array($table, $field);
448 }
449 } elseif ($config['config']['type'] == 'select' && $config['config']['foreign_table'] == $theSearchTable) {
450 $result[] = array($table, $field);
451 }
452 }
453 }
454 return $result;
455 }
456
457 /**
458 * This selects non-empty-records from the tables/fields in the fkey_array generated by getGroupFields()
459 *
460 * @param array $fkey_arrays Array with tables/fields generated by getGroupFields()
461 * @return void
462 * @see getGroupFields()
463 * @todo Define visibility
464 */
465 public function selectNonEmptyRecordsWithFkeys($fkey_arrays) {
466 if (is_array($fkey_arrays)) {
467 foreach ($fkey_arrays as $table => $field_list) {
468 if ($GLOBALS['TCA'][$table] && trim($field_list)) {
469 $fieldArr = explode(',', $field_list);
470 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('dbal')) {
471 $fields = $GLOBALS['TYPO3_DB']->admin_get_fields($table);
472 $field = array_shift($fieldArr);
473 $cl_fl = $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'I' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'N' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'R' ? $field . '<>0' : $field . '<>\'\'';
474 foreach ($fieldArr as $field) {
475 $cl_fl .= $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'I' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'N' || $GLOBALS['TYPO3_DB']->MetaType($fields[$field]['type'], $table) == 'R' ? ' OR ' . $field . '<>0' : ' OR ' . $field . '<>\'\'';
476 }
477 unset($fields);
478 } else {
479 $cl_fl = implode('<>\'\' OR ', $fieldArr) . '<>\'\'';
480 }
481 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,' . $field_list, $table, $cl_fl);
482 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
483 foreach ($fieldArr as $field) {
484 if (trim($row[$field])) {
485 $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
486 if ($fieldConf['type'] == 'group') {
487 if ($fieldConf['internal_type'] == 'file') {
488 // Files...
489 if ($fieldConf['MM']) {
490 $tempArr = array();
491 /** @var $dbAnalysis \TYPO3\CMS\Core\Database\RelationHandler */
492 $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
493 $dbAnalysis->start('', 'files', $fieldConf['MM'], $row['uid']);
494 foreach ($dbAnalysis->itemArray as $somekey => $someval) {
495 if ($someval['id']) {
496 $tempArr[] = $someval['id'];
497 }
498 }
499 } else {
500 $tempArr = explode(',', trim($row[$field]));
501 }
502 foreach ($tempArr as $file) {
503 $file = trim($file);
504 if ($file) {
505 $this->checkFileRefs[$fieldConf['uploadfolder']][$file] += 1;
506 }
507 }
508 }
509 if ($fieldConf['internal_type'] == 'db') {
510 /** @var $dbAnalysis \TYPO3\CMS\Core\Database\RelationHandler */
511 $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
512 $dbAnalysis->start($row[$field], $fieldConf['allowed'], $fieldConf['MM'], $row['uid'], $table, $fieldConf);
513 foreach ($dbAnalysis->itemArray as $tempArr) {
514 $this->checkGroupDBRefs[$tempArr['table']][$tempArr['id']] += 1;
515 }
516 }
517 }
518 if ($fieldConf['type'] == 'select' && $fieldConf['foreign_table']) {
519 /** @var $dbAnalysis \TYPO3\CMS\Core\Database\RelationHandler */
520 $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
521 $dbAnalysis->start($row[$field], $fieldConf['foreign_table'], $fieldConf['MM'], $row['uid'], $table, $fieldConf);
522 foreach ($dbAnalysis->itemArray as $tempArr) {
523 if ($tempArr['id'] > 0) {
524 $this->checkGroupDBRefs[$fieldConf['foreign_table']][$tempArr['id']] += 1;
525 }
526 }
527 }
528 }
529 }
530 }
531 $GLOBALS['TYPO3_DB']->sql_free_result($mres);
532 }
533 }
534 }
535 }
536
537 /**
538 * Depends on selectNonEmpty.... to be executed first!!
539 *
540 * @return array Report over files; keys are "moreReferences", "noReferences", "noFile", "error
541 * @todo Define visibility
542 */
543 public function testFileRefs() {
544 $output = array();
545 // Handle direct references with upload folder setting (workaround)
546 $newCheckFileRefs = array();
547 foreach ($this->checkFileRefs as $folder => $files) {
548 // Only direct references without a folder setting
549 if ($folder !== '') {
550 $newCheckFileRefs[$folder] = $files;
551 continue;
552 }
553 foreach ($files as $file => $references) {
554 // Direct file references have often many references (removes occurences in the moreReferences section of the result array)
555 if ($references > 1) {
556 $references = 1;
557 }
558 // The directory must be empty (prevents checking of the root directory)
559 $directory = dirname($file);
560 if ($directory !== '') {
561 $newCheckFileRefs[$directory][basename($file)] = $references;
562 }
563 }
564 }
565 $this->checkFileRefs = $newCheckFileRefs;
566 foreach ($this->checkFileRefs as $folder => $fileArr) {
567 $path = PATH_site . $folder;
568 if (@is_dir($path) && @is_readable($path)) {
569 $d = dir($path);
570 while ($entry = $d->read()) {
571 if (@is_file(($path . '/' . $entry))) {
572 if (isset($fileArr[$entry])) {
573 if ($fileArr[$entry] > 1) {
574 $temp = $this->whereIsFileReferenced($folder, $entry);
575 $tempList = '';
576 foreach ($temp as $inf) {
577 $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
578 }
579 $output['moreReferences'][] = array($path, $entry, $fileArr[$entry], $tempList);
580 }
581 unset($fileArr[$entry]);
582 } else {
583 // Contains workaround for direct references
584 if (!strstr($entry, 'index.htm') && !preg_match(('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/'), $folder)) {
585 $output['noReferences'][] = array($path, $entry);
586 }
587 }
588 }
589 }
590 $d->close();
591 $tempCounter = 0;
592 foreach ($fileArr as $file => $value) {
593 // Workaround for direct file references
594 if (preg_match('/^' . preg_quote($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/', $folder)) {
595 $file = $folder . '/' . $file;
596 $folder = '';
597 $path = substr(PATH_site, 0, -1);
598 }
599 $temp = $this->whereIsFileReferenced($folder, $file);
600 $tempList = '';
601 foreach ($temp as $inf) {
602 $tempList .= '[' . $inf['table'] . '][' . $inf['uid'] . '][' . $inf['field'] . '] (pid:' . $inf['pid'] . ') - ';
603 }
604 $tempCounter++;
605 $output['noFile'][substr($path, -3) . '_' . substr($file, 0, 3) . '_' . $tempCounter] = array($path, $file, $tempList);
606 }
607 } else {
608 $output['error'][] = array($path);
609 }
610 }
611 return $output;
612 }
613
614 /**
615 * Depends on selectNonEmpty.... to be executed first!!
616 *
617 * @param array $theArray Table with key/value pairs being table names and arrays with uid numbers
618 * @return string HTML Error message
619 * @todo Define visibility
620 */
621 public function testDBRefs($theArray) {
622 $result = '';
623 foreach ($theArray as $table => $dbArr) {
624 if ($GLOBALS['TCA'][$table]) {
625 $idlist = array_keys($dbArr);
626 $theList = implode(',', $idlist);
627 if ($theList) {
628 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $theList . ')' . BackendUtility::deleteClause($table));
629 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
630 if (isset($dbArr[$row['uid']])) {
631 unset($dbArr[$row['uid']]);
632 } else {
633 $result .= 'Strange Error. ...<br />';
634 }
635 }
636 $GLOBALS['TYPO3_DB']->sql_free_result($mres);
637 foreach ($dbArr as $theId => $theC) {
638 $result .= 'There are ' . $theC . ' records pointing to this missing or deleted record; [' . $table . '][' . $theId . ']<br />';
639 }
640 }
641 } else {
642 $result .= 'Codeerror. Table is not a table...<br />';
643 }
644 }
645 return $result;
646 }
647
648 /**
649 * Finding all references to record based on table/uid
650 *
651 * @param string $searchTable Table name
652 * @param integer $id Uid of database record
653 * @return array Array with other arrays containing information about where references was found
654 * @todo Define visibility
655 */
656 public function whereIsRecordReferenced($searchTable, $id) {
657 // Gets tables / Fields that reference to files
658 $fileFields = $this->getDBFields($searchTable);
659 $theRecordList = array();
660 foreach ($fileFields as $info) {
661 $table = $info[0];
662 $field = $info[1];
663 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'] . ',' . $field, $table, $field . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($id, $table) . '%\'');
664 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
665 // Now this is the field, where the reference COULD come from. But we're not garanteed, so we must carefully examine the data.
666 $fieldConf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
667 $allowedTables = $fieldConf['type'] == 'group' ? $fieldConf['allowed'] : $fieldConf['foreign_table'];
668 /** @var $dbAnalysis \TYPO3\CMS\Core\Database\RelationHandler */
669 $dbAnalysis = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\RelationHandler');
670 $dbAnalysis->start($row[$field], $allowedTables, $fieldConf['MM'], $row['uid'], $table, $fieldConf);
671 foreach ($dbAnalysis->itemArray as $tempArr) {
672 if ($tempArr['table'] == $searchTable && $tempArr['id'] == $id) {
673 $theRecordList[] = array('table' => $table, 'uid' => $row['uid'], 'field' => $field, 'pid' => $row['pid']);
674 }
675 }
676 }
677 $GLOBALS['TYPO3_DB']->sql_free_result($mres);
678 }
679 return $theRecordList;
680 }
681
682 /**
683 * Finding all references to file based on uploadfolder / filename
684 *
685 * @param string $uploadfolder Upload folder where file is found
686 * @param string $filename Filename to search for
687 * @return array Array with other arrays containing information about where references was found
688 * @todo Define visibility
689 */
690 public function whereIsFileReferenced($uploadfolder, $filename) {
691 // Gets tables / Fields that reference to files
692 $fileFields = $this->getFileFields($uploadfolder);
693 $theRecordList = array();
694 foreach ($fileFields as $info) {
695 $table = $info[0];
696 $field = $info[1];
697 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,pid,' . $GLOBALS['TCA'][$table]['ctrl']['label'] . ',' . $field, $table, $field . ' LIKE \'%' . $GLOBALS['TYPO3_DB']->quoteStr($filename, $table) . '%\'');
698 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
699 // Now this is the field, where the reference COULD come from.
700 // But we're not guaranteed, so we must carefully examine the data.
701 $tempArr = explode(',', trim($row[$field]));
702 foreach ($tempArr as $file) {
703 $file = trim($file);
704 if ($file == $filename) {
705 $theRecordList[] = array('table' => $table, 'uid' => $row['uid'], 'field' => $field, 'pid' => $row['pid']);
706 }
707 }
708 }
709 $GLOBALS['TYPO3_DB']->sql_free_result($mres);
710 }
711 return $theRecordList;
712 }
713
714 }