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