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