Added feature #11148: Remove static require_once() to t3lib and tslib and use autoloa...
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_refindex.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2009 Kasper Skaarhoj (kasperYYYY@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 * [CLASS/FUNCTION INDEX of SCRIPT]
29 *
30 *
31 *
32 * 89: class t3lib_refindex
33 * 107: function updateRefIndexTable($table,$uid,$testOnly=FALSE)
34 * 178: function generateRefIndexData($table,$uid)
35 * 255: function createEntryData($table,$uid,$field,$flexpointer,$deleted,$ref_table,$ref_uid,$ref_string='',$sort=-1,$softref_key='',$softref_id='')
36 * 282: function createEntryData_dbRels($table,$uid,$fieldname,$flexpointer,$deleted,$items)
37 * 299: function createEntryData_fileRels($table,$uid,$fieldname,$flexpointer,$deleted,$items)
38 * 320: function createEntryData_softreferences($table,$uid,$fieldname,$flexpointer,$deleted,$keys)
39 *
40 * SECTION: Get relations from table row
41 * 376: function getRelations($table,$row,$onlyField='')
42 * 473: function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, &$pObj)
43 * 523: function getRelations_procFiles($value, $conf, $uid)
44 * 573: function getRelations_procDB($value, $conf, $uid)
45 *
46 * SECTION: Setting values
47 * 616: function setReferenceValue($hash,$newValue,$returnDataArray=FALSE)
48 * 699: function setReferenceValue_dbRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='')
49 * 737: function setReferenceValue_fileRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='')
50 * 775: function setReferenceValue_softreferences($refRec,$softref,$newValue,&$dataArray,$flexpointer='')
51 *
52 * SECTION: Helper functions
53 * 822: function isReferenceField($conf)
54 * 832: function destPathFromUploadFolder($folder)
55 * 842: function error($msg)
56 * 853: function updateIndex($testOnly,$cli_echo=FALSE)
57 *
58 * TOTAL FUNCTIONS: 18
59 * (This index is automatically created/updated by the extension "extdeveval")
60 *
61 */
62
63
64
65
66 /**
67 * Reference index processing and relation extraction
68 *
69 * NOTICE: When the reference index is updated for an offline version the results may not be correct.
70 * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyse whole database!)
71 * Secondly, lets assume that in a Draft workspace you have changed the data structure of a parent page record - this is (in TemplaVoila) inherited by subpages.
72 * When in the LIVE workspace the data structure for the records/pages in the offline workspace will not be evaluated to the right one simply because the data structure is taken from a rootline traversal and in the Live workspace that will NOT include the changed DataSTructure! Thus the evaluation will be based on the Data Structure set in the Live workspace!
73 * Somehow this scenario is rarely going to happen. Yet, it is an inconsistency and I see now practical way to handle it - other than simply ignoring maintaining the index for workspace records. Or we can say that the index is precise for all Live elements while glitches might happen in an offline workspace?
74 * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
75 *
76 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
77 * @package TYPO3
78 * @subpackage t3lib
79 */
80 class t3lib_refindex {
81
82 var $temp_flexRelations = array();
83 var $errorLog = array();
84 var $WSOL = FALSE;
85 var $relations = array();
86
87 var $words_strings = array();
88 var $words = array();
89
90 var $hashVersion = 1; // Number which we can increase if a change in the code means we will have to force a re-generation of the index.
91
92
93 /**
94 * Call this function to update the sys_refindex table for a record (even one just deleted)
95 * NOTICE: Currently, references updated for a deleted-flagged record will not include those from within flexform fields in some cases where the data structure is defined by another record since the resolving process ignores deleted records! This will also result in bad cleaning up in tcemain I think... Anyway, thats the story of flexforms; as long as the DS can change, lots of references can get lost in no time.
96 *
97 * @param string Table name
98 * @param integer UID of record
99 * @param boolean If set, nothing will be written to the index but the result value will still report statistics on what is added, deleted and kept. Can be used for mere analysis.
100 * @return array Array with statistics about how many index records were added, deleted and not altered plus the complete reference set for the record.
101 */
102 function updateRefIndexTable($table,$uid,$testOnly=FALSE) {
103
104 // First, secure that the index table is not updated with workspace tainted relations:
105 $this->WSOL = FALSE;
106
107 // Init:
108 $result = array(
109 'keptNodes' => 0,
110 'deletedNodes' => 0,
111 'addedNodes' => 0
112 );
113
114 // Get current index from Database:
115 $currentRels = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
116 '*',
117 'sys_refindex',
118 'tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table,'sys_refindex').
119 ' AND recuid='.intval($uid),
120 '','','','hash'
121 );
122
123 // First, test to see if the record exists (including deleted-flagged)
124 if (t3lib_BEfunc::getRecordRaw($table,'uid='.intval($uid),'uid')) {
125
126 // Then, get relations:
127 $relations = $this->generateRefIndexData($table,$uid);
128
129 if (is_array($relations)) {
130
131 // Traverse the generated index:
132 foreach($relations as $k => $datRec) {
133 $relations[$k]['hash'] = md5(implode('///',$relations[$k]).'///'.$this->hashVersion);
134
135 // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
136 if (isset($currentRels[$relations[$k]['hash']])) {
137 unset($currentRels[$relations[$k]['hash']]);
138 $result['keptNodes']++;
139 $relations[$k]['_ACTION'] = 'KEPT';
140 } else {
141 // If new, add it:
142 if (!$testOnly) $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_refindex',$relations[$k]);
143 $result['addedNodes']++;
144 $relations[$k]['_ACTION'] = 'ADDED';
145 }
146 }
147
148 $result['relations'] = $relations;
149 } else return FALSE; // Weird mistake I would say...
150
151 // Words:
152 if (!$testOnly) $this->wordIndexing($table,$uid);
153 }
154
155 // If any old are left, remove them:
156 if (count($currentRels)) {
157 $hashList = array_keys($currentRels);
158 if (count($hashList)) {
159 $result['deletedNodes'] = count($hashList);
160 $result['deletedNodes_hashList'] = implode(',',$hashList);
161 if (!$testOnly) $GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_refindex','hash IN ('.implode(',',$GLOBALS['TYPO3_DB']->fullQuoteArray($hashList,'sys_refindex')).')');
162 }
163 }
164
165 return $result;
166 }
167
168 /**
169 * Returns array of arrays with an index of all references found in record from table/uid
170 * If the result is used to update the sys_refindex table then ->WSOL must NOT be true (no workspace overlay anywhere!)
171 *
172 * @param string Table name from $TCA
173 * @param integer Record UID
174 * @return array Index Rows
175 */
176 function generateRefIndexData($table,$uid) {
177 global $TCA;
178
179 if (isset($TCA[$table])) {
180 // Get raw record from DB:
181 list($record) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*',$table,'uid='.intval($uid));
182
183 if (is_array($record)) {
184
185 // Initialize:
186 $this->words_strings = array();
187 $this->words = array();
188
189 // Deleted:
190 $deleted = $TCA[$table]['ctrl']['delete'] ? ($record[$TCA[$table]['ctrl']['delete']]?1:0) : 0;
191
192 // Get all relations from record:
193 $dbrels = $this->getRelations($table,$record);
194
195 // Traverse those relations, compile records to insert in table:
196 $this->relations = array();
197 foreach($dbrels as $fieldname => $dat) {
198
199 // Based on type,
200 switch((string)$dat['type']) {
201 case 'db':
202 $this->createEntryData_dbRels($table,$uid,$fieldname,'',$deleted,$dat['itemArray']);
203 break;
204 case 'file':
205 $this->createEntryData_fileRels($table,$uid,$fieldname,'',$deleted,$dat['newValueFiles']);
206 break;
207 case 'flex':
208 // DB references:
209 if (is_array($dat['flexFormRels']['db'])) {
210 foreach($dat['flexFormRels']['db'] as $flexpointer => $subList) {
211 $this->createEntryData_dbRels($table,$uid,$fieldname,$flexpointer,$deleted,$subList);
212 }
213 }
214 // File references (NOT TESTED!)
215 if (is_array($dat['flexFormRels']['file'])) { // Not tested
216 foreach($dat['flexFormRels']['file'] as $flexpointer => $subList) {
217 $this->createEntryData_fileRels($table,$uid,$fieldname,$flexpointer,$deleted,$subList);
218 }
219 }
220 // Soft references in flexforms (NOT TESTED!)
221 if (is_array($dat['flexFormRels']['softrefs'])) {
222 foreach($dat['flexFormRels']['softrefs'] as $flexpointer => $subList) {
223 $this->createEntryData_softreferences($table,$uid,$fieldname,$flexpointer,$deleted,$subList['keys']);
224 }
225 }
226 break;
227 }
228
229 // Softreferences in the field:
230 if (is_array($dat['softrefs'])) {
231 $this->createEntryData_softreferences($table,$uid,$fieldname,'',$deleted,$dat['softrefs']['keys']);
232 }
233 }
234
235 // Word indexing:
236 t3lib_div::loadTCA($table);
237 foreach($TCA[$table]['columns'] as $field => $conf) {
238 if (t3lib_div::inList('input,text',$conf['config']['type']) && strcmp($record[$field],'') && !t3lib_div::testInt($record[$field])) {
239 $this->words_strings[$field] = $record[$field];
240 }
241 }
242
243 return $this->relations;
244 }
245 }
246 }
247
248 /**
249 * Create array with field/value pairs ready to insert in database.
250 * The "hash" field is a fingerprint value across this table.
251 *
252 * @param string Tablename of source record (where reference is located)
253 * @param integer UID of source record (where reference is located)
254 * @param string Fieldname of source record (where reference is located)
255 * @param string Pointer to location inside flexform structure where reference is located in [field]
256 * @param integer Whether record is deleted-flagged or not
257 * @param string For database references; the tablename the reference points to. Special keyword "_FILE" indicates that "ref_string" is a file reference either absolute or relative to PATH_site. Special keyword "_STRING" indicates some special usage (typ. softreference) where "ref_string" is used for the value.
258 * @param integer For database references; The UID of the record (zero "ref_table" is "_FILE" or "_STRING")
259 * @param string For "_FILE" or "_STRING" references: The filepath (relative to PATH_site or absolute) or other string.
260 * @param integer The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
261 * @param string If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
262 * @param string Soft reference ID for key. Might be useful for replace operations.
263 * @return array Array record to insert into table.
264 */
265 function createEntryData($table,$uid,$field,$flexpointer,$deleted,$ref_table,$ref_uid,$ref_string='',$sort=-1,$softref_key='',$softref_id='') {
266 return array(
267 'tablename' => $table,
268 'recuid' => $uid,
269 'field' => $field,
270 'flexpointer' => $flexpointer,
271 'softref_key' => $softref_key,
272 'softref_id' => $softref_id,
273 'sorting' => $sort,
274 'deleted' => $deleted,
275 'ref_table' => $ref_table,
276 'ref_uid' => $ref_uid,
277 'ref_string' => $ref_string,
278 );
279 }
280
281 /**
282 * Enter database references to ->relations array
283 *
284 * @param string [See createEntryData, arg 1]
285 * @param integer [See createEntryData, arg 2]
286 * @param string [See createEntryData, arg 3]
287 * @param string [See createEntryData, arg 4]
288 * @param string [See createEntryData, arg 5]
289 * @param array Data array with databaes relations (table/id)
290 * @return void
291 */
292 function createEntryData_dbRels($table,$uid,$fieldname,$flexpointer,$deleted,$items) {
293 foreach($items as $sort => $i) {
294 $this->relations[] = $this->createEntryData($table,$uid,$fieldname,$flexpointer,$deleted,$i['table'],$i['id'],'',$sort);
295 }
296 }
297
298 /**
299 * Enter file references to ->relations array
300 *
301 * @param string [See createEntryData, arg 1]
302 * @param integer [See createEntryData, arg 2]
303 * @param string [See createEntryData, arg 3]
304 * @param string [See createEntryData, arg 4]
305 * @param string [See createEntryData, arg 5]
306 * @param array Data array with file relations
307 * @return void
308 */
309 function createEntryData_fileRels($table,$uid,$fieldname,$flexpointer,$deleted,$items) {
310 foreach($items as $sort => $i) {
311 $filePath = $i['ID_absFile'];
312 if (t3lib_div::isFirstPartOfStr($filePath,PATH_site)) {
313 $filePath = substr($filePath,strlen(PATH_site));
314 }
315 $this->relations[] = $this->createEntryData($table,$uid,$fieldname,$flexpointer,$deleted,'_FILE',0,$filePath,$sort);
316 }
317 }
318
319 /**
320 * Enter softref references to ->relations array
321 *
322 * @param string [See createEntryData, arg 1]
323 * @param integer [See createEntryData, arg 2]
324 * @param string [See createEntryData, arg 3]
325 * @param string [See createEntryData, arg 4]
326 * @param string [See createEntryData, arg 5]
327 * @param array Data array with soft reference keys
328 * @return void
329 */
330 function createEntryData_softreferences($table,$uid,$fieldname,$flexpointer,$deleted,$keys) {
331 if (is_array($keys)) {
332 foreach($keys as $spKey => $elements) {
333 if (is_array($elements)) {
334 foreach($elements as $subKey => $el) {
335 if (is_array($el['subst'])) {
336 switch((string)$el['subst']['type']) {
337 case 'db':
338 list($tableName,$recordId) = explode(':',$el['subst']['recordRef']);
339 $this->relations[] = $this->createEntryData($table,$uid,$fieldname,$flexpointer,$deleted,$tableName,$recordId,'',-1,$spKey,$subKey);
340 break;
341 case 'file':
342 $this->relations[] = $this->createEntryData($table,$uid,$fieldname,$flexpointer,$deleted,'_FILE',0,$el['subst']['relFileName'],-1,$spKey,$subKey);
343 break;
344 case 'string':
345 $this->relations[] = $this->createEntryData($table,$uid,$fieldname,$flexpointer,$deleted,'_STRING',0,$el['subst']['tokenValue'],-1,$spKey,$subKey);
346 break;
347 }
348 }
349 }
350 }
351 }
352 }
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 /*******************************
370 *
371 * Get relations from table row
372 *
373 *******************************/
374
375 /**
376 * Returns relation information for a $table/$row-array
377 * Traverses all fields in input row which are configured in TCA/columns
378 * It looks for hard relations to files and records in the TCA types "select" and "group"
379 *
380 * @param string Table name
381 * @param array Row from table
382 * @param string Specific field to fetch for.
383 * @return array Array with information about relations
384 * @see export_addRecord()
385 */
386 function getRelations($table,$row,$onlyField='') {
387 global $TCA;
388
389 // Load full table description
390 t3lib_div::loadTCA($table);
391
392 // Initialize:
393 $uid = $row['uid'];
394 $nonFields = explode(',','uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,pid');
395
396 $outRow = array();
397 foreach($row as $field => $value) {
398 if (!in_array($field,$nonFields) && is_array($TCA[$table]['columns'][$field]) && (!$onlyField || $onlyField===$field)) {
399 $conf = $TCA[$table]['columns'][$field]['config'];
400
401 // Add files
402 if ($result = $this->getRelations_procFiles($value, $conf, $uid)) {
403 // Creates an entry for the field with all the files:
404 $outRow[$field] = array(
405 'type' => 'file',
406 'newValueFiles' => $result,
407 );
408 }
409
410 // Add DB:
411 if ($result = $this->getRelations_procDB($value, $conf, $uid, $table)) {
412 // Create an entry for the field with all DB relations:
413 $outRow[$field] = array(
414 'type' => 'db',
415 'itemArray' => $result,
416 );
417 }
418
419 // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
420 if ($conf['type']=='flex') {
421
422 // Get current value array:
423 // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up the note in the JavaDoc documentation for the function t3lib_BEfunc::getFlexFormDS()
424 $dataStructArray = t3lib_BEfunc::getFlexFormDS($conf, $row, $table,'',$this->WSOL);
425 $currentValueArray = t3lib_div::xml2array($value);
426
427 // Traversing the XML structure, processing files:
428 if (is_array($currentValueArray)) {
429 $this->temp_flexRelations = array(
430 'db' => array(),
431 'file' => array(),
432 'softrefs' => array()
433 );
434
435 // Create and call iterator object:
436 $flexObj = t3lib_div::makeInstance('t3lib_flexformtools');
437 $flexObj->traverseFlexFormXMLData($table,$field,$row,$this,'getRelations_flexFormCallBack');
438
439 // Create an entry for the field:
440 $outRow[$field] = array(
441 'type' => 'flex',
442 'flexFormRels' => $this->temp_flexRelations,
443 );
444 }
445 }
446
447 // Soft References:
448 if (strlen($value) && $softRefs = t3lib_BEfunc::explodeSoftRefParserList($conf['softref'])) {
449 $softRefValue = $value;
450 foreach($softRefs as $spKey => $spParams) {
451 $softRefObj = &t3lib_BEfunc::softRefParserObj($spKey);
452 if (is_object($softRefObj)) {
453 $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);
454 if (is_array($resultArray)) {
455 $outRow[$field]['softrefs']['keys'][$spKey] = $resultArray['elements'];
456 if (strlen($resultArray['content'])) {
457 $softRefValue = $resultArray['content'];
458 }
459 }
460 }
461 }
462
463 if (is_array($outRow[$field]['softrefs']) && count($outRow[$field]['softrefs']) && strcmp($value,$softRefValue) && strstr($softRefValue,'{softref:')) {
464 $outRow[$field]['softrefs']['tokenizedContent'] = $softRefValue;
465 }
466 }
467 }
468 }
469
470 return $outRow;
471 }
472
473 /**
474 * Callback function for traversing the FlexForm structure in relation to finding file and DB references!
475 *
476 * @param array Data structure for the current value
477 * @param mixed Current value
478 * @param array Additional configuration used in calling function
479 * @param string Path of value in DS structure
480 * @param object Object reference to caller
481 * @return void
482 * @see t3lib_TCEmain::checkValue_flex_procInData_travDS()
483 */
484 function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, &$pObj) {
485 $structurePath = substr($structurePath,5).'/'; // removing "data/" in the beginning of path (which points to location in data array)
486
487 $dsConf = $dsArr['TCEforms']['config'];
488
489 // Implode parameter values:
490 list($table, $uid, $field) = array($PA['table'],$PA['uid'],$PA['field']);
491
492 // Add files
493 if ($result = $this->getRelations_procFiles($dataValue, $dsConf, $uid)) {
494
495 // Creates an entry for the field with all the files:
496 $this->temp_flexRelations['file'][$structurePath] = $result;
497 }
498
499 // Add DB:
500 if ($result = $this->getRelations_procDB($dataValue, $dsConf, $uid)) {
501
502 // Create an entry for the field with all DB relations:
503 $this->temp_flexRelations['db'][$structurePath] = $result;
504 }
505
506 // Soft References:
507 if (strlen($dataValue) && $softRefs = t3lib_BEfunc::explodeSoftRefParserList($dsConf['softref'])) {
508 $softRefValue = $dataValue;
509 foreach($softRefs as $spKey => $spParams) {
510 $softRefObj = &t3lib_BEfunc::softRefParserObj($spKey);
511 if (is_object($softRefObj)) {
512 $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams, $structurePath);
513 if (is_array($resultArray) && is_array($resultArray['elements'])) {
514 $this->temp_flexRelations['softrefs'][$structurePath]['keys'][$spKey] = $resultArray['elements'];
515 if (strlen($resultArray['content'])) $softRefValue = $resultArray['content'];
516 }
517 }
518 }
519
520 if (count($this->temp_flexRelations['softrefs']) && strcmp($dataValue,$softRefValue)) {
521 $this->temp_flexRelations['softrefs'][$structurePath]['tokenizedContent'] = $softRefValue;
522 }
523 }
524 }
525
526 /**
527 * Check field configuration if it is a file relation field and extract file relations if any
528 *
529 * @param string Field value
530 * @param array Field configuration array of type "TCA/columns"
531 * @param integer Field uid
532 * @return array If field type is OK it will return an array with the files inside. Else false
533 */
534 function getRelations_procFiles($value, $conf, $uid) {
535 // Take care of files...
536 if ($conf['type']=='group' && $conf['internal_type']=='file') {
537
538 // Collect file values in array:
539 if ($conf['MM']) {
540 $theFileValues = array();
541 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
542 $dbAnalysis->start('', 'files', $conf['MM'], $uid);
543
544 foreach($dbAnalysis->itemArray as $somekey => $someval) {
545 if ($someval['id']) {
546 $theFileValues[] = $someval['id'];
547 }
548 }
549 } else {
550 $theFileValues = explode(',',$value);
551 }
552
553 // Traverse the files and add them:
554 $uploadFolder = $conf['uploadfolder'];
555 $dest = $this->destPathFromUploadFolder($uploadFolder);
556 $newValue = array();
557 $newValueFiles = array();
558
559 foreach($theFileValues as $file) {
560 if (trim($file)) {
561 $realFile = $dest.'/'.trim($file);
562 # if (@is_file($realFile)) { // Now, the refernece index should NOT look if files exist - just faithfully include them if they are in the records!
563 $newValueFiles[] = array(
564 'filename' => $file,
565 'ID' => md5($realFile),
566 'ID_absFile' => $realFile
567 ); // the order should be preserved here because.. (?)
568 # } else $this->error('Missing file: '.$realFile);
569 }
570 }
571
572 return $newValueFiles;
573 }
574 }
575
576 /**
577 * Check field configuration if it is a DB relation field and extract DB relations if any
578 *
579 * @param string Field value
580 * @param array Field configuration array of type "TCA/columns"
581 * @param integer Field uid
582 * @param string Table name
583 * @return array If field type is OK it will return an array with the database relations. Else false
584 */
585 function getRelations_procDB($value, $conf, $uid, $table = '') {
586
587 // DB record lists:
588 if ($this->isReferenceField($conf)) {
589 $allowedTables = $conf['type']=='group' ? $conf['allowed'] : $conf['foreign_table'].','.$conf['neg_foreign_table'];
590 $prependName = $conf['type']=='group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
591
592 if($conf['MM_opposite_field']) {
593 return array();
594 }
595
596 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
597 $dbAnalysis->start($value,$allowedTables,$conf['MM'],$uid,$table,$conf);
598
599 return $dbAnalysis->itemArray;
600 }
601 }
602
603
604
605
606
607
608
609
610
611
612
613
614 /*******************************
615 *
616 * Setting values
617 *
618 *******************************/
619
620 /**
621 * Setting the value of a reference or removing it completely.
622 * Usage: For lowlevel clean up operations!
623 * WARNING: With this you can set values that are not allowed in the database since it will bypass all checks for validity! Hence it is targetted at clean-up operations. Please use TCEmain in the usual ways if you wish to manipulate references.
624 * Since this interface allows updates to soft reference values (which TCEmain does not directly) you may like to use it for that as an exception to the warning above.
625 * Notice; If you want to remove multiple references from the same field, you MUST start with the one having the highest sorting number. If you don't the removal of a reference with a lower number will recreate an index in which the remaining references in that field has new hash-keys due to new sorting numbers - and you will get errors for the remaining operations which cannot find the hash you feed it!
626 * To ensure proper working only admin-BE_USERS in live workspace should use this function
627 *
628 * @param string 32-byte hash string identifying the record from sys_refindex which you wish to change the value for
629 * @param mixed Value you wish to set for reference. If NULL, the reference is removed (unless a soft-reference in which case it can only be set to a blank string). If you wish to set a database reference, use the format "[table]:[uid]". Any other case, the input value is set as-is
630 * @param boolean Return $dataArray only, do not submit it to database.
631 * @param boolean If set, it will bypass check for workspace-zero and admin user
632 * @return string If a return string, that carries an error message, otherwise false (=OK) (except if $returnDataArray is set!)
633 */
634 function setReferenceValue($hash,$newValue,$returnDataArray=FALSE,$bypassWorkspaceAdminCheck=FALSE) {
635
636 if (($GLOBALS['BE_USER']->workspace===0 && $GLOBALS['BE_USER']->isAdmin()) || $bypassWorkspaceAdminCheck) {
637
638 // Get current index from Database:
639 list($refRec) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
640 '*',
641 'sys_refindex',
642 'hash='.$GLOBALS['TYPO3_DB']->fullQuoteStr($hash,'sys_refindex')
643 );
644
645 // Check if reference existed.
646 if (is_array($refRec)) {
647 if ($GLOBALS['TCA'][$refRec['tablename']]) {
648
649 // Get that record from database:
650 list($record) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*',$refRec['tablename'],'uid='.intval($refRec['recuid']));
651
652 if (is_array($record)) {
653
654 // Get all relations from record, filter with fieldname:
655 $dbrels = $this->getRelations($refRec['tablename'],$record,$refRec['field']);
656 if ($dat = $dbrels[$refRec['field']]) {
657
658 // Initialize data array that is to be sent to TCEmain afterwards:
659 $dataArray = array();
660
661 // Based on type,
662 switch((string)$dat['type']) {
663 case 'db':
664 $error = $this->setReferenceValue_dbRels($refRec,$dat['itemArray'],$newValue,$dataArray);
665 if ($error) return $error;
666 break;
667 case 'file':
668 $this->setReferenceValue_fileRels($refRec,$dat['newValueFiles'],$newValue,$dataArray);
669 if ($error) return $error;
670 break;
671 case 'flex':
672 // DB references:
673 if (is_array($dat['flexFormRels']['db'][$refRec['flexpointer']])) {
674 $error = $this->setReferenceValue_dbRels($refRec,$dat['flexFormRels']['db'][$refRec['flexpointer']],$newValue,$dataArray,$refRec['flexpointer']);
675 if ($error) return $error;
676 }
677 // File references
678 if (is_array($dat['flexFormRels']['file'][$refRec['flexpointer']])) {
679 $this->setReferenceValue_fileRels($refRec,$dat['flexFormRels']['file'][$refRec['flexpointer']],$newValue,$dataArray,$refRec['flexpointer']);
680 if ($error) return $error;
681 }
682 // Soft references in flexforms
683 if ($refRec['softref_key'] && is_array($dat['flexFormRels']['softrefs'][$refRec['flexpointer']]['keys'][$refRec['softref_key']])) {
684 $error = $this->setReferenceValue_softreferences($refRec,$dat['flexFormRels']['softrefs'][$refRec['flexpointer']],$newValue,$dataArray,$refRec['flexpointer']);
685 if ($error) return $error;
686 }
687 break;
688 }
689
690 // Softreferences in the field:
691 if ($refRec['softref_key'] && is_array($dat['softrefs']['keys'][$refRec['softref_key']])) {
692 $error = $this->setReferenceValue_softreferences($refRec,$dat['softrefs'],$newValue,$dataArray);
693 if ($error) return $error;
694
695 }
696
697 // Data Array, now ready to sent to TCEmain
698 if ($returnDataArray) {
699 return $dataArray;
700 } else {
701
702 // Execute CMD array:
703 $tce = t3lib_div::makeInstance('t3lib_TCEmain');
704 $tce->stripslashes_values = FALSE;
705 $tce->dontProcessTransformations = TRUE;
706 $tce->bypassWorkspaceRestrictions = TRUE;
707 $tce->bypassFileHandling = TRUE;
708 $tce->bypassAccessCheckForRecords = TRUE; // Otherwise this cannot update things in deleted records...
709
710 $tce->start($dataArray,array()); // check has been done previously that there is a backend user which is Admin and also in live workspace
711 $tce->process_datamap();
712
713 // Return errors if any:
714 if (count($tce->errorLog)) {
715 return chr(10).'TCEmain:'.implode(chr(10).'TCEmain:',$tce->errorLog);
716 }
717 }
718 }
719 }
720 } else return 'ERROR: Tablename "'.$refRec['tablename'].'" was not in TCA!';
721 } else return 'ERROR: No reference record with hash="'.$hash.'" was found!';
722 } else return 'ERROR: BE_USER object is not admin OR not in workspace 0 (Live)';
723 }
724
725 /**
726 * Setting a value for a reference for a DB field:
727 *
728 * @param array sys_refindex record
729 * @param array Array of references from that field
730 * @param string Value to substitute current value with (or NULL to unset it)
731 * @param array data array in which the new value is set (passed by reference)
732 * @param string Flexform pointer, if in a flex form field.
733 * @return string Error message if any, otherwise false = OK
734 */
735 function setReferenceValue_dbRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='') {
736 if (!strcmp($itemArray[$refRec['sorting']]['id'],$refRec['ref_uid']) && !strcmp($itemArray[$refRec['sorting']]['table'],$refRec['ref_table'])) {
737
738 // Setting or removing value:
739 if ($newValue===NULL) { // Remove value:
740 unset($itemArray[$refRec['sorting']]);
741 } else {
742 list($itemArray[$refRec['sorting']]['table'],$itemArray[$refRec['sorting']]['id']) = explode(':',$newValue);
743 }
744
745 // Traverse and compile new list of records:
746 $saveValue = array();
747 foreach($itemArray as $pair) {
748 $saveValue[] = $pair['table'].'_'.$pair['id'];
749 }
750
751 // Set in data array:
752 if ($flexpointer) {
753 $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
754 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
755 $flexToolObj->setArrayValueByPath(substr($flexpointer,0,-1),$dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'],implode(',',$saveValue));
756 } else {
757 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',',$saveValue);
758 }
759
760 } else return 'ERROR: table:id pair "'.$refRec['ref_table'].':'.$refRec['ref_uid'].'" did not match that of the record ("'.$itemArray[$refRec['sorting']]['table'].':'.$itemArray[$refRec['sorting']]['id'].'") in sorting index "'.$refRec['sorting'].'"';
761 }
762
763 /**
764 * Setting a value for a reference for a FILE field:
765 *
766 * @param array sys_refindex record
767 * @param array Array of references from that field
768 * @param string Value to substitute current value with (or NULL to unset it)
769 * @param array data array in which the new value is set (passed by reference)
770 * @param string Flexform pointer, if in a flex form field.
771 * @return string Error message if any, otherwise false = OK
772 */
773 function setReferenceValue_fileRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='') {
774 if (!strcmp(substr($itemArray[$refRec['sorting']]['ID_absFile'],strlen(PATH_site)),$refRec['ref_string']) && !strcmp('_FILE',$refRec['ref_table'])) {
775
776 // Setting or removing value:
777 if ($newValue===NULL) { // Remove value:
778 unset($itemArray[$refRec['sorting']]);
779 } else {
780 $itemArray[$refRec['sorting']]['filename'] = $newValue;
781 }
782
783 // Traverse and compile new list of records:
784 $saveValue = array();
785 foreach($itemArray as $fileInfo) {
786 $saveValue[] = $fileInfo['filename'];
787 }
788
789 // Set in data array:
790 if ($flexpointer) {
791 $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
792 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
793 $flexToolObj->setArrayValueByPath(substr($flexpointer,0,-1),$dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'],implode(',',$saveValue));
794 } else {
795 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',',$saveValue);
796 }
797
798 } else return 'ERROR: either "'.$refRec['ref_table'].'" was not "_FILE" or file PATH_site+"'.$refRec['ref_string'].'" did not match that of the record ("'.$itemArray[$refRec['sorting']]['ID_absFile'].'") in sorting index "'.$refRec['sorting'].'"';
799 }
800
801 /**
802 * Setting a value for a soft reference token
803 *
804 * @param array sys_refindex record
805 * @param array Array of soft reference occurencies
806 * @param string Value to substitute current value with
807 * @param array data array in which the new value is set (passed by reference)
808 * @param string Flexform pointer, if in a flex form field.
809 * @return string Error message if any, otherwise false = OK
810 */
811 function setReferenceValue_softreferences($refRec,$softref,$newValue,&$dataArray,$flexpointer='') {
812 if (is_array($softref['keys'][$refRec['softref_key']][$refRec['softref_id']])) {
813
814 // Set new value:
815 $softref['keys'][$refRec['softref_key']][$refRec['softref_id']]['subst']['tokenValue'] = ''.$newValue;
816
817 // Traverse softreferences and replace in tokenized content to rebuild it with new value inside:
818 foreach($softref['keys'] as $sfIndexes) {
819 foreach($sfIndexes as $data) {
820 $softref['tokenizedContent'] = str_replace('{softref:'.$data['subst']['tokenID'].'}', $data['subst']['tokenValue'], $softref['tokenizedContent']);
821 }
822 }
823
824 // Set in data array:
825 if (!strstr($softref['tokenizedContent'],'{softref:')) {
826 if ($flexpointer) {
827 $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
828 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
829 $flexToolObj->setArrayValueByPath(substr($flexpointer,0,-1),$dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'],$softref['tokenizedContent']);
830 } else {
831 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
832 }
833 } else return 'ERROR: After substituting all found soft references there were still soft reference tokens in the text. (theoretically this does not have to be an error if the string "{softref:" happens to be in the field for another reason.)';
834 } else return 'ERROR: Soft reference parser key "'.$refRec['softref_key'].'" or the index "'.$refRec['softref_id'].'" was not found.';
835 }
836
837
838
839
840
841
842
843
844
845
846 /*******************************
847 *
848 * Indexing words
849 *
850 *******************************/
851
852 /**
853 * Indexing words from table records. Can be useful for quick backend look ups in records across the system.
854 */
855 function wordIndexing($table,$uid) {
856 return; // Disabled until Kasper finishes this feature. If someone else needs it in the meantime you are welcome to complete it. Below my todo list.
857
858 // TODO:
859 // - Flag to disable indexing
860 // - Clean-up to remove words not used anymore and indexes for records not in the system anymore.
861 // - UTF-8 compliant substr()
862 $lexer = t3lib_div::makeInstance('tx_indexedsearch_lexer');
863 $words = $lexer->split2Words(implode(' ',$this->words_strings));
864 foreach($words as $w) {
865 $words[]=substr($w,0,3);
866 }
867 $words = array_unique($words);
868 $this->updateWordIndex($words,$table,$uid);
869 }
870
871 /**
872 * Update/Create word index for record
873 *
874 * @param array Word list array (words are values in array)
875 * @param string Table
876 * @param integer Rec uid
877 * @return void
878 */
879 function updateWordIndex($words,$table,$uid) {
880
881 // Submit words to
882 $this->submitWords($words);
883
884 // Result id and remove relations:
885 $rid = t3lib_div::md5int($table.':'.$uid);
886 $GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_refindex_rel', 'rid='.intval($rid));
887
888 // Add relations:
889 foreach($words as $w) {
890 $insertFields = array(
891 'rid' => $rid,
892 'wid' => t3lib_div::md5int($w)
893 );
894
895 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_refindex_rel', $insertFields);
896 }
897
898 // Add result record:
899 $GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_refindex_res', 'rid='.intval($rid));
900 $insertFields = array(
901 'rid' => $rid,
902 'tablename' => $table,
903 'recuid' => $uid
904 );
905 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_refindex_res', $insertFields);
906 }
907
908 /**
909 * Adds new words to db
910 *
911 * @param array Word List array (where each word has information about position etc).
912 * @return void
913 */
914 function submitWords($wl) {
915
916 $hashArr = array();
917 foreach($wl as $w) {
918 $hashArr[] = t3lib_div::md5int($w);
919 }
920 $wl = array_flip($wl);
921
922 if (count($hashArr)) {
923 $cwl = implode(',',$hashArr);
924 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('baseword', 'sys_refindex_words', 'wid IN ('.$cwl.')');
925
926 if($GLOBALS['TYPO3_DB']->sql_num_rows($res)!=count($wl)) {
927 while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
928 unset($wl[$row['baseword']]);
929 }
930
931 reset($wl);
932 while(list($key,$val)=each($wl)) {
933 $insertFields = array(
934 'wid' => t3lib_div::md5int($key),
935 'baseword' => $key
936 );
937
938 // A duplicate-key error will occur here if a word is NOT unset in the unset() line. However as long as the words in $wl are NOT longer as 60 chars (the baseword varchar is 60 characters...) this is not a problem.
939 $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_refindex_words', $insertFields);
940 }
941 }
942 }
943 }
944
945
946
947
948
949
950
951 /*******************************
952 *
953 * Helper functions
954 *
955 *******************************/
956
957 /**
958 * Returns true if the TCA/columns field type is a DB reference field
959 *
960 * @param array config array for TCA/columns field
961 * @return boolean True if DB reference field (group/db or select with foreign-table)
962 */
963 function isReferenceField($conf) {
964 return ($conf['type']=='group' && $conf['internal_type']=='db') || (($conf['type']=='select' || $conf['type']=='inline') && $conf['foreign_table']);
965 }
966
967 /**
968 * Returns destination path to an upload folder given by $folder
969 *
970 * @param string Folder relative to PATH_site
971 * @return string Input folder prefixed with PATH_site. No checking for existence is done. Output must be a folder without trailing slash.
972 */
973 function destPathFromUploadFolder($folder) {
974 return PATH_site.$folder;
975 }
976
977 /**
978 * Sets error message in the internal error log
979 *
980 * @param string Error message
981 * @return void
982 */
983 function error($msg) {
984 $this->errorLog[]=$msg;
985 }
986
987 /**
988 * Updating Index (External API)
989 *
990 * @param boolean If set, only a test
991 * @param boolean If set, output CLI status
992 * @return array Header and body status content
993 */
994 function updateIndex($testOnly,$cli_echo=FALSE) {
995 global $TCA, $TYPO3_DB;
996
997 $errors = array();
998 $tableNames = array();
999 $recCount=0;
1000 $tableCount=0;
1001
1002 $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, use "-e" to update)' : 'Reference Index being Updated';
1003 if ($cli_echo) echo
1004 '*******************************************'.chr(10).
1005 $headerContent.chr(10).
1006 '*******************************************'.chr(10);
1007
1008 // Traverse all tables:
1009 foreach ($TCA as $tableName => $cfg) {
1010 $tableNames[] = $tableName;
1011 $tableCount++;
1012
1013 // Traverse all records in tables, including deleted records:
1014 $allRecs = $TYPO3_DB->exec_SELECTgetRows('uid',$tableName,'1=1'); //.t3lib_BEfunc::deleteClause($tableName)
1015 $uidList = array(0);
1016 foreach ($allRecs as $recdat) {
1017 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
1018 $result = $refIndexObj->updateRefIndexTable($tableName,$recdat['uid'],$testOnly);
1019 $uidList[]= $recdat['uid'];
1020 $recCount++;
1021
1022 if ($result['addedNodes'] || $result['deletedNodes']) {
1023 $Err = 'Record '.$tableName.':'.$recdat['uid'].' had '.$result['addedNodes'].' added indexes and '.$result['deletedNodes'].' deleted indexes';
1024 $errors[]= $Err;
1025 if ($cli_echo) echo $Err.chr(10);
1026 #$errors[] = t3lib_div::view_array($result);
1027 }
1028 }
1029
1030 // Searching lost indexes for this table:
1031 $where = 'tablename='.$TYPO3_DB->fullQuoteStr($tableName,'sys_refindex').' AND recuid NOT IN ('.implode(',',$uidList).')';
1032 $lostIndexes = $TYPO3_DB->exec_SELECTgetRows('hash','sys_refindex',$where);
1033 if (count($lostIndexes)) {
1034 $Err = 'Table '.$tableName.' has '.count($lostIndexes).' lost indexes which are now deleted';
1035 $errors[]= $Err;
1036 if ($cli_echo) echo $Err.chr(10);
1037 if (!$testOnly) $TYPO3_DB->exec_DELETEquery('sys_refindex',$where);
1038 }
1039 }
1040
1041 // Searching lost indexes for non-existing tables:
1042 $where = 'tablename NOT IN ('.implode(',',$TYPO3_DB->fullQuoteArray($tableNames,'sys_refindex')).')';
1043 $lostTables = $TYPO3_DB->exec_SELECTgetRows('hash','sys_refindex',$where);
1044 if (count($lostTables)) {
1045 $Err = 'Index table hosted '.count($lostTables).' indexes for non-existing tables, now removed';
1046 $errors[]= $Err;
1047 if ($cli_echo) echo $Err.chr(10);
1048 if (!$testOnly) $TYPO3_DB->exec_DELETEquery('sys_refindex',$where);
1049 }
1050
1051 $testedHowMuch = $recCount.' records from '.$tableCount.' tables were checked/updated.'.chr(10);
1052
1053 $bodyContent = $testedHowMuch.(count($errors)?implode(chr(10),$errors):'Index Integrity was perfect!');
1054 if ($cli_echo) echo $testedHowMuch.(count($errors)?'Updates: '.count($errors):'Index Integrity was perfect!').chr(10);
1055
1056 return array($headerContent,$bodyContent,count($errors));
1057 }
1058 }
1059
1060
1061 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_refindex.php']) {
1062 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_refindex.php']);
1063 }
1064
1065 ?>