* Mount Points (Mount pages) reworking. See change log
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_admin.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2004 Kasper Skaarhoj (kasper@typo3.com)
6 * All rights reserved
7 *
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.
13 *
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.
18 *
19 *
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.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains a class for evaluation of database integrity according to $TCA
29 *
30 * $Id$
31 * Revised for TYPO3 3.6 July/2003 by Kasper Skaarhoj
32 * XHTML compliant
33 *
34 * @author Kasper Skaarhoj <kasper@typo3.com>
35 */
36 /**
37 * [CLASS/FUNCTION INDEX of SCRIPT]
38 *
39 *
40 *
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)
54 *
55 * TOTAL FUNCTIONS: 12
56 * (This index is automatically created/updated by the extension "extdeveval")
57 *
58 */
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 /**
80 * This class holds functions used by the TYPO3 backend to check the integrity of the database (The DBint module, 'lowlevel' extension)
81 *
82 * Depends on: Depends on loaddbgroup from t3lib/
83 *
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 <kasper@typo3.com>
87 * @package TYPO3
88 * @subpackage t3lib
89 */
90 class t3lib_admin {
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.
94 // internal
95 var $genTree_idlist = ''; // Will hold the id-list from genTree()
96 var $getTree_HTML = ''; // Will hold the HTML-code visualising the tree. genTree()
97 var $backPath='';
98
99 // internal
100 var $checkFileRefs = Array();
101 var $checkSelectDBRefs = Array(); // From the select-fields
102 var $checkGroupDBRefs = Array(); // From the group-fields
103
104 var $page_idArray=Array();
105 var $recStat = Array();
106 var $lRecords = Array();
107 var $lostPagesList = '';
108
109 /**
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.
111 *
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)
115 */
116 function genTree($theID, $depthData) {
117 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
118 'uid,title,doktype,deleted'.(t3lib_extMgm::isLoaded('cms')?',hidden':''),
119 'pages',
120 'pid='.intval($theID).' '.((!$this->genTree_includeDeleted)?'AND deleted=0':'').$this->perms_clause,
121 '',
122 'sorting'
123 );
124 $a=0;
125 $c = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
126 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
127 $a++;
128 $newID =$row['uid'];
129 if ($this->genTree_makeHTML) {
130 $this->genTree_HTML.=chr(10).'<div><span class="nobr">';
131 $PM = 'join';
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(strip_tags($row['title']),50)).'</span></div>';
135 }
136
137 if (isset($page_idlist[$newID])) {
138 $this->recStat['doublePageID'][]=$newID;
139 }
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']]++;
144
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="" />' : '');
146 }
147 return $GLOBALS['TYPO3_DB']->sql_num_rows($res);
148 }
149
150 /**
151 * Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
152 *
153 * @param string list of pid's (page-record uid's). This list is probably made by genTree()
154 * @return void
155 */
156 function lostRecords($pid_list) {
157 global $TCA;
158 reset($TCA);
159 $this->lostPagesList='';
160 if ($pid_list) {
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'],
165 $table,
166 'pid NOT IN ('.$pid_list.')'
167 );
168 $lostIdList=Array();
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'];
172 }
173 if ($table=='pages') {
174 $this->lostPagesList=implode($lostIdList,',');
175 }
176 }
177 }
178 }
179
180 /**
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.
182 *
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.
186 */
187 function fixLostRecord($table,$uid) {
188 if ($table && $GLOBALS['TCA'][$table] && $uid && is_array($this->lRecords[$table][$uid]) && $GLOBALS['BE_USER']->user['admin']) {
189
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;
194 }
195
196 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields);
197
198 return TRUE;
199 } else return FALSE;
200 }
201
202 /**
203 * Counts records from $TCA-tables that ARE attached to an existing page.
204 *
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.
207 */
208 function countRecords($pid_list) {
209 global $TCA;
210 reset($TCA);
211 $list=Array();
212 $list_n=Array();
213 if ($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];
219 }
220
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];
224 }
225 }
226 }
227 return array('all' => $list, 'non_deleted' => $list_n);
228 }
229
230 /**
231 * Finding relations in database based on type 'group' (files or database-uid's in a list)
232 *
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
235 */
236 function getGroupFields($mode) {
237 global $TCA;
238 reset ($TCA);
239 $result = Array();
240 while (list($table)=each($TCA)) {
241 t3lib_div::loadTCA($table);
242 $cols = $TCA[$table]['columns'];
243 reset ($cols);
244 while (list($field,$config)=each($cols)) {
245 if ($config['config']['type']=='group') {
246 if (
247 ((!$mode||$mode=='file') && $config['config']['internal_type']=='file') ||
248 ((!$mode||$mode=='db') && $config['config']['internal_type']=='db')
249 ) {
250 $result[$table][]=$field;
251 }
252 }
253 if ( (!$mode||$mode=='db') && $config['config']['type']=='select' && $config['config']['foreign_table']) {
254 $result[$table][]=$field;
255 }
256 }
257 if ($result[$table]) {
258 $result[$table] = implode(',',$result[$table]);
259 }
260 }
261 return $result;
262 }
263
264 /**
265 * Finds all fields that hold filenames from uploadfolder
266 *
267 * @param string Path to uploadfolder
268 * @return array An array with all fields listed that have references to files in the $uploadfolder
269 */
270 function getFileFields($uploadfolder) {
271 global $TCA;
272 reset ($TCA);
273 $result = Array();
274 while (list($table)=each($TCA)) {
275 t3lib_div::loadTCA($table);
276 $cols = $TCA[$table]['columns'];
277 reset ($cols);
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);
281 }
282 }
283 }
284 return $result;
285 }
286
287 /**
288 * Returns an array with arrays of table/field pairs which are allowed to hold references to the input table name - according to $TCA
289 *
290 * @param string Table name
291 * @return array
292 */
293 function getDBFields($theSearchTable) {
294 global $TCA;
295 $result = Array();
296 reset ($TCA);
297 while (list($table)=each($TCA)) {
298 t3lib_div::loadTCA($table);
299 $cols = $TCA[$table]['columns'];
300 reset ($cols);
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);
305 }
306 } else if ($config['config']['type']=='select' && $config['config']['foreign_table']==$theSearchTable) {
307 $result[]=Array($table,$field);
308 }
309 }
310 }
311 return $result;
312 }
313
314 /**
315 * This selects non-empty-records from the tables/fields in the fkey_array generated by getGroupFields()
316 *
317 * @param array Array with tables/fields generated by getGroupFields()
318 * @return void
319 * @see getGroupFields()
320 */
321 function selectNonEmptyRecordsWithFkeys($fkey_arrays) {
322 global $TCA;
323 if (is_array($fkey_arrays)) {
324 reset($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)) {
332 reset($fieldArr);
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') {
338 // files...
339 if ($fieldConf['MM']) {
340 $tempArr=array();
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'];
347 }
348 }
349 } else {
350 $tempArr = explode(',',trim($row[$field]));
351 }
352 reset($tempArr);
353 while (list(,$file)=each($tempArr)) {
354 $file = trim($file);
355 if ($file) {
356 $this->checkFileRefs[$fieldConf['uploadfolder']][$file]+=1;
357 }
358 }
359 }
360 if ($fieldConf['internal_type']=='db') {
361 // dbs - group
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;
367 }
368 }
369 }
370 if ($fieldConf['type']=='select' && $fieldConf['foreign_table']) {
371 // dbs - select
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;
378 }
379 }
380 }
381 }
382 }
383 }
384 }
385 }
386 }
387 }
388
389 /**
390 * Depends on selectNonEmpty.... to be executed first!!
391 *
392 * @return array Report over files; keys are "moreReferences", "noReferences", "noFile", "error"
393 */
394 function testFileRefs () {
395 $output=Array();
396 reset($this->checkFileRefs);
397 while(list($folder,$fileArr)=each($this->checkFileRefs)) {
398 $path = PATH_site.$folder;
399 if (@is_dir($path)) {
400 $d = 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);
406 $tempList = '';
407 while(list(,$inf)=each($temp)) {
408 $tempList.='['.$inf['table'].']['.$inf['uid'].']['.$inf['field'].'] (pid:'.$inf['pid'].') - ';
409 }
410 $output['moreReferences'][] = Array($path,$entry,$fileArr[$entry],$tempList);
411 }
412 unset($fileArr[$entry]);
413 } else {
414 if (!strstr($entry,'index.htm')) {
415 $output['noReferences'][] = Array($path,$entry);
416 }
417 }
418 }
419 }
420 $d->close();
421 reset($fileArr);
422 $tempCounter=0;
423 while(list($file,)=each($fileArr)) {
424 $temp = $this->whereIsFileReferenced($folder,$file);
425 $tempList = '';
426 while(list(,$inf)=each($temp)) {
427 $tempList.='['.$inf['table'].']['.$inf['uid'].']['.$inf['field'].'] (pid:'.$inf['pid'].') - ';
428 }
429 $tempCounter++;
430 $output['noFile'][substr($path,-3).'_'.substr($file,0,3).'_'.$tempCounter] = Array($path,$file,$tempList);
431 }
432 } else {
433 $output['error'][] = Array($path);
434 }
435 }
436 return $output;
437 }
438
439 /**
440 * Depends on selectNonEmpty.... to be executed first!!
441 *
442 * @param array Table with key/value pairs being table names and arrays with uid numbers
443 * @return string HTML Error message
444 */
445 function testDBRefs($theArray) {
446 global $TCA;
447 reset($theArray);
448 while(list($table,$dbArr)=each($theArray)) {
449 if ($TCA[$table]) {
450 $idlist = Array();
451 while(list($id,)=each($dbArr)) {
452 $idlist[]=$id;
453 }
454 $theList = implode($idlist,',');
455 if ($theList) {
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']]);
460 } else {
461 $result.='Strange Error. ...<br />';
462 }
463 }
464 reset($dbArr);
465 while (list($theId,$theC)=each($dbArr)) {
466 $result.='There are '.$theC.' records pointing to this missing or deleted record; ['.$table.']['.$theId.']<br />';
467 }
468 }
469 } else {
470 $result.='Codeerror. Table is not a table...<br />';
471 }
472 }
473 return $result;
474 }
475
476 /**
477 * Finding all references to record based on table/uid
478 *
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
482 */
483 function whereIsRecordReferenced($searchTable,$id) {
484 global $TCA;
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,
492 $table,
493 $field.' LIKE "%'.$GLOBALS['TYPO3_DB']->quoteStr($id, $table).'%"'
494 );
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'];
499
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']);
506 }
507 }
508 }
509 }
510 return $theRecordList;
511 }
512
513 /**
514 * Finding all references to file based on uploadfolder / filename
515 *
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
519 */
520 function whereIsFileReferenced($uploadfolder,$filename) {
521 global $TCA;
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,
528 $table,
529 $field.' LIKE "%'.$GLOBALS['TYPO3_DB']->quoteStr($filename, $table).'%"'
530 );
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)) {
535 $file = trim($file);
536 if ($file==$filename) {
537 $theRecordList[]=Array('table'=>$table,'uid'=>$row['uid'],'field'=>$field,'pid'=>$row['pid']);
538 }
539 }
540 }
541 }
542 return $theRecordList;
543 }
544 }
545
546
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']);
549 }
550 ?>