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