TYPO3  7.6
ReferenceIndex.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Database;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
28 
43 {
58  protected static $nonRelationTables = array(
59  'sys_log' => true,
60  'sys_history' => true,
61  'tx_extensionmanager_domain_model_extension' => true
62  );
63 
75  protected static $nonRelationFields = array(
76  'uid' => true,
77  'perms_userid' => true,
78  'perms_groupid' => true,
79  'perms_user' => true,
80  'perms_group' => true,
81  'perms_everybody' => true,
82  'pid' => true
83  );
84 
91  protected static $cachePrefixTableRelationFields = 'core-refidx-tblRelFields-';
92 
99  public $temp_flexRelations = array();
100 
108  public $errorLog = array();
109 
117  public $WSOL = false;
118 
125  public $relations = array();
126 
133  public $hashVersion = 1;
134 
140  protected $workspaceId = 0;
141 
147  protected $runtimeCache = null;
148 
152  public function __construct()
153  {
154  $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
155  }
156 
163  public function setWorkspaceId($workspaceId)
164  {
165  $this->workspaceId = (int)$workspaceId;
166  }
167 
174  public function getWorkspaceId()
175  {
176  return $this->workspaceId;
177  }
178 
191  public function updateRefIndexTable($tableName, $uid, $testOnly = false)
192  {
193 
194  // First, secure that the index table is not updated with workspace tainted relations:
195  $this->WSOL = false;
196 
197  // Init:
198  $result = array(
199  'keptNodes' => 0,
200  'deletedNodes' => 0,
201  'addedNodes' => 0
202  );
203 
204  // If this table cannot contain relations, skip it
205  if (isset(static::$nonRelationTables[$tableName])) {
206  return $result;
207  }
208 
209  // Fetch tableRelationFields and save them in cache if not there yet
210  $cacheId = static::$cachePrefixTableRelationFields . $tableName;
211  if (!$this->runtimeCache->has($cacheId)) {
212  $tableRelationFields = $this->fetchTableRelationFields($tableName);
213  $this->runtimeCache->set($cacheId, $tableRelationFields);
214  } else {
215  $tableRelationFields = $this->runtimeCache->get($cacheId);
216  }
217 
218  $databaseConnection = $this->getDatabaseConnection();
219 
220  // Get current index from Database with hash as index using $uidIndexField
221  $currentRelations = $databaseConnection->exec_SELECTgetRows(
222  '*',
223  'sys_refindex',
224  'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_refindex')
225  . ' AND recuid=' . (int)$uid . ' AND workspace=' . $this->getWorkspaceId(),
226  '', '', '', 'hash'
227  );
228 
229  // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
230  if ($tableRelationFields !== '' && BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid, 'uid')) {
231  // Then, get relations:
232  $relations = $this->generateRefIndexData($tableName, $uid);
233  if (is_array($relations)) {
234  // Traverse the generated index:
235  foreach ($relations as &$relation) {
236  if (!is_array($relation)) {
237  continue;
238  }
239  $relation['hash'] = md5(implode('
240  // First, check if already indexed and if so, unset that row (so in the end we know which rows to remove!)
241  if (isset($currentRelations[$relation['hash']])) {
242  unset($currentRelations[$relation['hash']]);
243  $result['keptNodes']++;
244  $relation['_ACTION'] = 'KEPT';
245  } else {
246  // If new, add it:
247  if (!$testOnly) {
248  $databaseConnection->exec_INSERTquery('sys_refindex', $relation);
249  }
250  $result['addedNodes']++;
251  $relation['_ACTION'] = 'ADDED';
252  }
253  }
254  $result['relations'] = $relations;
255  } else {
256  return $result;
257  }
258  }
259 
260  // If any old are left, remove them:
261  if (!empty($currentRelations)) {
262  $hashList = array_keys($currentRelations);
263  if (!empty($hashList)) {
264  $result['deletedNodes'] = count($hashList);
265  $result['deletedNodes_hashList'] = implode(',', $hashList);
266  if (!$testOnly) {
267  $databaseConnection->exec_DELETEquery(
268  'sys_refindex', 'hash IN (' . implode(',', $databaseConnection->fullQuoteArray($hashList, 'sys_refindex')) . ')'
269  );
270  }
271  }
272  }
273 
274  return $result;
275  }
276 
285  public function generateRefIndexData($tableName, $uid)
286  {
287  if (!isset($GLOBALS['TCA'][$tableName])) {
288  return null;
289  }
290 
291  $this->relations = array();
292 
293  // Fetch tableRelationFields and save them in cache if not there yet
294  $cacheId = static::$cachePrefixTableRelationFields . $tableName;
295  if (!$this->runtimeCache->has($cacheId)) {
296  $tableRelationFields = $this->fetchTableRelationFields($tableName);
297  $this->runtimeCache->set($cacheId, $tableRelationFields);
298  } else {
299  $tableRelationFields = $this->runtimeCache->get($cacheId);
300  }
301 
302  // Return if there are no fields which could contain relations
303  if ($tableRelationFields === '') {
304  return $this->relations;
305  }
306 
307  $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
308 
309  if ($tableRelationFields === '*') {
310  // If one field of a record is of type flex, all fields have to be fetched to be passed to BackendUtility::getFlexFormDS
311  $selectFields = '*';
312  } else {
313  // otherwise only fields that might contain relations are fetched
314  $selectFields = 'uid,' . $tableRelationFields . ($deleteField ? ',' . $deleteField : '');
315  }
316 
317  // Get raw record from DB:
318  $record = $this->getDatabaseConnection()->exec_SELECTgetSingleRow($selectFields, $tableName, 'uid=' . (int)$uid);
319  if (!is_array($record)) {
320  return null;
321  }
322 
323  // Deleted:
324  $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
325 
326  // Get all relations from record:
327  $recordRelations = $this->getRelations($tableName, $record);
328  // Traverse those relations, compile records to insert in table:
329  foreach ($recordRelations as $fieldName => $fieldRelations) {
330  // Based on type
331  switch ((string)$fieldRelations['type']) {
332  case 'db':
333  $this->createEntryData_dbRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['itemArray']);
334  break;
335  case 'file_reference':
336  // not used (see getRelations()), but fallback to file
337  case 'file':
338  $this->createEntryData_fileRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['newValueFiles']);
339  break;
340  case 'flex':
341  // DB references in FlexForms
342  if (is_array($fieldRelations['flexFormRels']['db'])) {
343  foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
344  $this->createEntryData_dbRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
345  }
346  }
347  // File references in FlexForms
348  // @todo #65463 Test correct handling of file references in FlexForms
349  if (is_array($fieldRelations['flexFormRels']['file'])) {
350  foreach ($fieldRelations['flexFormRels']['file'] as $flexPointer => $subList) {
351  $this->createEntryData_fileRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
352  }
353  }
354  // Soft references in FlexForms
355  // @todo #65464 Test correct handling of soft references in FlexForms
356  if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
357  foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
358  $this->createEntryData_softreferences($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList['keys']);
359  }
360  }
361  break;
362  }
363  // Soft references in the field
364  if (is_array($fieldRelations['softrefs'])) {
365  $this->createEntryData_softreferences($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
366  }
367  }
368 
369  return $this->relations;
370  }
371 
389  public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
390  {
391  if (BackendUtility::isTableWorkspaceEnabled($table)) {
392  $element = BackendUtility::getRecord($table, $uid, 't3ver_wsid');
393  if ($element !== null && isset($element['t3ver_wsid']) && (int)$element['t3ver_wsid'] !== $this->getWorkspaceId()) {
394  //The given Element is ws-enabled but doesn't live in the selected workspace
395  // => don't add index as it's not actually there
396  return false;
397  }
398  }
399  return array(
400  'tablename' => $table,
401  'recuid' => $uid,
402  'field' => $field,
403  'flexpointer' => $flexPointer,
404  'softref_key' => $softref_key,
405  'softref_id' => $softref_id,
406  'sorting' => $sort,
407  'deleted' => $deleted,
408  'workspace' => $this->getWorkspaceId(),
409  'ref_table' => $ref_table,
410  'ref_uid' => $ref_uid,
411  'ref_string' => $ref_string
412  );
413  }
414 
426  public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
427  {
428  foreach ($items as $sort => $i) {
429  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $i['table'], $i['id'], '', $sort);
430  }
431  }
432 
444  public function createEntryData_fileRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
445  {
446  foreach ($items as $sort => $i) {
447  $filePath = $i['ID_absFile'];
448  if (GeneralUtility::isFirstPartOfStr($filePath, PATH_site)) {
449  $filePath = PathUtility::stripPathSitePrefix($filePath);
450  }
451  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $filePath, $sort);
452  }
453  }
454 
466  public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
467  {
468  if (is_array($keys)) {
469  foreach ($keys as $spKey => $elements) {
470  if (is_array($elements)) {
471  foreach ($elements as $subKey => $el) {
472  if (is_array($el['subst'])) {
473  switch ((string)$el['subst']['type']) {
474  case 'db':
475  list($tableName, $recordId) = explode(':', $el['subst']['recordRef']);
476  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $tableName, $recordId, '', -1, $spKey, $subKey);
477  break;
478  case 'file_reference':
479  // not used (see getRelations()), but fallback to file
480  case 'file':
481  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $el['subst']['relFileName'], -1, $spKey, $subKey);
482  break;
483  case 'string':
484  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
485  break;
486  }
487  }
488  }
489  }
490  }
491  }
492  }
493 
494  /*******************************
495  *
496  * Get relations from table row
497  *
498  *******************************/
499 
511  public function getRelations($table, $row, $onlyField = '')
512  {
513  // Initialize:
514  $uid = $row['uid'];
515  $outRow = array();
516  foreach ($row as $field => $value) {
517  if (!isset(static::$nonRelationFields[$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && (!$onlyField || $onlyField === $field)) {
518  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
519  // Add files
520  $resultsFromFiles = $this->getRelations_procFiles($value, $conf, $uid);
521  if (!empty($resultsFromFiles)) {
522  // We have to fill different arrays here depending on the result.
523  // internal_type file is still a relation of type file and
524  // since http://forge.typo3.org/issues/49538 internal_type file_reference
525  // is a database relation to a sys_file record
526  $fileResultsFromFiles = array();
527  $dbResultsFromFiles = array();
528  foreach ($resultsFromFiles as $resultFromFiles) {
529  if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
530  $dbResultsFromFiles[] = $resultFromFiles;
531  } else {
532  // Creates an entry for the field with all the files:
533  $fileResultsFromFiles[] = $resultFromFiles;
534  }
535  }
536  if (!empty($fileResultsFromFiles)) {
537  $outRow[$field] = array(
538  'type' => 'file',
539  'newValueFiles' => $fileResultsFromFiles
540  );
541  }
542  if (!empty($dbResultsFromFiles)) {
543  $outRow[$field] = array(
544  'type' => 'db',
545  'itemArray' => $dbResultsFromFiles
546  );
547  }
548  }
549  // Add a softref definition for link fields if the TCA does not specify one already
550  if ($conf['type'] === 'input' && isset($conf['wizards']['link']) && empty($conf['softref'])) {
551  $conf['softref'] = 'typolink';
552  }
553  // Add DB:
554  $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table, $field);
555  if (!empty($resultsFromDatabase)) {
556  // Create an entry for the field with all DB relations:
557  $outRow[$field] = array(
558  'type' => 'db',
559  'itemArray' => $resultsFromDatabase
560  );
561  }
562  // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
563  if ($conf['type'] === 'flex') {
564  // Get current value array:
565  // 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 \TYPO3\CMS\Backend\Utility\BackendUtility::getFlexFormDS()
566  $currentValueArray = GeneralUtility::xml2array($value);
567  // Traversing the XML structure, processing files:
568  if (is_array($currentValueArray)) {
569  $this->temp_flexRelations = array(
570  'db' => array(),
571  'file' => array(),
572  'softrefs' => array()
573  );
574  // Create and call iterator object:
575  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
576  $flexFormTools->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
577  // Create an entry for the field:
578  $outRow[$field] = array(
579  'type' => 'flex',
580  'flexFormRels' => $this->temp_flexRelations
581  );
582  }
583  }
584  // Soft References:
585  if ((string)$value !== '') {
586  $softRefValue = $value;
587  $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
588  if ($softRefs !== false) {
589  foreach ($softRefs as $spKey => $spParams) {
590  $softRefObj = BackendUtility::softRefParserObj($spKey);
591  if (is_object($softRefObj)) {
592  $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);
593  if (is_array($resultArray)) {
594  $outRow[$field]['softrefs']['keys'][$spKey] = $resultArray['elements'];
595  if ((string)$resultArray['content'] !== '') {
596  $softRefValue = $resultArray['content'];
597  }
598  }
599  }
600  }
601  }
602  if (!empty($outRow[$field]['softrefs']) && (string)$value !== (string)$softRefValue && strpos($softRefValue, '{softref:') !== false) {
603  $outRow[$field]['softrefs']['tokenizedContent'] = $softRefValue;
604  }
605  }
606  }
607  }
608  return $outRow;
609  }
610 
622  public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $parentObject)
623  {
624  // Removing "data/" in the beginning of path (which points to location in data array)
625  $structurePath = substr($structurePath, 5) . '/';
626  $dsConf = $dsArr['TCEforms']['config'];
627  // Implode parameter values:
628  list($table, $uid, $field) = array(
629  $PA['table'],
630  $PA['uid'],
631  $PA['field']
632  );
633  // Add files
634  $resultsFromFiles = $this->getRelations_procFiles($dataValue, $dsConf, $uid);
635  if (!empty($resultsFromFiles)) {
636  // We have to fill different arrays here depending on the result.
637  // internal_type file is still a relation of type file and
638  // since http://forge.typo3.org/issues/49538 internal_type file_reference
639  // is a database relation to a sys_file record
640  $fileResultsFromFiles = array();
641  $dbResultsFromFiles = array();
642  foreach ($resultsFromFiles as $resultFromFiles) {
643  if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
644  $dbResultsFromFiles[] = $resultFromFiles;
645  } else {
646  $fileResultsFromFiles[] = $resultFromFiles;
647  }
648  }
649  if (!empty($fileResultsFromFiles)) {
650  $this->temp_flexRelations['file'][$structurePath] = $fileResultsFromFiles;
651  }
652  if (!empty($dbResultsFromFiles)) {
653  $this->temp_flexRelations['db'][$structurePath] = $dbResultsFromFiles;
654  }
655  }
656  // Add a softref definition for link fields if the TCA does not specify one already
657  if ($dsConf['type'] === 'input' && isset($dsConf['wizards']['link']) && empty($dsConf['softref'])) {
658  $dsConf['softref'] = 'typolink';
659  }
660  // Add DB:
661  $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table, $field);
662  if (!empty($resultsFromDatabase)) {
663  // Create an entry for the field with all DB relations:
664  $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
665  }
666  // Soft References:
667  if (is_array($dataValue) || (string)$dataValue !== '') {
668  $softRefValue = $dataValue;
669  $softRefs = BackendUtility::explodeSoftRefParserList($dsConf['softref']);
670  if ($softRefs !== false) {
671  foreach ($softRefs as $spKey => $spParams) {
672  $softRefObj = BackendUtility::softRefParserObj($spKey);
673  if (is_object($softRefObj)) {
674  $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams, $structurePath);
675  if (is_array($resultArray) && is_array($resultArray['elements'])) {
676  $this->temp_flexRelations['softrefs'][$structurePath]['keys'][$spKey] = $resultArray['elements'];
677  if ((string)$resultArray['content'] !== '') {
678  $softRefValue = $resultArray['content'];
679  }
680  }
681  }
682  }
683  }
684  if (!empty($this->temp_flexRelations['softrefs']) && (string)$dataValue !== (string)$softRefValue) {
685  $this->temp_flexRelations['softrefs'][$structurePath]['tokenizedContent'] = $softRefValue;
686  }
687  }
688  }
689 
698  public function getRelations_procFiles($value, $conf, $uid)
699  {
700  if ($conf['type'] !== 'group' || ($conf['internal_type'] !== 'file' && $conf['internal_type'] !== 'file_reference')) {
701  return false;
702  }
703 
704  // Collect file values in array:
705  if ($conf['MM']) {
706  $theFileValues = array();
707  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
708  $dbAnalysis->start('', 'files', $conf['MM'], $uid);
709  foreach ($dbAnalysis->itemArray as $someval) {
710  if ($someval['id']) {
711  $theFileValues[] = $someval['id'];
712  }
713  }
714  } else {
715  $theFileValues = explode(',', $value);
716  }
717  // Traverse the files and add them:
718  $uploadFolder = $conf['internal_type'] === 'file' ? $conf['uploadfolder'] : '';
719  $destinationFolder = $this->destPathFromUploadFolder($uploadFolder);
720  $newValueFiles = array();
721  foreach ($theFileValues as $file) {
722  if (trim($file)) {
723  $realFile = $destinationFolder . '/' . trim($file);
724  $newValueFile = array(
725  'filename' => basename($file),
726  'ID' => md5($realFile),
727  'ID_absFile' => $realFile
728  );
729  // Set sys_file and id for referenced files
730  if ($conf['internal_type'] === 'file_reference') {
731  try {
732  $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($file);
733  if ($file instanceof File || $file instanceof Folder) {
734  // For setting this as sys_file relation later, the keys filename, ID and ID_absFile
735  // have not to be included, because the are not evaluated for db relations.
736  $newValueFile = array(
737  'table' => 'sys_file',
738  'id' => $file->getUid()
739  );
740  }
741  } catch (\Exception $e) {
742  }
743  }
744  $newValueFiles[] = $newValueFile;
745  }
746  }
747  return $newValueFiles;
748  }
749 
760  public function getRelations_procDB($value, $conf, $uid, $table = '', $field = '')
761  {
762  // Get IRRE relations
763  if (empty($conf)) {
764  return false;
765  } elseif ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
766  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
767  $dbAnalysis->setUseLiveReferenceIds(false);
768  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
769  return $dbAnalysis->itemArray;
770  // DB record lists:
771  } elseif ($this->isDbReferenceField($conf)) {
772  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
773  if ($conf['MM_opposite_field']) {
774  return array();
775  }
776  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
777  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
778  return $dbAnalysis->itemArray;
779  } elseif ($conf['type'] === 'inline' && $conf['foreign_table'] === 'sys_file_reference') {
780  // @todo It looks like this was never called before since isDbReferenceField also checks for type 'inline' and any 'foreign_table'
781  $files = $this->getDatabaseConnection()->exec_SELECTgetRows(
782  'uid_local',
783  'sys_file_reference',
784  'tablenames=\'' . $table . '\' AND fieldname=\'' . $field . '\' AND uid_foreign=' . $uid . ' AND deleted=0'
785  );
786  $fileArray = array();
787  if (!empty($files)) {
788  foreach ($files as $fileUid) {
789  $fileArray[] = array(
790  'table' => 'sys_file',
791  'id' => $fileUid['uid_local']
792  );
793  }
794  }
795  return $fileArray;
796  }
797  return false;
798  }
799 
800  /*******************************
801  *
802  * Setting values
803  *
804  *******************************/
805 
825  public function setReferenceValue($hash, $newValue, $returnDataArray = false, $bypassWorkspaceAdminCheck = false)
826  {
827  $backendUser = $this->getBackendUser();
828  if ($backendUser->workspace === 0 && $backendUser->isAdmin() || $bypassWorkspaceAdminCheck) {
829  $databaseConnection = $this->getDatabaseConnection();
830 
831  // Get current index from Database:
832  $referenceRecord = $databaseConnection->exec_SELECTgetSingleRow('*', 'sys_refindex', 'hash=' . $databaseConnection->fullQuoteStr($hash, 'sys_refindex'));
833  // Check if reference existed.
834  if (!is_array($referenceRecord)) {
835  return 'ERROR: No reference record with hash="' . $hash . '" was found!';
836  }
837 
838  if (empty($GLOBALS['TCA'][$referenceRecord['tablename']])) {
839  return 'ERROR: Table "' . $referenceRecord['tablename'] . '" was not in TCA!';
840  }
841 
842  // Get that record from database:
843  $record = $databaseConnection->exec_SELECTgetSingleRow('*', $referenceRecord['tablename'], 'uid=' . (int)$referenceRecord['recuid']);
844  if (is_array($record)) {
845  // Get relation for single field from record
846  $recordRelations = $this->getRelations($referenceRecord['tablename'], $record, $referenceRecord['field']);
847  if ($fieldRelation = $recordRelations[$referenceRecord['field']]) {
848  // Initialize data array that is to be sent to DataHandler afterwards:
849  $dataArray = array();
850  // Based on type
851  switch ((string)$fieldRelation['type']) {
852  case 'db':
853  $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['itemArray'], $newValue, $dataArray);
854  if ($error) {
855  return $error;
856  }
857  break;
858  case 'file_reference':
859  // not used (see getRelations()), but fallback to file
860  case 'file':
861  $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['newValueFiles'], $newValue, $dataArray);
862  if ($error) {
863  return $error;
864  }
865  break;
866  case 'flex':
867  // DB references in FlexForms
868  if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
869  $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
870  if ($error) {
871  return $error;
872  }
873  }
874  // File references in FlexForms
875  if (is_array($fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']])) {
876  $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
877  if ($error) {
878  return $error;
879  }
880  }
881  // Soft references in FlexForms
882  if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
883  $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
884  if ($error) {
885  return $error;
886  }
887  }
888  break;
889  }
890  // Soft references in the field:
891  if ($referenceRecord['softref_key'] && is_array($fieldRelation['softrefs']['keys'][$referenceRecord['softref_key']])) {
892  $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['softrefs'], $newValue, $dataArray);
893  if ($error) {
894  return $error;
895  }
896  }
897  // Data Array, now ready to be sent to DataHandler
898  if ($returnDataArray) {
899  return $dataArray;
900  } else {
901  // Execute CMD array:
902  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
903  $dataHandler->stripslashes_values = false;
904  $dataHandler->dontProcessTransformations = true;
905  $dataHandler->bypassWorkspaceRestrictions = true;
906  $dataHandler->bypassFileHandling = true;
907  // Otherwise this cannot update things in deleted records...
908  $dataHandler->bypassAccessCheckForRecords = true;
909  // Check has been done previously that there is a backend user which is Admin and also in live workspace
910  $dataHandler->start($dataArray, array());
911  $dataHandler->process_datamap();
912  // Return errors if any:
913  if (!empty($dataHandler->errorLog)) {
914  return LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $dataHandler->errorLog);
915  }
916  }
917  }
918  }
919  } else {
920  return 'ERROR: BE_USER object is not admin OR not in workspace 0 (Live)';
921  }
922 
923  return false;
924  }
925 
936  public function setReferenceValue_dbRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
937  {
938  if ((int)$itemArray[$refRec['sorting']]['id'] === (int)$refRec['ref_uid'] && (string)$itemArray[$refRec['sorting']]['table'] === (string)$refRec['ref_table']) {
939  // Setting or removing value:
940  // Remove value:
941  if ($newValue === null) {
942  unset($itemArray[$refRec['sorting']]);
943  } else {
944  list($itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']) = explode(':', $newValue);
945  }
946  // Traverse and compile new list of records:
947  $saveValue = array();
948  foreach ($itemArray as $pair) {
949  $saveValue[] = $pair['table'] . '_' . $pair['id'];
950  }
951  // Set in data array:
952  if ($flexPointer) {
953  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
954  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
955  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
956  } else {
957  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
958  }
959  } else {
960  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'] . '"';
961  }
962 
963  return false;
964  }
965 
976  public function setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
977  {
978  $ID_absFile = PathUtility::stripPathSitePrefix($itemArray[$refRec['sorting']]['ID_absFile']);
979  if ($ID_absFile === (string)$refRec['ref_string'] && $refRec['ref_table'] === '_FILE') {
980  // Setting or removing value:
981  // Remove value:
982  if ($newValue === null) {
983  unset($itemArray[$refRec['sorting']]);
984  } else {
985  $itemArray[$refRec['sorting']]['filename'] = $newValue;
986  }
987  // Traverse and compile new list of records:
988  $saveValue = array();
989  foreach ($itemArray as $fileInfo) {
990  $saveValue[] = $fileInfo['filename'];
991  }
992  // Set in data array:
993  if ($flexPointer) {
994  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
995  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
996  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
997  } else {
998  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
999  }
1000  } else {
1001  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'] . '"';
1002  }
1003 
1004  return false;
1005  }
1006 
1017  public function setReferenceValue_softreferences($refRec, $softref, $newValue, &$dataArray, $flexPointer = '')
1018  {
1019  if (!is_array($softref['keys'][$refRec['softref_key']][$refRec['softref_id']])) {
1020  return 'ERROR: Soft reference parser key "' . $refRec['softref_key'] . '" or the index "' . $refRec['softref_id'] . '" was not found.';
1021  }
1022 
1023  // Set new value:
1024  $softref['keys'][$refRec['softref_key']][$refRec['softref_id']]['subst']['tokenValue'] = '' . $newValue;
1025  // Traverse softreferences and replace in tokenized content to rebuild it with new value inside:
1026  foreach ($softref['keys'] as $sfIndexes) {
1027  foreach ($sfIndexes as $data) {
1028  $softref['tokenizedContent'] = str_replace('{softref:' . $data['subst']['tokenID'] . '}', $data['subst']['tokenValue'], $softref['tokenizedContent']);
1029  }
1030  }
1031  // Set in data array:
1032  if (!strstr($softref['tokenizedContent'], '{softref:')) {
1033  if ($flexPointer) {
1034  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
1035  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = array();
1036  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
1037  } else {
1038  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
1039  }
1040  } else {
1041  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.)';
1042  }
1043 
1044  return false;
1045  }
1046 
1047  /*******************************
1048  *
1049  * Helper functions
1050  *
1051  *******************************/
1052 
1059  protected function isDbReferenceField(array $configuration)
1060  {
1061  return (
1062  ($configuration['type'] === 'group' && $configuration['internal_type'] === 'db')
1063  || (
1064  ($configuration['type'] === 'select' || $configuration['type'] === 'inline')
1065  && !empty($configuration['foreign_table'])
1066  )
1067  );
1068  }
1069 
1076  public function isReferenceField(array $configuration)
1077  {
1078  return (
1079  $this->isDbReferenceField($configuration)
1080  ||
1081  ($configuration['type'] === 'group' && ($configuration['internal_type'] === 'file' || $configuration['internal_type'] === 'file_reference')) // getRelations_procFiles
1082  ||
1083  ($configuration['type'] === 'input' && isset($configuration['wizards']['link'])) // getRelations_procDB
1084  ||
1085  $configuration['type'] === 'flex'
1086  ||
1087  isset($configuration['softref'])
1088  ||
1089  (
1090  // @deprecated global soft reference parsers are deprecated since TYPO3 CMS 7 and will be removed in TYPO3 CMS 8
1091  is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser_GL'])
1092  && !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['softRefParser_GL'])
1093  )
1094  );
1095  }
1096 
1103  protected function fetchTableRelationFields($tableName)
1104  {
1105  if (!isset($GLOBALS['TCA'][$tableName])) {
1106  return '';
1107  }
1108 
1109  $fields = array();
1110 
1111  foreach ($GLOBALS['TCA'][$tableName]['columns'] as $field => $fieldDefinition) {
1112  if (is_array($fieldDefinition['config'])) {
1113  // Check for flex field
1114  if (isset($fieldDefinition['config']['type']) && $fieldDefinition['config']['type'] === 'flex') {
1115  // Fetch all fields if the is a field of type flex in the table definition because the complete row is passed to
1116  // BackendUtility::getFlexFormDS in the end and might be needed in ds_pointerField or $hookObj->getFlexFormDS_postProcessDS
1117  return '*';
1118  }
1119  // Only fetch this field if it can contain a reference
1120  if ($this->isReferenceField($fieldDefinition['config'])) {
1121  $fields[] = $field;
1122  }
1123  }
1124  }
1125 
1126  return implode(',', $fields);
1127  }
1128 
1135  public function destPathFromUploadFolder($folder)
1136  {
1137  if (!$folder) {
1138  return substr(PATH_site, 0, -1);
1139  }
1140  return PATH_site . $folder;
1141  }
1142 
1150  public function error($msg)
1151  {
1152  GeneralUtility::logDeprecatedFunction();
1153  $this->errorLog[] = $msg;
1154  }
1155 
1163  public function updateIndex($testOnly, $cli_echo = false)
1164  {
1165  $databaseConnection = $this->getDatabaseConnection();
1166  $errors = array();
1167  $tableNames = array();
1168  $recCount = 0;
1169  $tableCount = 0;
1170  $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, use "--refindex update" to update)' : 'Reference Index being Updated';
1171  if ($cli_echo) {
1172  echo '*******************************************' . LF . $headerContent . LF . '*******************************************' . LF;
1173  }
1174  // Traverse all tables:
1175  foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
1176  if (isset(static::$nonRelationTables[$tableName])) {
1177  continue;
1178  }
1179  // Traverse all records in tables, including deleted records:
1180  $fieldNames = (BackendUtility::isTableWorkspaceEnabled($tableName) ? 'uid,t3ver_wsid' : 'uid');
1181  $res = $databaseConnection->exec_SELECTquery($fieldNames, $tableName, '1=1');
1182  if ($databaseConnection->sql_error()) {
1183  // Table exists in $TCA but does not exist in the database
1184  GeneralUtility::sysLog(sprintf('Table "%s" exists in $TCA but does not exist in the database. You should run the Database Analyzer in the Install Tool to fix this.', $tableName), 'core', GeneralUtility::SYSLOG_SEVERITY_ERROR);
1185  continue;
1186  }
1187  $tableNames[] = $tableName;
1188  $tableCount++;
1189  $uidList = array(0);
1190  while ($record = $databaseConnection->sql_fetch_assoc($res)) {
1192  $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
1193  if (isset($record['t3ver_wsid'])) {
1194  $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
1195  }
1196  $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
1197  $uidList[] = $record['uid'];
1198  $recCount++;
1199  if ($result['addedNodes'] || $result['deletedNodes']) {
1200  $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
1201  $errors[] = $error;
1202  if ($cli_echo) {
1203  echo $error . LF;
1204  }
1205  }
1206  }
1207  $databaseConnection->sql_free_result($res);
1208 
1209  // Searching lost indexes for this table:
1210  $where = 'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_refindex') . ' AND recuid NOT IN (' . implode(',', $uidList) . ')';
1211  $lostIndexes = $databaseConnection->exec_SELECTgetRows('hash', 'sys_refindex', $where);
1212  $lostIndexesCount = count($lostIndexes);
1213  if ($lostIndexesCount) {
1214  $error = 'Table ' . $tableName . ' has ' . $lostIndexesCount . ' lost indexes which are now deleted';
1215  $errors[] = $error;
1216  if ($cli_echo) {
1217  echo $error . LF;
1218  }
1219  if (!$testOnly) {
1220  $databaseConnection->exec_DELETEquery('sys_refindex', $where);
1221  }
1222  }
1223  }
1224  // Searching lost indexes for non-existing tables:
1225  $where = 'tablename NOT IN (' . implode(',', $databaseConnection->fullQuoteArray($tableNames, 'sys_refindex')) . ')';
1226  $lostTables = $databaseConnection->exec_SELECTgetRows('hash', 'sys_refindex', $where);
1227  $lostTablesCount = count($lostTables);
1228  if ($lostTablesCount) {
1229  $error = 'Index table hosted ' . $lostTablesCount . ' indexes for non-existing tables, now removed';
1230  $errors[] = $error;
1231  if ($cli_echo) {
1232  echo $error . LF;
1233  }
1234  if (!$testOnly) {
1235  $databaseConnection->exec_DELETEquery('sys_refindex', $where);
1236  }
1237  }
1238  $errorCount = count($errors);
1239  $recordsCheckedString = $recCount . ' records from ' . $tableCount . ' tables were checked/updated.' . LF;
1240  $flashMessage = GeneralUtility::makeInstance(
1241  FlashMessage::class,
1242  $errorCount ? implode('##LF##', $errors) : 'Index Integrity was perfect!',
1243  $recordsCheckedString,
1244  $errorCount ? FlashMessage::ERROR : FlashMessage::OK
1245  );
1246  $bodyContent = $flashMessage->render();
1247  if ($cli_echo) {
1248  echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
1249  }
1250  if (!$testOnly) {
1251  $registry = GeneralUtility::makeInstance(Registry::class);
1252  $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
1253  }
1254  return array($headerContent, $bodyContent, $errorCount);
1255  }
1256 
1262  protected function getDatabaseConnection()
1263  {
1264  return $GLOBALS['TYPO3_DB'];
1265  }
1266 
1272  protected function getBackendUser()
1273  {
1274  return $GLOBALS['BE_USER'];
1275  }
1276 }