2 /***************************************************************
5 * (c) 1999-2004 Kasper Skaarhoj (kasperYYYY@typo3.com)
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Contains a class for evaluation of database integrity according to $TCA
31 * Revised for TYPO3 3.6 July/2003 by Kasper Skaarhoj
34 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
37 * [CLASS/FUNCTION INDEX of SCRIPT]
41 * 90: class t3lib_admin
42 * 116: function genTree($theID, $depthData)
43 * 156: function lostRecords($pid_list)
44 * 187: function fixLostRecord($table,$uid)
45 * 208: function countRecords($pid_list)
46 * 236: function getGroupFields($mode)
47 * 270: function getFileFields($uploadfolder)
48 * 293: function getDBFields($theSearchTable)
49 * 321: function selectNonEmptyRecordsWithFkeys($fkey_arrays)
50 * 394: function testFileRefs ()
51 * 445: function testDBRefs($theArray)
52 * 483: function whereIsRecordReferenced($searchTable,$id)
53 * 520: function whereIsFileReferenced($uploadfolder,$filename)
56 * (This index is automatically created/updated by the extension "extdeveval")
80 * This class holds functions used by the TYPO3 backend to check the integrity of the database (The DBint module, 'lowlevel' extension)
82 * Depends on: Depends on loaddbgroup from t3lib/
84 * @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!
85 * @see SC_mod_tools_dbint_index::func_relations(), SC_mod_tools_dbint_index::func_records()
86 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
91 var $genTree_includeDeleted = 1; // if set, genTree() includes deleted pages. This is default.
92 var $perms_clause=''; // extra where-clauses for the tree-selection
93 var $genTree_makeHTML = 0; // if set, genTree() generates HTML, that visualizes the tree.
95 var $genTree_idlist = ''; // Will hold the id-list from genTree()
96 var $getTree_HTML = ''; // Will hold the HTML-code visualising the tree. genTree()
100 var $checkFileRefs = Array();
101 var $checkSelectDBRefs = Array(); // From the select-fields
102 var $checkGroupDBRefs = Array(); // From the group-fields
104 var $page_idArray=Array();
105 var $recStat = Array();
106 var $lRecords = Array();
107 var $lostPagesList = '';
110 * Generates a list of Page-uid's that corresponds to the tables in the tree. This list should ideally include all records in the pages-table.
112 * @param integer a pid (page-record id) from which to start making the tree
113 * @param string HTML-code (image-tags) used when this function calls itself recursively.
114 * @return integer Number of $GLOBALS['TYPO3_DB']->sql_num_rows (most recent query)
116 function genTree($theID, $depthData) {
117 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
118 'uid,title,doktype,deleted'.(t3lib_extMgm
::isLoaded('cms')?
',hidden':''),
120 'pid='.intval($theID).' '.((!$this->genTree_includeDeleted
)?
'AND deleted=0':'').$this->perms_clause
,
125 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
126 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
129 if ($this->genTree_makeHTML
) {
130 $this->genTree_HTML
.=chr(10).'<div><span class="nobr">';
132 $LN = ($a==$c)?
'blank':'line';
133 $BTM = ($a==$c)?
'bottom':'';
134 $this->genTree_HTML
.= $depthData.'<img'.t3lib_iconWorks
::skinImg($this->backPath
,'gfx/ol/'.$PM.$BTM.'.gif','width="18" height="16"').' align="top" alt="" />'.t3lib_iconWorks
::getIconImage('pages',$row,$this->backPath
,'align="top"').htmlspecialchars($row['uid'].': '.t3lib_div
::fixed_lgd_cs(strip_tags($row['title']),50)).'</span></div>';
137 if (isset($page_idlist[$newID])) {
138 $this->recStat
['doublePageID'][]=$newID;
140 $this->page_idArray
[$newID]=$newID;
141 if ($row['deleted']) {$this->recStat
['deleted']++
;}
142 if ($row['hidden']) {$this->recStat
['hidden']++
;}
143 $this->recStat
['doktype'][$row['doktype']]++
;
145 $this->genTree($newID,$this->genTree_HTML ?
$depthData.'<img'.t3lib_iconWorks
::skinImg($this->backPath
,'gfx/ol/'.$LN.'.gif','width="18" height="16"').' align="top" alt="" />' : '');
147 return $GLOBALS['TYPO3_DB']->sql_num_rows($res);
151 * Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
153 * @param string list of pid's (page-record uid's). This list is probably made by genTree()
156 function lostRecords($pid_list) {
159 $this->lostPagesList
='';
161 while (list($table)=each($TCA)) {
162 t3lib_div
::loadTCA($table);
163 $garbage = $GLOBALS['TYPO3_DB']->exec_SELECTquery (
164 'uid,pid,'.$TCA[$table]['ctrl']['label'],
166 'pid NOT IN ('.$pid_list.')'
169 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($garbage)) {
170 $this->lRecords
[$table][$row['uid']]=Array('uid'=>$row['uid'], 'pid'=>$row['pid'], 'title'=> strip_tags($row[$TCA[$table]['ctrl']['label']]) );
171 $lostIdList[]=$row['uid'];
173 if ($table=='pages') {
174 $this->lostPagesList
=implode(',',$lostIdList);
181 * Fixes lost record from $table with uid $uid by setting the PID to zero. If there is a disabled column for the record that will be set as well.
183 * @param string Database tablename
184 * @param integer The uid of the record which will have the PID value set to 0 (zero)
185 * @return boolean True if done.
187 function fixLostRecord($table,$uid) {
188 if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords
[$table][$uid]) && $GLOBALS['BE_USER']->user
['admin']) {
190 $updateFields = array();
191 $updateFields['pid'] = 0;
192 if ($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']) { // If possible a lost record restored is hidden as default
193 $updateFields[$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled']] = 1;
196 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields);
203 * Counts records from $TCA-tables that ARE attached to an existing page.
205 * @param string list of pid's (page-record uid's). This list is probably made by genTree()
206 * @return array an array with the number of records from all $TCA-tables that are attached to a PID in the pid-list.
208 function countRecords($pid_list) {
214 while (list($table)=each($TCA)) {
215 t3lib_div
::loadTCA($table);
216 $count = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*)', $table, 'pid IN ('.$pid_list.')');
217 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($count)) {
218 $list[$table]=$row[0];
221 $count = $GLOBALS['TYPO3_DB']->exec_SELECTquery('count(*)', $table, 'pid IN ('.$pid_list.')'.t3lib_BEfunc
::deleteClause($table));
222 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($count)) {
223 $list_n[$table]=$row[0];
227 return array('all' => $list, 'non_deleted' => $list_n);
231 * Finding relations in database based on type 'group' (files or database-uid's in a list)
233 * @param string $mode = file, $mode = db, $mode = '' (all...)
234 * @return array An array with all fields listed that somehow are references to other records (foreign-keys) or files
236 function getGroupFields($mode) {
240 while (list($table)=each($TCA)) {
241 t3lib_div
::loadTCA($table);
242 $cols = $TCA[$table]['columns'];
244 while (list($field,$config)=each($cols)) {
245 if ($config['config']['type']=='group') {
247 ((!$mode||
$mode=='file') && $config['config']['internal_type']=='file') ||
248 ((!$mode||
$mode=='db') && $config['config']['internal_type']=='db')
250 $result[$table][]=$field;
253 if ( (!$mode||
$mode=='db') && $config['config']['type']=='select' && $config['config']['foreign_table']) {
254 $result[$table][]=$field;
257 if ($result[$table]) {
258 $result[$table] = implode(',',$result[$table]);
265 * Finds all fields that hold filenames from uploadfolder
267 * @param string Path to uploadfolder
268 * @return array An array with all fields listed that have references to files in the $uploadfolder
270 function getFileFields($uploadfolder) {
274 while (list($table)=each($TCA)) {
275 t3lib_div
::loadTCA($table);
276 $cols = $TCA[$table]['columns'];
278 while (list($field,$config)=each($cols)) {
279 if ($config['config']['type']=='group' && $config['config']['internal_type']=='file' && $config['config']['uploadfolder']==$uploadfolder) {
280 $result[]=Array($table,$field);
288 * Returns an array with arrays of table/field pairs which are allowed to hold references to the input table name - according to $TCA
290 * @param string Table name
293 function getDBFields($theSearchTable) {
297 while (list($table)=each($TCA)) {
298 t3lib_div
::loadTCA($table);
299 $cols = $TCA[$table]['columns'];
301 while (list($field,$config)=each($cols)) {
302 if ($config['config']['type']=='group' && $config['config']['internal_type']=='db') {
303 if (trim($config['config']['allowed'])=='*' ||
strstr($config['config']['allowed'],$theSearchTable)) {
304 $result[]=Array($table,$field);
306 } else if ($config['config']['type']=='select' && $config['config']['foreign_table']==$theSearchTable) {
307 $result[]=Array($table,$field);
315 * This selects non-empty-records from the tables/fields in the fkey_array generated by getGroupFields()
317 * @param array Array with tables/fields generated by getGroupFields()
319 * @see getGroupFields()
321 function selectNonEmptyRecordsWithFkeys($fkey_arrays) {
323 if (is_array($fkey_arrays)) {
325 while (list($table,$field_list)=each($fkey_arrays)) {
326 if ($TCA[$table] && trim($field_list)) {
327 t3lib_div
::loadTCA($table);
328 $fieldArr = explode(',',$field_list);
329 $cl_fl = implode ('!="" OR ',$fieldArr). '!=""';
330 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,'.$field_list, $table, $cl_fl);
331 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
333 while (list(,$field)=each($fieldArr)) {
334 if (trim($row[$field])) {
335 $fieldConf = $TCA[$table]['columns'][$field]['config'];
336 if ($fieldConf['type']=='group') {
337 if ($fieldConf['internal_type']=='file') {
339 if ($fieldConf['MM']) {
341 $dbAnalysis = t3lib_div
::makeInstance('t3lib_loadDBGroup');
342 $dbAnalysis->start('','files',$fieldConf['MM'],$row['uid']);
343 reset($dbAnalysis->itemArray
);
344 while (list($somekey,$someval)=each($dbAnalysis->itemArray
)) {
345 if ($someval['id']) {
346 $tempArr[]=$someval['id'];
350 $tempArr = explode(',',trim($row[$field]));
353 while (list(,$file)=each($tempArr)) {
356 $this->checkFileRefs
[$fieldConf['uploadfolder']][$file]+
=1;
360 if ($fieldConf['internal_type']=='db') {
362 $dbAnalysis = t3lib_div
::makeInstance('t3lib_loadDBGroup');
363 $dbAnalysis->start($row[$field],$fieldConf['allowed'],$fieldConf['MM'],$row['uid']);
364 reset($dbAnalysis->itemArray
);
365 while (list(,$tempArr)=each($dbAnalysis->itemArray
)) {
366 $this->checkGroupDBRefs
[$tempArr['table']][$tempArr['id']]+
=1;
370 if ($fieldConf['type']=='select' && $fieldConf['foreign_table']) {
372 $dbAnalysis = t3lib_div
::makeInstance('t3lib_loadDBGroup');
373 $dbAnalysis->start($row[$field],$fieldConf['foreign_table'],$fieldConf['MM'],$row['uid']);
374 reset($dbAnalysis->itemArray
);
375 while (list(,$tempArr)=each($dbAnalysis->itemArray
)) {
376 if ($tempArr['id']>0) {
377 $this->checkGroupDBRefs
[$fieldConf['foreign_table']][$tempArr['id']]+
=1;
390 * Depends on selectNonEmpty.... to be executed first!!
392 * @return array Report over files; keys are "moreReferences", "noReferences", "noFile", "error"
394 function testFileRefs () {
396 reset($this->checkFileRefs
);
397 while(list($folder,$fileArr)=each($this->checkFileRefs
)) {
398 $path = PATH_site
.$folder;
399 if (@is_dir
($path)) {
401 while($entry=$d->read()) {
402 if (@is_file
($path.'/'.$entry)) {
403 if (isset($fileArr[$entry])) {
404 if ($fileArr[$entry] > 1) {
405 $temp = $this->whereIsFileReferenced($folder,$entry);
407 while(list(,$inf)=each($temp)) {
408 $tempList.='['.$inf['table'].']['.$inf['uid'].']['.$inf['field'].'] (pid:'.$inf['pid'].') - ';
410 $output['moreReferences'][] = Array($path,$entry,$fileArr[$entry],$tempList);
412 unset($fileArr[$entry]);
414 if (!strstr($entry,'index.htm')) {
415 $output['noReferences'][] = Array($path,$entry);
423 while(list($file,)=each($fileArr)) {
424 $temp = $this->whereIsFileReferenced($folder,$file);
426 while(list(,$inf)=each($temp)) {
427 $tempList.='['.$inf['table'].']['.$inf['uid'].']['.$inf['field'].'] (pid:'.$inf['pid'].') - ';
430 $output['noFile'][substr($path,-3).'_'.substr($file,0,3).'_'.$tempCounter] = Array($path,$file,$tempList);
433 $output['error'][] = Array($path);
440 * Depends on selectNonEmpty.... to be executed first!!
442 * @param array Table with key/value pairs being table names and arrays with uid numbers
443 * @return string HTML Error message
445 function testDBRefs($theArray) {
448 while(list($table,$dbArr)=each($theArray)) {
451 while(list($id,)=each($dbArr)) {
454 $theList = implode(',',$idlist);
456 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN ('.$theList.')'.t3lib_BEfunc
::deleteClause($table));
457 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
458 if (isset($dbArr[$row['uid']])) {
459 unset ($dbArr[$row['uid']]);
461 $result.='Strange Error. ...<br />';
465 while (list($theId,$theC)=each($dbArr)) {
466 $result.='There are '.$theC.' records pointing to this missing or deleted record; ['.$table.']['.$theId.']<br />';
470 $result.='Codeerror. Table is not a table...<br />';
477 * Finding all references to record based on table/uid
479 * @param string Table name
480 * @param integer Uid of database record
481 * @return array Array with other arrays containing information about where references was found
483 function whereIsRecordReferenced($searchTable,$id) {
485 $fileFields = $this->getDBFields($searchTable); // Gets tables / Fields that reference to files...
486 $theRecordList=Array();
487 while (list(,$info)=each($fileFields)) {
488 $table=$info[0]; $field=$info[1];
489 t3lib_div
::loadTCA($table);
490 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
491 'uid,pid,'.$TCA[$table]['ctrl']['label'].','.$field,
493 $field.' LIKE "%'.$GLOBALS['TYPO3_DB']->quoteStr($id, $table).'%"'
495 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
496 // Now this is the field, where the reference COULD come from. But we're not garanteed, so we must carefully examine the data.
497 $fieldConf = $TCA[$table]['columns'][$field]['config'];
498 $allowedTables = ($fieldConf['type']=='group') ?
$fieldConf['allowed'] : $fieldConf['foreign_table'];
500 $dbAnalysis = t3lib_div
::makeInstance('t3lib_loadDBGroup');
501 $dbAnalysis->start($row[$field],$allowedTables,$fieldConf['MM'],$row['uid']);
502 reset($dbAnalysis->itemArray
);
503 while (list(,$tempArr)=each($dbAnalysis->itemArray
)) {
504 if ($tempArr['table']==$searchTable && $tempArr['id']==$id) {
505 $theRecordList[]=Array('table'=>$table,'uid'=>$row['uid'],'field'=>$field,'pid'=>$row['pid']);
510 return $theRecordList;
514 * Finding all references to file based on uploadfolder / filename
516 * @param string Upload folder where file is found
517 * @param string Filename to search for
518 * @return array Array with other arrays containing information about where references was found
520 function whereIsFileReferenced($uploadfolder,$filename) {
522 $fileFields = $this->getFileFields($uploadfolder); // Gets tables / Fields that reference to files...
523 $theRecordList=Array();
524 while (list(,$info)=each($fileFields)) {
525 $table=$info[0]; $field=$info[1];
526 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
527 'uid,pid,'.$TCA[$table]['ctrl']['label'].','.$field,
529 $field.' LIKE "%'.$GLOBALS['TYPO3_DB']->quoteStr($filename, $table).'%"'
531 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
532 // Now this is the field, where the reference COULD come from. But we're not garanteed, so we must carefully examine the data.
533 $tempArr = explode(',',trim($row[$field]));
534 while (list(,$file)=each($tempArr)) {
536 if ($file==$filename) {
537 $theRecordList[]=Array('table'=>$table,'uid'=>$row['uid'],'field'=>$field,'pid'=>$row['pid']);
542 return $theRecordList;
547 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_admin.php']) {
548 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_admin.php']);