TYPO3 API  SVNRelease
class.t3lib_refindex.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * [CLASS/FUNCTION INDEX of SCRIPT]
00029  *
00030  *
00031  *
00032  *   89: class t3lib_refindex
00033  *  107:     function updateRefIndexTable($table,$uid,$testOnly=FALSE)
00034  *  178:     function generateRefIndexData($table,$uid)
00035  *  255:     function createEntryData($table,$uid,$field,$flexpointer,$deleted,$ref_table,$ref_uid,$ref_string='',$sort=-1,$softref_key='',$softref_id='')
00036  *  282:     function createEntryData_dbRels($table,$uid,$fieldname,$flexpointer,$deleted,$items)
00037  *  299:     function createEntryData_fileRels($table,$uid,$fieldname,$flexpointer,$deleted,$items)
00038  *  320:     function createEntryData_softreferences($table,$uid,$fieldname,$flexpointer,$deleted,$keys)
00039  *
00040  *            SECTION: Get relations from table row
00041  *  376:     function getRelations($table,$row,$onlyField='')
00042  *  473:     function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, &$pObj)
00043  *  523:     function getRelations_procFiles($value, $conf, $uid)
00044  *  573:     function getRelations_procDB($value, $conf, $uid)
00045  *
00046  *            SECTION: Setting values
00047  *  616:     function setReferenceValue($hash,$newValue,$returnDataArray=FALSE)
00048  *  699:     function setReferenceValue_dbRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='')
00049  *  737:     function setReferenceValue_fileRels($refRec,$itemArray,$newValue,&$dataArray,$flexpointer='')
00050  *  775:     function setReferenceValue_softreferences($refRec,$softref,$newValue,&$dataArray,$flexpointer='')
00051  *
00052  *            SECTION: Helper functions
00053  *  822:     function isReferenceField($conf)
00054  *  832:     function destPathFromUploadFolder($folder)
00055  *  842:     function error($msg)
00056  *  853:     function updateIndex($testOnly,$cli_echo=FALSE)
00057  *
00058  * TOTAL FUNCTIONS: 18
00059  * (This index is automatically created/updated by the extension "extdeveval")
00060  *
00061  */
00062 
00063 
00064 /**
00065  * Reference index processing and relation extraction
00066  *
00067  * NOTICE: When the reference index is updated for an offline version the results may not be correct.
00068  * First, lets assumed that the reference update happens in LIVE workspace (ALWAYS update from Live workspace if you analyse whole database!)
00069  * 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.
00070  * 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!
00071  * 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?
00072  * Anyway, I just wanted to document this finding - I don't think we can find a solution for it. And its very TemplaVoila specific.
00073  *
00074  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00075  * @package TYPO3
00076  * @subpackage t3lib
00077  */
00078 class t3lib_refindex {
00079 
00080     var $temp_flexRelations = array();
00081     var $errorLog = array();
00082     var $WSOL = FALSE;
00083     var $relations = array();
00084 
00085     var $words_strings = array();
00086     var $words = array();
00087 
00088     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.
00089 
00090 
00091     /**
00092      * Call this function to update the sys_refindex table for a record (even one just deleted)
00093      * 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.
00094      *
00095      * @param   string      Table name
00096      * @param   integer     UID of record
00097      * @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.
00098      * @return  array       Array with statistics about how many index records were added, deleted and not altered plus the complete reference set for the record.
00099      */
00100     function updateRefIndexTable($table, $uid, $testOnly = FALSE) {
00101 
00102             // First, secure that the index table is not updated with workspace tainted relations:
00103         $this->WSOL = FALSE;
00104 
00105             // Init:
00106         $result = array(
00107             'keptNodes' => 0,
00108             'deletedNodes' => 0,
00109             'addedNodes' => 0
00110         );
00111 
00112             // Get current index from Database:
00113         $currentRels = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
00114             '*',
00115             'sys_refindex',
00116             'tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_refindex') .
00117             ' AND recuid=' . intval($uid),
00118             '', '', '', 'hash'
00119         );
00120 
00121             // First, test to see if the record exists (including deleted-flagged)
00122         if (t3lib_BEfunc::getRecordRaw($table, 'uid=' . intval($uid), 'uid')) {
00123 
00124                 // Then, get relations:
00125             $relations = $this->generateRefIndexData($table, $uid);
00126 
00127             if (is_array($relations)) {
00128 
00129                     // Traverse the generated index:
00130                 foreach ($relations as $k => $datRec) {
00131                     $relations[$k]['hash'] = md5(implode('///', $relations[$k]) . '///' . $this->hashVersion);
00132 
00133                         // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
00134                     if (isset($currentRels[$relations[$k]['hash']])) {
00135                         unset($currentRels[$relations[$k]['hash']]);
00136                         $result['keptNodes']++;
00137                         $relations[$k]['_ACTION'] = 'KEPT';
00138                     } else {
00139                             // If new, add it:
00140                         if (!$testOnly) {
00141                             $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_refindex', $relations[$k]);
00142                         }
00143                         $result['addedNodes']++;
00144                         $relations[$k]['_ACTION'] = 'ADDED';
00145                     }
00146                 }
00147 
00148                 $result['relations'] = $relations;
00149             } else {
00150                 return FALSE;
00151             } // Weird mistake I would say...
00152         }
00153 
00154             // If any old are left, remove them:
00155         if (count($currentRels)) {
00156             $hashList = array_keys($currentRels);
00157             if (count($hashList)) {
00158                 $result['deletedNodes'] = count($hashList);
00159                 $result['deletedNodes_hashList'] = implode(',', $hashList);
00160                 if (!$testOnly) {
00161                     $GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_refindex', 'hash IN (' . implode(',', $GLOBALS['TYPO3_DB']->fullQuoteArray($hashList, 'sys_refindex')) . ')');
00162                 }
00163             }
00164         }
00165 
00166         return $result;
00167     }
00168 
00169     /**
00170      * Returns array of arrays with an index of all references found in record from table/uid
00171      * If the result is used to update the sys_refindex table then ->WSOL must NOT be true (no workspace overlay anywhere!)
00172      *
00173      * @param   string      Table name from $TCA
00174      * @param   integer     Record UID
00175      * @return  array       Index Rows
00176      */
00177     function generateRefIndexData($table, $uid) {
00178         global $TCA;
00179 
00180         if (isset($TCA[$table])) {
00181                 // Get raw record from DB:
00182             $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', $table, 'uid=' . intval($uid));
00183 
00184             if (is_array($record)) {
00185 
00186                     // Initialize:
00187                 $this->words_strings = array();
00188                 $this->words = array();
00189 
00190                     // Deleted:
00191                 $deleted = $TCA[$table]['ctrl']['delete'] ? ($record[$TCA[$table]['ctrl']['delete']] ? 1 : 0) : 0;
00192 
00193                     // Get all relations from record:
00194                 $dbrels = $this->getRelations($table, $record);
00195 
00196                     // Traverse those relations, compile records to insert in table:
00197                 $this->relations = array();
00198                 foreach ($dbrels as $fieldname => $dat) {
00199 
00200                         // Based on type,
00201                     switch ((string) $dat['type']) {
00202                         case 'db':
00203                             $this->createEntryData_dbRels($table, $uid, $fieldname, '', $deleted, $dat['itemArray']);
00204                         break;
00205                         case 'file_reference':
00206                         case 'file':
00207                             $this->createEntryData_fileRels($table, $uid, $fieldname, '', $deleted, $dat['newValueFiles']);
00208                         break;
00209                         case 'flex':
00210                                 // DB references:
00211                             if (is_array($dat['flexFormRels']['db'])) {
00212                                 foreach ($dat['flexFormRels']['db'] as $flexpointer => $subList) {
00213                                     $this->createEntryData_dbRels($table, $uid, $fieldname, $flexpointer, $deleted, $subList);
00214                                 }
00215                             }
00216                                 // File references (NOT TESTED!)
00217                             if (is_array($dat['flexFormRels']['file'])) { // Not tested
00218                                 foreach ($dat['flexFormRels']['file'] as $flexpointer => $subList) {
00219                                     $this->createEntryData_fileRels($table, $uid, $fieldname, $flexpointer, $deleted, $subList);
00220                                 }
00221                             }
00222                                 // Soft references in flexforms (NOT TESTED!)
00223                             if (is_array($dat['flexFormRels']['softrefs'])) {
00224                                 foreach ($dat['flexFormRels']['softrefs'] as $flexpointer => $subList) {
00225                                     $this->createEntryData_softreferences($table, $uid, $fieldname, $flexpointer, $deleted, $subList['keys']);
00226                                 }
00227                             }
00228                         break;
00229                     }
00230 
00231                         // Softreferences in the field:
00232                     if (is_array($dat['softrefs'])) {
00233                         $this->createEntryData_softreferences($table, $uid, $fieldname, '', $deleted, $dat['softrefs']['keys']);
00234                     }
00235                 }
00236 
00237                     // Word indexing:
00238                 t3lib_div::loadTCA($table);
00239                 foreach ($TCA[$table]['columns'] as $field => $conf) {
00240                     if (t3lib_div::inList('input,text', $conf['config']['type']) && strcmp($record[$field], '') && !t3lib_div::testInt($record[$field])) {
00241                         $this->words_strings[$field] = $record[$field];
00242                     }
00243                 }
00244 
00245                 return $this->relations;
00246             }
00247         }
00248     }
00249 
00250     /**
00251      * Create array with field/value pairs ready to insert in database.
00252      * The "hash" field is a fingerprint value across this table.
00253      *
00254      * @param   string      Tablename of source record (where reference is located)
00255      * @param   integer     UID of source record (where reference is located)
00256      * @param   string      Fieldname of source record (where reference is located)
00257      * @param   string      Pointer to location inside flexform structure where reference is located in [field]
00258      * @param   integer     Whether record is deleted-flagged or not
00259      * @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.
00260      * @param   integer     For database references; The UID of the record (zero "ref_table" is "_FILE" or "_STRING")
00261      * @param   string      For "_FILE" or "_STRING" references: The filepath (relative to PATH_site or absolute) or other string.
00262      * @param   integer     The sorting order of references if many (the "group" or "select" TCA types). -1 if no sorting order is specified.
00263      * @param   string      If the reference is a soft reference, this is the soft reference parser key. Otherwise empty.
00264      * @param   string      Soft reference ID for key. Might be useful for replace operations.
00265      * @return  array       Array record to insert into table.
00266      */
00267     function createEntryData($table, $uid, $field, $flexpointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '') {
00268         return array(
00269             'tablename' => $table,
00270             'recuid' => $uid,
00271             'field' => $field,
00272             'flexpointer' => $flexpointer,
00273             'softref_key' => $softref_key,
00274             'softref_id' => $softref_id,
00275             'sorting' => $sort,
00276             'deleted' => $deleted,
00277             'ref_table' => $ref_table,
00278             'ref_uid' => $ref_uid,
00279             'ref_string' => $ref_string,
00280         );
00281     }
00282 
00283     /**
00284      * Enter database references to ->relations array
00285      *
00286      * @param   string      [See createEntryData, arg 1]
00287      * @param   integer     [See createEntryData, arg 2]
00288      * @param   string      [See createEntryData, arg 3]
00289      * @param   string      [See createEntryData, arg 4]
00290      * @param   string      [See createEntryData, arg 5]
00291      * @param   array       Data array with databaes relations (table/id)
00292      * @return  void
00293      */
00294     function createEntryData_dbRels($table, $uid, $fieldname, $flexpointer, $deleted, $items) {
00295         foreach ($items as $sort => $i) {
00296             $this->relations[] = $this->createEntryData($table, $uid, $fieldname, $flexpointer, $deleted, $i['table'], $i['id'], '', $sort);
00297         }
00298     }
00299 
00300     /**
00301      * Enter file references to ->relations array
00302      *
00303      * @param   string      [See createEntryData, arg 1]
00304      * @param   integer     [See createEntryData, arg 2]
00305      * @param   string      [See createEntryData, arg 3]
00306      * @param   string      [See createEntryData, arg 4]
00307      * @param   string      [See createEntryData, arg 5]
00308      * @param   array       Data array with file relations
00309      * @return  void
00310      */
00311     function createEntryData_fileRels($table, $uid, $fieldname, $flexpointer, $deleted, $items) {
00312         foreach ($items as $sort => $i) {
00313             $filePath = $i['ID_absFile'];
00314             if (t3lib_div::isFirstPartOfStr($filePath, PATH_site)) {
00315                 $filePath = substr($filePath, strlen(PATH_site));
00316             }
00317             $this->relations[] = $this->createEntryData($table, $uid, $fieldname, $flexpointer, $deleted, '_FILE', 0, $filePath, $sort);
00318         }
00319     }
00320 
00321     /**
00322      * Enter softref references to ->relations array
00323      *
00324      * @param   string      [See createEntryData, arg 1]
00325      * @param   integer     [See createEntryData, arg 2]
00326      * @param   string      [See createEntryData, arg 3]
00327      * @param   string      [See createEntryData, arg 4]
00328      * @param   string      [See createEntryData, arg 5]
00329      * @param   array       Data array with soft reference keys
00330      * @return  void
00331      */
00332     function createEntryData_softreferences($table, $uid, $fieldname, $flexpointer, $deleted, $keys) {
00333         if (is_array($keys)) {
00334             foreach ($keys as $spKey => $elements) {
00335                 if (is_array($elements)) {
00336                     foreach ($elements as $subKey => $el) {
00337                         if (is_array($el['subst'])) {
00338                             switch ((string) $el['subst']['type']) {
00339                                 case 'db':
00340                                     list($tableName, $recordId) = explode(':', $el['subst']['recordRef']);
00341                                     $this->relations[] = $this->createEntryData($table, $uid, $fieldname, $flexpointer, $deleted, $tableName, $recordId, '', -1, $spKey, $subKey);
00342                                 break;
00343                                 case 'file_reference':
00344                                 case 'file':
00345                                     $this->relations[] = $this->createEntryData($table, $uid, $fieldname, $flexpointer, $deleted, '_FILE', 0, $el['subst']['relFileName'], -1, $spKey, $subKey);
00346                                 break;
00347                                 case 'string':
00348                                     $this->relations[] = $this->createEntryData($table, $uid, $fieldname, $flexpointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
00349                                 break;
00350                             }
00351                         }
00352                     }
00353                 }
00354             }
00355         }
00356     }
00357 
00358 
00359     /*******************************
00360      *
00361      * Get relations from table row
00362      *
00363      *******************************/
00364 
00365     /**
00366      * Returns relation information for a $table/$row-array
00367      * Traverses all fields in input row which are configured in TCA/columns
00368      * It looks for hard relations to files and records in the TCA types "select" and "group"
00369      *
00370      * @param   string      Table name
00371      * @param   array       Row from table
00372      * @param   string      Specific field to fetch for.
00373      * @return  array       Array with information about relations
00374      * @see export_addRecord()
00375      */
00376     function getRelations($table, $row, $onlyField = '') {
00377         global $TCA;
00378 
00379             // Load full table description
00380         t3lib_div::loadTCA($table);
00381 
00382             // Initialize:
00383         $uid = $row['uid'];
00384         $nonFields = explode(',', 'uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,pid');
00385 
00386         $outRow = array();
00387         foreach ($row as $field => $value) {
00388             if (!in_array($field, $nonFields) && is_array($TCA[$table]['columns'][$field]) && (!$onlyField || $onlyField === $field)) {
00389                 $conf = $TCA[$table]['columns'][$field]['config'];
00390 
00391                     // Add files
00392                 if ($result = $this->getRelations_procFiles($value, $conf, $uid)) {
00393                         // Creates an entry for the field with all the files:
00394                     $outRow[$field] = array(
00395                         'type' => 'file',
00396                         'newValueFiles' => $result,
00397                     );
00398                 }
00399 
00400                     // Add DB:
00401                 if ($result = $this->getRelations_procDB($value, $conf, $uid, $table)) {
00402                         // Create an entry for the field with all DB relations:
00403                     $outRow[$field] = array(
00404                         'type' => 'db',
00405                         'itemArray' => $result,
00406                     );
00407                 }
00408 
00409                     // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
00410                 if ($conf['type'] == 'flex') {
00411 
00412                         // Get current value array:
00413                         // 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()
00414                     $dataStructArray = t3lib_BEfunc::getFlexFormDS($conf, $row, $table, '', $this->WSOL);
00415                     $currentValueArray = t3lib_div::xml2array($value);
00416 
00417                         // Traversing the XML structure, processing files:
00418                     if (is_array($currentValueArray)) {
00419                         $this->temp_flexRelations = array(
00420                             'db' => array(),
00421                             'file' => array(),
00422                             'softrefs' => array()
00423                         );
00424 
00425                             // Create and call iterator object:
00426                         $flexObj = t3lib_div::makeInstance('t3lib_flexformtools');
00427                         $flexObj->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
00428 
00429                             // Create an entry for the field:
00430                         $outRow[$field] = array(
00431                             'type' => 'flex',
00432                             'flexFormRels' => $this->temp_flexRelations,
00433                         );
00434                     }
00435                 }
00436 
00437                     // Soft References:
00438                 if (strlen($value) && $softRefs = t3lib_BEfunc::explodeSoftRefParserList($conf['softref'])) {
00439                     $softRefValue = $value;
00440                     foreach ($softRefs as $spKey => $spParams) {
00441                         $softRefObj = t3lib_BEfunc::softRefParserObj($spKey);
00442                         if (is_object($softRefObj)) {
00443                             $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);
00444                             if (is_array($resultArray)) {
00445                                 $outRow[$field]['softrefs']['keys'][$spKey] = $resultArray['elements'];
00446                                 if (strlen($resultArray['content'])) {
00447                                     $softRefValue = $resultArray['content'];
00448                                 }
00449                             }
00450                         }
00451                     }
00452 
00453                     if (is_array($outRow[$field]['softrefs']) && count($outRow[$field]['softrefs']) && strcmp($value, $softRefValue) && strstr($softRefValue, '{softref:')) {
00454                         $outRow[$field]['softrefs']['tokenizedContent'] = $softRefValue;
00455                     }
00456                 }
00457             }
00458         }
00459 
00460         return $outRow;
00461     }
00462 
00463     /**
00464      * Callback function for traversing the FlexForm structure in relation to finding file and DB references!
00465      *
00466      * @param   array       Data structure for the current value
00467      * @param   mixed       Current value
00468      * @param   array       Additional configuration used in calling function
00469      * @param   string      Path of value in DS structure
00470      * @param   object      Object reference to caller
00471      * @return  void
00472      * @see t3lib_TCEmain::checkValue_flex_procInData_travDS()
00473      */
00474     function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $pObj) {
00475         $structurePath = substr($structurePath, 5) . '/'; // removing "data/" in the beginning of path (which points to location in data array)
00476 
00477         $dsConf = $dsArr['TCEforms']['config'];
00478 
00479             // Implode parameter values:
00480         list($table, $uid, $field) = array($PA['table'], $PA['uid'], $PA['field']);
00481 
00482             // Add files
00483         if ($result = $this->getRelations_procFiles($dataValue, $dsConf, $uid)) {
00484 
00485                 // Creates an entry for the field with all the files:
00486             $this->temp_flexRelations['file'][$structurePath] = $result;
00487         }
00488 
00489             // Add DB:
00490         if ($result = $this->getRelations_procDB($dataValue, $dsConf, $uid)) {
00491 
00492                 // Create an entry for the field with all DB relations:
00493             $this->temp_flexRelations['db'][$structurePath] = $result;
00494         }
00495 
00496             // Soft References:
00497         if (strlen($dataValue) && $softRefs = t3lib_BEfunc::explodeSoftRefParserList($dsConf['softref'])) {
00498             $softRefValue = $dataValue;
00499             foreach ($softRefs as $spKey => $spParams) {
00500                 $softRefObj = t3lib_BEfunc::softRefParserObj($spKey);
00501                 if (is_object($softRefObj)) {
00502                     $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams, $structurePath);
00503                     if (is_array($resultArray) && is_array($resultArray['elements'])) {
00504                         $this->temp_flexRelations['softrefs'][$structurePath]['keys'][$spKey] = $resultArray['elements'];
00505                         if (strlen($resultArray['content'])) {
00506                             $softRefValue = $resultArray['content'];
00507                         }
00508                     }
00509                 }
00510             }
00511 
00512             if (count($this->temp_flexRelations['softrefs']) && strcmp($dataValue, $softRefValue)) {
00513                 $this->temp_flexRelations['softrefs'][$structurePath]['tokenizedContent'] = $softRefValue;
00514             }
00515         }
00516     }
00517 
00518     /**
00519      * Check field configuration if it is a file relation field and extract file relations if any
00520      *
00521      * @param   string      Field value
00522      * @param   array       Field configuration array of type "TCA/columns"
00523      * @param   integer     Field uid
00524      * @return  array       If field type is OK it will return an array with the files inside. Else false
00525      */
00526     function getRelations_procFiles($value, $conf, $uid) {
00527             // Take care of files...
00528         if ($conf['type'] == 'group' && ($conf['internal_type'] == 'file' || $conf['internal_type'] == 'file_reference')) {
00529 
00530                 // Collect file values in array:
00531             if ($conf['MM']) {
00532                 $theFileValues = array();
00533                 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
00534                 $dbAnalysis->start('', 'files', $conf['MM'], $uid);
00535 
00536                 foreach ($dbAnalysis->itemArray as $somekey => $someval) {
00537                     if ($someval['id']) {
00538                         $theFileValues[] = $someval['id'];
00539                     }
00540                 }
00541             } else {
00542                 $theFileValues = explode(',', $value);
00543             }
00544 
00545                 // Traverse the files and add them:
00546             $uploadFolder = $conf['internal_type'] == 'file' ? $conf['uploadfolder'] : '';
00547             $dest = $this->destPathFromUploadFolder($uploadFolder);
00548             $newValue = array();
00549             $newValueFiles = array();
00550 
00551             foreach ($theFileValues as $file) {
00552                 if (trim($file)) {
00553                     $realFile = $dest . '/' . trim($file);
00554                     #                   if (@is_file($realFile))    {       // Now, the refernece index should NOT look if files exist - just faithfully include them if they are in the records!
00555                     $newValueFiles[] = array(
00556                         'filename' => basename($file),
00557                         'ID' => md5($realFile),
00558                         'ID_absFile' => $realFile
00559                     ); // the order should be preserved here because.. (?)
00560                     #                   } else $this->error('Missing file: '.$realFile);
00561                 }
00562             }
00563 
00564             return $newValueFiles;
00565         }
00566     }
00567 
00568     /**
00569      * Check field configuration if it is a DB relation field and extract DB relations if any
00570      *
00571      * @param   string      Field value
00572      * @param   array       Field configuration array of type "TCA/columns"
00573      * @param   integer     Field uid
00574      * @param   string      Table name
00575      * @return  array       If field type is OK it will return an array with the database relations. Else false
00576      */
00577     function getRelations_procDB($value, $conf, $uid, $table = '') {
00578 
00579             // DB record lists:
00580         if ($this->isReferenceField($conf)) {
00581             $allowedTables = $conf['type'] == 'group' ? $conf['allowed'] : $conf['foreign_table'] . ',' . $conf['neg_foreign_table'];
00582             $prependName = $conf['type'] == 'group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
00583 
00584             if ($conf['MM_opposite_field']) {
00585                 return array();
00586             }
00587 
00588             $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
00589             $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
00590 
00591             return $dbAnalysis->itemArray;
00592         }
00593     }
00594 
00595 
00596     /*******************************
00597      *
00598      * Setting values
00599      *
00600      *******************************/
00601 
00602     /**
00603      * Setting the value of a reference or removing it completely.
00604      * Usage: For lowlevel clean up operations!
00605      * 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.
00606      * 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.
00607      * 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!
00608      * To ensure proper working only admin-BE_USERS in live workspace should use this function
00609      *
00610      * @param   string      32-byte hash string identifying the record from sys_refindex which you wish to change the value for
00611      * @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
00612      * @param   boolean     Return $dataArray only, do not submit it to database.
00613      * @param   boolean     If set, it will bypass check for workspace-zero and admin user
00614      * @return  string      If a return string, that carries an error message, otherwise false (=OK) (except if $returnDataArray is set!)
00615      */
00616     function setReferenceValue($hash, $newValue, $returnDataArray = FALSE, $bypassWorkspaceAdminCheck = FALSE) {
00617 
00618         if (($GLOBALS['BE_USER']->workspace === 0 && $GLOBALS['BE_USER']->isAdmin()) || $bypassWorkspaceAdminCheck) {
00619 
00620                 // Get current index from Database:
00621             $refRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
00622                 '*',
00623                 'sys_refindex',
00624                 'hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($hash, 'sys_refindex')
00625             );
00626 
00627                 // Check if reference existed.
00628             if (is_array($refRec)) {
00629                 if ($GLOBALS['TCA'][$refRec['tablename']]) {
00630 
00631                         // Get that record from database:
00632                     $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', $refRec['tablename'], 'uid=' . intval($refRec['recuid']));
00633 
00634                     if (is_array($record)) {
00635 
00636                             // Get all relations from record, filter with fieldname:
00637                         $dbrels = $this->getRelations($refRec['tablename'], $record, $refRec['field']);
00638                         if ($dat = $dbrels[$refRec['field']]) {
00639 
00640                                 // Initialize data array that is to be sent to TCEmain afterwards:
00641                             $dataArray = array();
00642 
00643                                 // Based on type,
00644                             switch ((string) $dat['type']) {
00645                                 case 'db':
00646                                     $error = $this->setReferenceValue_dbRels($refRec, $dat['itemArray'], $newValue, $dataArray);
00647                                     if ($error) {
00648                                         return $error;
00649                                     }
00650                                 break;
00651                                 case 'file_reference':
00652                                 case 'file':
00653                                     $this->setReferenceValue_fileRels($refRec, $dat['newValueFiles'], $newValue, $dataArray);
00654                                     if ($error) {
00655                                         return $error;
00656                                     }
00657                                 break;
00658                                 case 'flex':
00659                                         // DB references:
00660                                     if (is_array($dat['flexFormRels']['db'][$refRec['flexpointer']])) {
00661                                         $error = $this->setReferenceValue_dbRels($refRec, $dat['flexFormRels']['db'][$refRec['flexpointer']], $newValue, $dataArray, $refRec['flexpointer']);
00662                                         if ($error) {
00663                                             return $error;
00664                                         }
00665                                     }
00666                                         // File references
00667                                     if (is_array($dat['flexFormRels']['file'][$refRec['flexpointer']])) {
00668                                         $this->setReferenceValue_fileRels($refRec, $dat['flexFormRels']['file'][$refRec['flexpointer']], $newValue, $dataArray, $refRec['flexpointer']);
00669                                         if ($error) {
00670                                             return $error;
00671                                         }
00672                                     }
00673                                         // Soft references in flexforms
00674                                     if ($refRec['softref_key'] && is_array($dat['flexFormRels']['softrefs'][$refRec['flexpointer']]['keys'][$refRec['softref_key']])) {
00675                                         $error = $this->setReferenceValue_softreferences($refRec, $dat['flexFormRels']['softrefs'][$refRec['flexpointer']], $newValue, $dataArray, $refRec['flexpointer']);
00676                                         if ($error) {
00677                                             return $error;
00678                                         }
00679                                     }
00680                                 break;
00681                             }
00682 
00683                                 // Softreferences in the field:
00684                             if ($refRec['softref_key'] && is_array($dat['softrefs']['keys'][$refRec['softref_key']])) {
00685                                 $error = $this->setReferenceValue_softreferences($refRec, $dat['softrefs'], $newValue, $dataArray);
00686                                 if ($error) {
00687                                     return $error;
00688                                 }
00689 
00690                             }
00691 
00692                                 // Data Array, now ready to sent to TCEmain
00693                             if ($returnDataArray) {
00694                                 return $dataArray;
00695                             } else {
00696 
00697                                     // Execute CMD array:
00698                                 $tce = t3lib_div::makeInstance('t3lib_TCEmain');
00699                                 $tce->stripslashes_values = FALSE;
00700                                 $tce->dontProcessTransformations = TRUE;
00701                                 $tce->bypassWorkspaceRestrictions = TRUE;
00702                                 $tce->bypassFileHandling = TRUE;
00703                                 $tce->bypassAccessCheckForRecords = TRUE; // Otherwise this cannot update things in deleted records...
00704 
00705                                 $tce->start($dataArray, array()); // check has been done previously that there is a backend user which is Admin and also in live workspace
00706                                 $tce->process_datamap();
00707 
00708                                     // Return errors if any:
00709                                 if (count($tce->errorLog)) {
00710                                     return LF . 'TCEmain:' . implode(LF . 'TCEmain:', $tce->errorLog);
00711                                 }
00712                             }
00713                         }
00714                     }
00715                 } else {
00716                     return 'ERROR: Tablename "' . $refRec['tablename'] . '" was not in TCA!';
00717                 }
00718             } else {
00719                 return 'ERROR: No reference record with hash="' . $hash . '" was found!';
00720             }
00721         } else {
00722             return 'ERROR: BE_USER object is not admin OR not in workspace 0 (Live)';
00723         }
00724     }
00725 
00726     /**
00727      * Setting a value for a reference for a DB field:
00728      *
00729      * @param   array       sys_refindex record
00730      * @param   array       Array of references from that field
00731      * @param   string      Value to substitute current value with (or NULL to unset it)
00732      * @param   array       data array in which the new value is set (passed by reference)
00733      * @param   string      Flexform pointer, if in a flex form field.
00734      * @return  string      Error message if any, otherwise false = OK
00735      */
00736     function setReferenceValue_dbRels($refRec, $itemArray, $newValue, &$dataArray, $flexpointer = '') {
00737         if (!strcmp($itemArray[$refRec['sorting']]['id'], $refRec['ref_uid']) && !strcmp($itemArray[$refRec['sorting']]['table'], $refRec['ref_table'])) {
00738 
00739                 // Setting or removing value:
00740             if ($newValue === NULL) { // Remove value:
00741                 unset($itemArray[$refRec['sorting']]);
00742             } else {
00743                 list($itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']) = explode(':', $newValue);
00744             }
00745 
00746                 // Traverse and compile new list of records:
00747             $saveValue = array();
00748             foreach ($itemArray as $pair) {
00749                 $saveValue[] = $pair['table'] . '_' . $pair['id'];
00750             }
00751 
00752                 // Set in data array:
00753             if ($flexpointer) {
00754                 $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
00755                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
00756                 $flexToolObj->setArrayValueByPath(substr($flexpointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
00757             } else {
00758                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
00759             }
00760 
00761         } else {
00762             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'] . '"';
00763         }
00764     }
00765 
00766     /**
00767      * Setting a value for a reference for a FILE field:
00768      *
00769      * @param   array       sys_refindex record
00770      * @param   array       Array of references from that field
00771      * @param   string      Value to substitute current value with (or NULL to unset it)
00772      * @param   array       data array in which the new value is set (passed by reference)
00773      * @param   string      Flexform pointer, if in a flex form field.
00774      * @return  string      Error message if any, otherwise false = OK
00775      */
00776     function setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexpointer = '') {
00777         if (!strcmp(substr($itemArray[$refRec['sorting']]['ID_absFile'], strlen(PATH_site)), $refRec['ref_string']) && !strcmp('_FILE', $refRec['ref_table'])) {
00778 
00779                 // Setting or removing value:
00780             if ($newValue === NULL) { // Remove value:
00781                 unset($itemArray[$refRec['sorting']]);
00782             } else {
00783                 $itemArray[$refRec['sorting']]['filename'] = $newValue;
00784             }
00785 
00786                 // Traverse and compile new list of records:
00787             $saveValue = array();
00788             foreach ($itemArray as $fileInfo) {
00789                 $saveValue[] = $fileInfo['filename'];
00790             }
00791 
00792                 // Set in data array:
00793             if ($flexpointer) {
00794                 $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
00795                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
00796                 $flexToolObj->setArrayValueByPath(substr($flexpointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
00797             } else {
00798                 $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
00799             }
00800 
00801         } else {
00802             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'] . '"';
00803         }
00804     }
00805 
00806     /**
00807      * Setting a value for a soft reference token
00808      *
00809      * @param   array       sys_refindex record
00810      * @param   array       Array of soft reference occurencies
00811      * @param   string      Value to substitute current value with
00812      * @param   array       data array in which the new value is set (passed by reference)
00813      * @param   string      Flexform pointer, if in a flex form field.
00814      * @return  string      Error message if any, otherwise false = OK
00815      */
00816     function setReferenceValue_softreferences($refRec, $softref, $newValue, &$dataArray, $flexpointer = '') {
00817         if (is_array($softref['keys'][$refRec['softref_key']][$refRec['softref_id']])) {
00818 
00819                 // Set new value:
00820             $softref['keys'][$refRec['softref_key']][$refRec['softref_id']]['subst']['tokenValue'] = '' . $newValue;
00821 
00822                 // Traverse softreferences and replace in tokenized content to rebuild it with new value inside:
00823             foreach ($softref['keys'] as $sfIndexes) {
00824                 foreach ($sfIndexes as $data) {
00825                     $softref['tokenizedContent'] = str_replace('{softref:' . $data['subst']['tokenID'] . '}', $data['subst']['tokenValue'], $softref['tokenizedContent']);
00826                 }
00827             }
00828 
00829                 // Set in data array:
00830             if (!strstr($softref['tokenizedContent'], '{softref:')) {
00831                 if ($flexpointer) {
00832                     $flexToolObj = t3lib_div::makeInstance('t3lib_flexformtools');
00833                     $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
00834                     $flexToolObj->setArrayValueByPath(substr($flexpointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
00835                 } else {
00836                     $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
00837                 }
00838             } else {
00839                 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.)';
00840             }
00841         } else {
00842             return 'ERROR: Soft reference parser key "' . $refRec['softref_key'] . '" or the index "' . $refRec['softref_id'] . '" was not found.';
00843         }
00844     }
00845 
00846 
00847     /*******************************
00848      *
00849      * Helper functions
00850      *
00851      *******************************/
00852 
00853     /**
00854      * Returns true if the TCA/columns field type is a DB reference field
00855      *
00856      * @param   array       config array for TCA/columns field
00857      * @return  boolean     True if DB reference field (group/db or select with foreign-table)
00858      */
00859     function isReferenceField($conf) {
00860         return ($conf['type'] == 'group' && $conf['internal_type'] == 'db') || (($conf['type'] == 'select' || $conf['type'] == 'inline') && $conf['foreign_table']);
00861     }
00862 
00863     /**
00864      * Returns destination path to an upload folder given by $folder
00865      *
00866      * @param   string      Folder relative to PATH_site
00867      * @return  string      Input folder prefixed with PATH_site. No checking for existence is done. Output must be a folder without trailing slash.
00868      */
00869     function destPathFromUploadFolder($folder) {
00870         if (!$folder) {
00871             return substr(PATH_site, 0, -1);
00872         }
00873         return PATH_site . $folder;
00874     }
00875 
00876     /**
00877      * Sets error message in the internal error log
00878      *
00879      * @param   string      Error message
00880      * @return  void
00881      */
00882     function error($msg) {
00883         $this->errorLog[] = $msg;
00884     }
00885 
00886     /**
00887      * Updating Index (External API)
00888      *
00889      * @param   boolean     If set, only a test
00890      * @param   boolean     If set, output CLI status
00891      * @return  array       Header and body status content
00892      */
00893     function updateIndex($testOnly, $cli_echo = FALSE) {
00894         global $TCA, $TYPO3_DB;
00895 
00896         $errors = array();
00897         $tableNames = array();
00898         $recCount = 0;
00899         $tableCount = 0;
00900 
00901         $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, use "-e" to update)' : 'Reference Index being Updated';
00902         if ($cli_echo) {
00903             echo
00904                     '*******************************************' . LF .
00905                     $headerContent . LF .
00906                     '*******************************************' . LF;
00907         }
00908 
00909             // Traverse all tables:
00910         foreach ($TCA as $tableName => $cfg) {
00911             $tableNames[] = $tableName;
00912             $tableCount++;
00913 
00914                 // Traverse all records in tables, including deleted records:
00915             $allRecs = $TYPO3_DB->exec_SELECTgetRows('uid', $tableName, '1=1'); //.t3lib_BEfunc::deleteClause($tableName)
00916             $uidList = array(0);
00917             foreach ($allRecs as $recdat) {
00918                 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
00919                 $result = $refIndexObj->updateRefIndexTable($tableName, $recdat['uid'], $testOnly);
00920                 $uidList[] = $recdat['uid'];
00921                 $recCount++;
00922 
00923                 if ($result['addedNodes'] || $result['deletedNodes']) {
00924                     $Err = 'Record ' . $tableName . ':' . $recdat['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
00925                     $errors[] = $Err;
00926                     if ($cli_echo) {
00927                         echo $Err . LF;
00928                     }
00929                 }
00930             }
00931 
00932                 // Searching lost indexes for this table:
00933             $where = 'tablename=' . $TYPO3_DB->fullQuoteStr($tableName, 'sys_refindex') . ' AND recuid NOT IN (' . implode(',', $uidList) . ')';
00934             $lostIndexes = $TYPO3_DB->exec_SELECTgetRows('hash', 'sys_refindex', $where);
00935             if (count($lostIndexes)) {
00936                 $Err = 'Table ' . $tableName . ' has ' . count($lostIndexes) . ' lost indexes which are now deleted';
00937                 $errors[] = $Err;
00938                 if ($cli_echo) {
00939                     echo $Err . LF;
00940                 }
00941                 if (!$testOnly) {
00942                     $TYPO3_DB->exec_DELETEquery('sys_refindex', $where);
00943                 }
00944             }
00945         }
00946 
00947             // Searching lost indexes for non-existing tables:
00948         $where = 'tablename NOT IN (' . implode(',', $TYPO3_DB->fullQuoteArray($tableNames, 'sys_refindex')) . ')';
00949         $lostTables = $TYPO3_DB->exec_SELECTgetRows('hash', 'sys_refindex', $where);
00950         if (count($lostTables)) {
00951             $Err = 'Index table hosted ' . count($lostTables) . ' indexes for non-existing tables, now removed';
00952             $errors[] = $Err;
00953             if ($cli_echo) {
00954                 echo $Err . LF;
00955             }
00956             if (!$testOnly) {
00957                 $TYPO3_DB->exec_DELETEquery('sys_refindex', $where);
00958             }
00959         }
00960 
00961         $testedHowMuch = $recCount . ' records from ' . $tableCount . ' tables were checked/updated.' . LF;
00962 
00963         $bodyContent = $testedHowMuch . (count($errors) ? implode(LF, $errors) : 'Index Integrity was perfect!');
00964         if ($cli_echo) {
00965             echo $testedHowMuch . (count($errors) ? 'Updates: ' . count($errors) : 'Index Integrity was perfect!') . LF;
00966         }
00967 
00968         if (!$testOnly) {
00969             $registry = t3lib_div::makeInstance('t3lib_Registry');
00970             $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
00971         }
00972 
00973         return array($headerContent, $bodyContent, count($errors));
00974     }
00975 }
00976 
00977 
00978 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_refindex.php'])) {
00979     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_refindex.php']);
00980 }
00981 
00982 ?>