TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
17 use Doctrine\DBAL\DBALException;
30 
45 {
60  protected static $nonRelationTables = [
61  'sys_log' => true,
62  'sys_history' => true,
63  'tx_extensionmanager_domain_model_extension' => true
64  ];
65 
77  protected static $nonRelationFields = [
78  'uid' => true,
79  'perms_userid' => true,
80  'perms_groupid' => true,
81  'perms_user' => true,
82  'perms_group' => true,
83  'perms_everybody' => true,
84  'pid' => true
85  ];
86 
93  protected static $cachePrefixTableRelationFields = 'core-refidx-tblRelFields-';
94 
101  public $temp_flexRelations = [];
102 
110  public $WSOL = false;
111 
118  public $relations = [];
119 
126  public $hashVersion = 1;
127 
133  protected $workspaceId = 0;
134 
140  protected $runtimeCache = null;
141 
145  public function __construct()
146  {
147  $this->runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
148  }
149 
156  public function setWorkspaceId($workspaceId)
157  {
158  $this->workspaceId = (int)$workspaceId;
159  }
160 
167  public function getWorkspaceId()
168  {
169  return $this->workspaceId;
170  }
171 
184  public function updateRefIndexTable($tableName, $uid, $testOnly = false)
185  {
186 
187  // First, secure that the index table is not updated with workspace tainted relations:
188  $this->WSOL = false;
189 
190  // Init:
191  $result = [
192  'keptNodes' => 0,
193  'deletedNodes' => 0,
194  'addedNodes' => 0
195  ];
196 
197  // If this table cannot contain relations, skip it
198  if (isset(static::$nonRelationTables[$tableName])) {
199  return $result;
200  }
201 
202  // Fetch tableRelationFields and save them in cache if not there yet
203  $cacheId = static::$cachePrefixTableRelationFields . $tableName;
204  if (!$this->runtimeCache->has($cacheId)) {
205  $tableRelationFields = $this->fetchTableRelationFields($tableName);
206  $this->runtimeCache->set($cacheId, $tableRelationFields);
207  } else {
208  $tableRelationFields = $this->runtimeCache->get($cacheId);
209  }
210 
211  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_refindex');
212 
213  // Get current index from Database with hash as index using $uidIndexField
214  // no restrictions are needed, since sys_refindex is not a TCA table
215  $queryBuilder = $connection->createQueryBuilder();
216  $queryBuilder->getRestrictions()->removeAll();
217  $queryResult = $queryBuilder->select('*')->from('sys_refindex')->where(
218  $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)),
219  $queryBuilder->expr()->eq('recuid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
220  $queryBuilder->expr()->eq(
221  'workspace',
222  $queryBuilder->createNamedParameter($this->getWorkspaceId(), \PDO::PARAM_INT)
223  )
224  )->execute();
225  $currentRelations = [];
226  while ($relation = $queryResult->fetch()) {
227  $currentRelations[$relation['hash']] = $currentRelations;
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  $connection->insert('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  $queryBuilder = $connection->createQueryBuilder();
269  $queryBuilder
270  ->delete('sys_refindex')
271  ->where(
272  $queryBuilder->expr()->in(
273  'hash',
274  $queryBuilder->createNamedParameter($hashList, Connection::PARAM_STR_ARRAY)
275  )
276  )
277  ->execute();
278  }
279  }
280  }
281 
282  return $result;
283  }
284 
293  public function generateRefIndexData($tableName, $uid)
294  {
295  if (!isset($GLOBALS['TCA'][$tableName])) {
296  return null;
297  }
298 
299  $this->relations = [];
300 
301  // Fetch tableRelationFields and save them in cache if not there yet
302  $cacheId = static::$cachePrefixTableRelationFields . $tableName;
303  if (!$this->runtimeCache->has($cacheId)) {
304  $tableRelationFields = $this->fetchTableRelationFields($tableName);
305  $this->runtimeCache->set($cacheId, $tableRelationFields);
306  } else {
307  $tableRelationFields = $this->runtimeCache->get($cacheId);
308  }
309 
310  // Return if there are no fields which could contain relations
311  if ($tableRelationFields === '') {
312  return $this->relations;
313  }
314 
315  $deleteField = $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
316 
317  if ($tableRelationFields === '*') {
318  // If one field of a record is of type flex, all fields have to be fetched
319  // to be passed to FlexFormTools->getDataStructureIdentifier()
320  $selectFields = '*';
321  } else {
322  // otherwise only fields that might contain relations are fetched
323  $selectFields = 'uid,' . $tableRelationFields . ($deleteField ? ',' . $deleteField : '');
324  }
325 
326  // Get raw record from DB
327  $record = BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid, $selectFields);
328  if (!is_array($record)) {
329  return null;
330  }
331 
332  // Deleted:
333  $deleted = $deleteField && $record[$deleteField] ? 1 : 0;
334 
335  // Get all relations from record:
336  $recordRelations = $this->getRelations($tableName, $record);
337  // Traverse those relations, compile records to insert in table:
338  foreach ($recordRelations as $fieldName => $fieldRelations) {
339  // Based on type
340  switch ((string)$fieldRelations['type']) {
341  case 'db':
342  $this->createEntryData_dbRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['itemArray']);
343  break;
344  case 'file_reference':
345  // not used (see getRelations()), but fallback to file
346  case 'file':
347  $this->createEntryData_fileRels($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['newValueFiles']);
348  break;
349  case 'flex':
350  // DB references in FlexForms
351  if (is_array($fieldRelations['flexFormRels']['db'])) {
352  foreach ($fieldRelations['flexFormRels']['db'] as $flexPointer => $subList) {
353  $this->createEntryData_dbRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
354  }
355  }
356  // File references in FlexForms
357  // @todo #65463 Test correct handling of file references in FlexForms
358  if (is_array($fieldRelations['flexFormRels']['file'])) {
359  foreach ($fieldRelations['flexFormRels']['file'] as $flexPointer => $subList) {
360  $this->createEntryData_fileRels($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList);
361  }
362  }
363  // Soft references in FlexForms
364  // @todo #65464 Test correct handling of soft references in FlexForms
365  if (is_array($fieldRelations['flexFormRels']['softrefs'])) {
366  foreach ($fieldRelations['flexFormRels']['softrefs'] as $flexPointer => $subList) {
367  $this->createEntryData_softreferences($tableName, $uid, $fieldName, $flexPointer, $deleted, $subList['keys']);
368  }
369  }
370  break;
371  }
372  // Soft references in the field
373  if (is_array($fieldRelations['softrefs'])) {
374  $this->createEntryData_softreferences($tableName, $uid, $fieldName, '', $deleted, $fieldRelations['softrefs']['keys']);
375  }
376  }
377 
378  return $this->relations;
379  }
380 
398  public function createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string = '', $sort = -1, $softref_key = '', $softref_id = '')
399  {
400  if (BackendUtility::isTableWorkspaceEnabled($table)) {
401  $element = BackendUtility::getRecord($table, $uid, 't3ver_wsid');
402  if ($element !== null && isset($element['t3ver_wsid']) && (int)$element['t3ver_wsid'] !== $this->getWorkspaceId()) {
403  //The given Element is ws-enabled but doesn't live in the selected workspace
404  // => don't add index as it's not actually there
405  return false;
406  }
407  }
408  return [
409  'tablename' => $table,
410  'recuid' => $uid,
411  'field' => $field,
412  'flexpointer' => $flexPointer,
413  'softref_key' => $softref_key,
414  'softref_id' => $softref_id,
415  'sorting' => $sort,
416  'deleted' => $deleted,
417  'workspace' => $this->getWorkspaceId(),
418  'ref_table' => $ref_table,
419  'ref_uid' => $ref_uid,
420  'ref_string' => $ref_string
421  ];
422  }
423 
435  public function createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
436  {
437  foreach ($items as $sort => $i) {
438  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $i['table'], $i['id'], '', $sort);
439  }
440  }
441 
453  public function createEntryData_fileRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
454  {
455  foreach ($items as $sort => $i) {
456  $filePath = $i['ID_absFile'];
457  if (GeneralUtility::isFirstPartOfStr($filePath, PATH_site)) {
458  $filePath = PathUtility::stripPathSitePrefix($filePath);
459  }
460  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $filePath, $sort);
461  }
462  }
463 
475  public function createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
476  {
477  if (is_array($keys)) {
478  foreach ($keys as $spKey => $elements) {
479  if (is_array($elements)) {
480  foreach ($elements as $subKey => $el) {
481  if (is_array($el['subst'])) {
482  switch ((string)$el['subst']['type']) {
483  case 'db':
484  list($tableName, $recordId) = explode(':', $el['subst']['recordRef']);
485  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, $tableName, $recordId, '', -1, $spKey, $subKey);
486  break;
487  case 'file_reference':
488  // not used (see getRelations()), but fallback to file
489  case 'file':
490  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_FILE', 0, $el['subst']['relFileName'], -1, $spKey, $subKey);
491  break;
492  case 'string':
493  $this->relations[] = $this->createEntryData($table, $uid, $fieldName, $flexPointer, $deleted, '_STRING', 0, $el['subst']['tokenValue'], -1, $spKey, $subKey);
494  break;
495  }
496  }
497  }
498  }
499  }
500  }
501  }
502 
503  /*******************************
504  *
505  * Get relations from table row
506  *
507  *******************************/
508 
520  public function getRelations($table, $row, $onlyField = '')
521  {
522  // Initialize:
523  $uid = $row['uid'];
524  $outRow = [];
525  foreach ($row as $field => $value) {
526  if (!isset(static::$nonRelationFields[$field]) && is_array($GLOBALS['TCA'][$table]['columns'][$field]) && (!$onlyField || $onlyField === $field)) {
527  $conf = $GLOBALS['TCA'][$table]['columns'][$field]['config'];
528  // Add files
529  $resultsFromFiles = $this->getRelations_procFiles($value, $conf, $uid);
530  if (!empty($resultsFromFiles)) {
531  // We have to fill different arrays here depending on the result.
532  // internal_type file is still a relation of type file and
533  // since http://forge.typo3.org/issues/49538 internal_type file_reference
534  // is a database relation to a sys_file record
535  $fileResultsFromFiles = [];
536  $dbResultsFromFiles = [];
537  foreach ($resultsFromFiles as $resultFromFiles) {
538  if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
539  $dbResultsFromFiles[] = $resultFromFiles;
540  } else {
541  // Creates an entry for the field with all the files:
542  $fileResultsFromFiles[] = $resultFromFiles;
543  }
544  }
545  if (!empty($fileResultsFromFiles)) {
546  $outRow[$field] = [
547  'type' => 'file',
548  'newValueFiles' => $fileResultsFromFiles
549  ];
550  }
551  if (!empty($dbResultsFromFiles)) {
552  $outRow[$field] = [
553  'type' => 'db',
554  'itemArray' => $dbResultsFromFiles
555  ];
556  }
557  }
558  // Add a softref definition for link fields if the TCA does not specify one already
559  if ($conf['type'] === 'input' && isset($conf['wizards']['link']) && empty($conf['softref'])) {
560  $conf['softref'] = 'typolink';
561  }
562  // Add DB:
563  $resultsFromDatabase = $this->getRelations_procDB($value, $conf, $uid, $table, $field);
564  if (!empty($resultsFromDatabase)) {
565  // Create an entry for the field with all DB relations:
566  $outRow[$field] = [
567  'type' => 'db',
568  'itemArray' => $resultsFromDatabase
569  ];
570  }
571  // For "flex" fieldtypes we need to traverse the structure looking for file and db references of course!
572  if ($conf['type'] === 'flex') {
573  // Get current value array:
574  // NOTICE: failure to resolve Data Structures can lead to integrity problems with the reference index. Please look up
575  // the note in the JavaDoc documentation for the function FlexFormTools->getDataStructureIdentifier()
576  $currentValueArray = GeneralUtility::xml2array($value);
577  // Traversing the XML structure, processing files:
578  if (is_array($currentValueArray)) {
579  $this->temp_flexRelations = [
580  'db' => [],
581  'file' => [],
582  'softrefs' => []
583  ];
584  // Create and call iterator object:
585  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
586  $flexFormTools->traverseFlexFormXMLData($table, $field, $row, $this, 'getRelations_flexFormCallBack');
587  // Create an entry for the field:
588  $outRow[$field] = [
589  'type' => 'flex',
590  'flexFormRels' => $this->temp_flexRelations
591  ];
592  }
593  }
594  // Soft References:
595  if ((string)$value !== '') {
596  $softRefValue = $value;
597  $softRefs = BackendUtility::explodeSoftRefParserList($conf['softref']);
598  if ($softRefs !== false) {
599  foreach ($softRefs as $spKey => $spParams) {
600  $softRefObj = BackendUtility::softRefParserObj($spKey);
601  if (is_object($softRefObj)) {
602  $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams);
603  if (is_array($resultArray)) {
604  $outRow[$field]['softrefs']['keys'][$spKey] = $resultArray['elements'];
605  if ((string)$resultArray['content'] !== '') {
606  $softRefValue = $resultArray['content'];
607  }
608  }
609  }
610  }
611  }
612  if (!empty($outRow[$field]['softrefs']) && (string)$value !== (string)$softRefValue && strpos($softRefValue, '{softref:') !== false) {
613  $outRow[$field]['softrefs']['tokenizedContent'] = $softRefValue;
614  }
615  }
616  }
617  }
618  return $outRow;
619  }
620 
632  public function getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $parentObject)
633  {
634  // Removing "data/" in the beginning of path (which points to location in data array)
635  $structurePath = substr($structurePath, 5) . '/';
636  $dsConf = $dsArr['TCEforms']['config'];
637  // Implode parameter values:
638  list($table, $uid, $field) = [
639  $PA['table'],
640  $PA['uid'],
641  $PA['field']
642  ];
643  // Add files
644  $resultsFromFiles = $this->getRelations_procFiles($dataValue, $dsConf, $uid);
645  if (!empty($resultsFromFiles)) {
646  // We have to fill different arrays here depending on the result.
647  // internal_type file is still a relation of type file and
648  // since http://forge.typo3.org/issues/49538 internal_type file_reference
649  // is a database relation to a sys_file record
650  $fileResultsFromFiles = [];
651  $dbResultsFromFiles = [];
652  foreach ($resultsFromFiles as $resultFromFiles) {
653  if (isset($resultFromFiles['table']) && $resultFromFiles['table'] === 'sys_file') {
654  $dbResultsFromFiles[] = $resultFromFiles;
655  } else {
656  $fileResultsFromFiles[] = $resultFromFiles;
657  }
658  }
659  if (!empty($fileResultsFromFiles)) {
660  $this->temp_flexRelations['file'][$structurePath] = $fileResultsFromFiles;
661  }
662  if (!empty($dbResultsFromFiles)) {
663  $this->temp_flexRelations['db'][$structurePath] = $dbResultsFromFiles;
664  }
665  }
666  // Add a softref definition for link fields if the TCA does not specify one already
667  if ($dsConf['type'] === 'input' && isset($dsConf['wizards']['link']) && empty($dsConf['softref'])) {
668  $dsConf['softref'] = 'typolink';
669  }
670  // Add DB:
671  $resultsFromDatabase = $this->getRelations_procDB($dataValue, $dsConf, $uid, $table, $field);
672  if (!empty($resultsFromDatabase)) {
673  // Create an entry for the field with all DB relations:
674  $this->temp_flexRelations['db'][$structurePath] = $resultsFromDatabase;
675  }
676  // Soft References:
677  if (is_array($dataValue) || (string)$dataValue !== '') {
678  $softRefValue = $dataValue;
679  $softRefs = BackendUtility::explodeSoftRefParserList($dsConf['softref']);
680  if ($softRefs !== false) {
681  foreach ($softRefs as $spKey => $spParams) {
682  $softRefObj = BackendUtility::softRefParserObj($spKey);
683  if (is_object($softRefObj)) {
684  $resultArray = $softRefObj->findRef($table, $field, $uid, $softRefValue, $spKey, $spParams, $structurePath);
685  if (is_array($resultArray) && is_array($resultArray['elements'])) {
686  $this->temp_flexRelations['softrefs'][$structurePath]['keys'][$spKey] = $resultArray['elements'];
687  if ((string)$resultArray['content'] !== '') {
688  $softRefValue = $resultArray['content'];
689  }
690  }
691  }
692  }
693  }
694  if (!empty($this->temp_flexRelations['softrefs']) && (string)$dataValue !== (string)$softRefValue) {
695  $this->temp_flexRelations['softrefs'][$structurePath]['tokenizedContent'] = $softRefValue;
696  }
697  }
698  }
699 
708  public function getRelations_procFiles($value, $conf, $uid)
709  {
710  if ($conf['type'] !== 'group' || ($conf['internal_type'] !== 'file' && $conf['internal_type'] !== 'file_reference')) {
711  return false;
712  }
713 
714  // Collect file values in array:
715  if ($conf['MM']) {
716  $theFileValues = [];
717  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
718  $dbAnalysis->start('', 'files', $conf['MM'], $uid);
719  foreach ($dbAnalysis->itemArray as $someval) {
720  if ($someval['id']) {
721  $theFileValues[] = $someval['id'];
722  }
723  }
724  } else {
725  $theFileValues = explode(',', $value);
726  }
727  // Traverse the files and add them:
728  $uploadFolder = $conf['internal_type'] === 'file' ? $conf['uploadfolder'] : '';
729  $destinationFolder = $this->destPathFromUploadFolder($uploadFolder);
730  $newValueFiles = [];
731  foreach ($theFileValues as $file) {
732  if (trim($file)) {
733  $realFile = $destinationFolder . '/' . trim($file);
734  $newValueFile = [
735  'filename' => basename($file),
736  'ID' => md5($realFile),
737  'ID_absFile' => $realFile
738  ];
739  // Set sys_file and id for referenced files
740  if ($conf['internal_type'] === 'file_reference') {
741  try {
742  $file = ResourceFactory::getInstance()->retrieveFileOrFolderObject($file);
743  if ($file instanceof File || $file instanceof Folder) {
744  // For setting this as sys_file relation later, the keys filename, ID and ID_absFile
745  // have not to be included, because the are not evaluated for db relations.
746  $newValueFile = [
747  'table' => 'sys_file',
748  'id' => $file->getUid()
749  ];
750  }
751  } catch (\Exception $e) {
752  }
753  }
754  $newValueFiles[] = $newValueFile;
755  }
756  }
757  return $newValueFiles;
758  }
759 
770  public function getRelations_procDB($value, $conf, $uid, $table = '', $field = '')
771  {
772  // Get IRRE relations
773  if (empty($conf)) {
774  return false;
775  } elseif ($conf['type'] === 'inline' && !empty($conf['foreign_table']) && empty($conf['MM'])) {
776  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
777  $dbAnalysis->setUseLiveReferenceIds(false);
778  $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
779  return $dbAnalysis->itemArray;
780  // DB record lists:
781  } elseif ($this->isDbReferenceField($conf)) {
782  $allowedTables = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
783  if ($conf['MM_opposite_field']) {
784  return [];
785  }
786  $dbAnalysis = GeneralUtility::makeInstance(RelationHandler::class);
787  $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
788  return $dbAnalysis->itemArray;
789  }
790  return false;
791  }
792 
793  /*******************************
794  *
795  * Setting values
796  *
797  *******************************/
798 
818  public function setReferenceValue($hash, $newValue, $returnDataArray = false, $bypassWorkspaceAdminCheck = false)
819  {
820  $backendUser = $this->getBackendUser();
821  if ($backendUser->workspace === 0 && $backendUser->isAdmin() || $bypassWorkspaceAdminCheck) {
822  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
823  $queryBuilder->getRestrictions()->removeAll();
824 
825  // Get current index from Database
826  $referenceRecord = $queryBuilder
827  ->select('*')
828  ->from('sys_refindex')
829  ->where(
830  $queryBuilder->expr()->eq('hash', $queryBuilder->createNamedParameter($hash, \PDO::PARAM_STR))
831  )
832  ->setMaxResults(1)
833  ->execute()
834  ->fetch();
835 
836  // Check if reference existed.
837  if (!is_array($referenceRecord)) {
838  return 'ERROR: No reference record with hash="' . $hash . '" was found!';
839  }
840 
841  if (empty($GLOBALS['TCA'][$referenceRecord['tablename']])) {
842  return 'ERROR: Table "' . $referenceRecord['tablename'] . '" was not in TCA!';
843  }
844 
845  // Get that record from database
846  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
847  ->getQueryBuilderForTable($referenceRecord['tablename']);
848  $queryBuilder->getRestrictions()->removeAll();
849  $record = $queryBuilder
850  ->select('*')
851  ->from($referenceRecord['tablename'])
852  ->where(
853  $queryBuilder->expr()->eq(
854  'uid',
855  $queryBuilder->createNamedParameter($referenceRecord['recuid'], \PDO::PARAM_INT)
856  )
857  )
858  ->setMaxResults(1)
859  ->execute()
860  ->fetch();
861 
862  if (is_array($record)) {
863  // Get relation for single field from record
864  $recordRelations = $this->getRelations($referenceRecord['tablename'], $record, $referenceRecord['field']);
865  if ($fieldRelation = $recordRelations[$referenceRecord['field']]) {
866  // Initialize data array that is to be sent to DataHandler afterwards:
867  $dataArray = [];
868  // Based on type
869  switch ((string)$fieldRelation['type']) {
870  case 'db':
871  $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['itemArray'], $newValue, $dataArray);
872  if ($error) {
873  return $error;
874  }
875  break;
876  case 'file_reference':
877  // not used (see getRelations()), but fallback to file
878  case 'file':
879  $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['newValueFiles'], $newValue, $dataArray);
880  if ($error) {
881  return $error;
882  }
883  break;
884  case 'flex':
885  // DB references in FlexForms
886  if (is_array($fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']])) {
887  $error = $this->setReferenceValue_dbRels($referenceRecord, $fieldRelation['flexFormRels']['db'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
888  if ($error) {
889  return $error;
890  }
891  }
892  // File references in FlexForms
893  if (is_array($fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']])) {
894  $error = $this->setReferenceValue_fileRels($referenceRecord, $fieldRelation['flexFormRels']['file'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
895  if ($error) {
896  return $error;
897  }
898  }
899  // Soft references in FlexForms
900  if ($referenceRecord['softref_key'] && is_array($fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']]['keys'][$referenceRecord['softref_key']])) {
901  $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['flexFormRels']['softrefs'][$referenceRecord['flexpointer']], $newValue, $dataArray, $referenceRecord['flexpointer']);
902  if ($error) {
903  return $error;
904  }
905  }
906  break;
907  }
908  // Soft references in the field:
909  if ($referenceRecord['softref_key'] && is_array($fieldRelation['softrefs']['keys'][$referenceRecord['softref_key']])) {
910  $error = $this->setReferenceValue_softreferences($referenceRecord, $fieldRelation['softrefs'], $newValue, $dataArray);
911  if ($error) {
912  return $error;
913  }
914  }
915  // Data Array, now ready to be sent to DataHandler
916  if ($returnDataArray) {
917  return $dataArray;
918  } else {
919  // Execute CMD array:
920  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
921  $dataHandler->dontProcessTransformations = true;
922  $dataHandler->bypassWorkspaceRestrictions = true;
923  $dataHandler->bypassFileHandling = true;
924  // Otherwise this cannot update things in deleted records...
925  $dataHandler->bypassAccessCheckForRecords = true;
926  // Check has been done previously that there is a backend user which is Admin and also in live workspace
927  $dataHandler->start($dataArray, []);
928  $dataHandler->process_datamap();
929  // Return errors if any:
930  if (!empty($dataHandler->errorLog)) {
931  return LF . 'DataHandler:' . implode((LF . 'DataHandler:'), $dataHandler->errorLog);
932  }
933  }
934  }
935  }
936  } else {
937  return 'ERROR: BE_USER object is not admin OR not in workspace 0 (Live)';
938  }
939 
940  return false;
941  }
942 
953  public function setReferenceValue_dbRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
954  {
955  if ((int)$itemArray[$refRec['sorting']]['id'] === (int)$refRec['ref_uid'] && (string)$itemArray[$refRec['sorting']]['table'] === (string)$refRec['ref_table']) {
956  // Setting or removing value:
957  // Remove value:
958  if ($newValue === null) {
959  unset($itemArray[$refRec['sorting']]);
960  } else {
961  list($itemArray[$refRec['sorting']]['table'], $itemArray[$refRec['sorting']]['id']) = explode(':', $newValue);
962  }
963  // Traverse and compile new list of records:
964  $saveValue = [];
965  foreach ($itemArray as $pair) {
966  $saveValue[] = $pair['table'] . '_' . $pair['id'];
967  }
968  // Set in data array:
969  if ($flexPointer) {
970  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
971  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
972  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
973  } else {
974  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
975  }
976  } else {
977  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'] . '"';
978  }
979 
980  return false;
981  }
982 
993  public function setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer = '')
994  {
995  $ID_absFile = PathUtility::stripPathSitePrefix($itemArray[$refRec['sorting']]['ID_absFile']);
996  if ($ID_absFile === (string)$refRec['ref_string'] && $refRec['ref_table'] === '_FILE') {
997  // Setting or removing value:
998  // Remove value:
999  if ($newValue === null) {
1000  unset($itemArray[$refRec['sorting']]);
1001  } else {
1002  $itemArray[$refRec['sorting']]['filename'] = $newValue;
1003  }
1004  // Traverse and compile new list of records:
1005  $saveValue = [];
1006  foreach ($itemArray as $fileInfo) {
1007  $saveValue[] = $fileInfo['filename'];
1008  }
1009  // Set in data array:
1010  if ($flexPointer) {
1011  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
1012  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
1013  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], implode(',', $saveValue));
1014  } else {
1015  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = implode(',', $saveValue);
1016  }
1017  } else {
1018  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'] . '"';
1019  }
1020 
1021  return false;
1022  }
1023 
1034  public function setReferenceValue_softreferences($refRec, $softref, $newValue, &$dataArray, $flexPointer = '')
1035  {
1036  if (!is_array($softref['keys'][$refRec['softref_key']][$refRec['softref_id']])) {
1037  return 'ERROR: Soft reference parser key "' . $refRec['softref_key'] . '" or the index "' . $refRec['softref_id'] . '" was not found.';
1038  }
1039 
1040  // Set new value:
1041  $softref['keys'][$refRec['softref_key']][$refRec['softref_id']]['subst']['tokenValue'] = '' . $newValue;
1042  // Traverse softreferences and replace in tokenized content to rebuild it with new value inside:
1043  foreach ($softref['keys'] as $sfIndexes) {
1044  foreach ($sfIndexes as $data) {
1045  $softref['tokenizedContent'] = str_replace('{softref:' . $data['subst']['tokenID'] . '}', $data['subst']['tokenValue'], $softref['tokenizedContent']);
1046  }
1047  }
1048  // Set in data array:
1049  if (!strstr($softref['tokenizedContent'], '{softref:')) {
1050  if ($flexPointer) {
1051  $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
1052  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'] = [];
1053  $flexFormTools->setArrayValueByPath(substr($flexPointer, 0, -1), $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']]['data'], $softref['tokenizedContent']);
1054  } else {
1055  $dataArray[$refRec['tablename']][$refRec['recuid']][$refRec['field']] = $softref['tokenizedContent'];
1056  }
1057  } else {
1058  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.)';
1059  }
1060 
1061  return false;
1062  }
1063 
1064  /*******************************
1065  *
1066  * Helper functions
1067  *
1068  *******************************/
1069 
1076  protected function isDbReferenceField(array $configuration)
1077  {
1078  return
1079  ($configuration['type'] === 'group' && $configuration['internal_type'] === 'db')
1080  || (
1081  ($configuration['type'] === 'select' || $configuration['type'] === 'inline')
1082  && !empty($configuration['foreign_table'])
1083  )
1084  ;
1085  }
1086 
1093  public function isReferenceField(array $configuration)
1094  {
1095  return
1096  $this->isDbReferenceField($configuration)
1097  ||
1098  ($configuration['type'] === 'group' && ($configuration['internal_type'] === 'file' || $configuration['internal_type'] === 'file_reference')) // getRelations_procFiles
1099  ||
1100  ($configuration['type'] === 'input' && isset($configuration['wizards']['link'])) // getRelations_procDB
1101  ||
1102  $configuration['type'] === 'flex'
1103  ||
1104  isset($configuration['softref'])
1105  ;
1106  }
1107 
1114  protected function fetchTableRelationFields($tableName)
1115  {
1116  if (!isset($GLOBALS['TCA'][$tableName]['columns'])) {
1117  return '';
1118  }
1119 
1120  $fields = [];
1121 
1122  foreach ($GLOBALS['TCA'][$tableName]['columns'] as $field => $fieldDefinition) {
1123  if (is_array($fieldDefinition['config'])) {
1124  // Check for flex field
1125  if (isset($fieldDefinition['config']['type']) && $fieldDefinition['config']['type'] === 'flex') {
1126  // Fetch all fields if the is a field of type flex in the table definition because the complete row is passed to
1127  // FlexFormTools->getDataStructureIdentifier() in the end and might be needed in ds_pointerField or a hook
1128  return '*';
1129  }
1130  // Only fetch this field if it can contain a reference
1131  if ($this->isReferenceField($fieldDefinition['config'])) {
1132  $fields[] = $field;
1133  }
1134  }
1135  }
1136 
1137  return implode(',', $fields);
1138  }
1139 
1146  public function destPathFromUploadFolder($folder)
1147  {
1148  if (!$folder) {
1149  return substr(PATH_site, 0, -1);
1150  }
1151  return PATH_site . $folder;
1152  }
1153 
1161  public function updateIndex($testOnly, $cli_echo = false)
1162  {
1163  $errors = [];
1164  $tableNames = [];
1165  $recCount = 0;
1166  $tableCount = 0;
1167  $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, remove the "--check" argument)' : 'Reference Index being Updated';
1168  if ($cli_echo) {
1169  echo '*******************************************' . LF . $headerContent . LF . '*******************************************' . LF;
1170  }
1171  // Traverse all tables:
1172  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1173  foreach ($GLOBALS['TCA'] as $tableName => $cfg) {
1174  if (isset(static::$nonRelationTables[$tableName])) {
1175  continue;
1176  }
1177  $fields = ['uid'];
1178  if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
1179  $fields[] = 't3ver_wsid';
1180  }
1181  // Traverse all records in tables, including deleted records
1182  $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
1183  $queryBuilder->getRestrictions()->removeAll();
1184  try {
1185  $queryResult = $queryBuilder
1186  ->select(...$fields)
1187  ->from($tableName)
1188  ->execute();
1189  } catch (DBALException $e) {
1190  // Table exists in $TCA but does not exist in the database
1191  // @todo: improve / change message and add actual sql error?
1192  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);
1193  continue;
1194  }
1195 
1196  $tableNames[] = $tableName;
1197  $tableCount++;
1198  $uidList = [0];
1199  while ($record = $queryResult->fetch()) {
1200  $refIndexObj = GeneralUtility::makeInstance(self::class);
1201  if (isset($record['t3ver_wsid'])) {
1202  $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
1203  }
1204  $result = $refIndexObj->updateRefIndexTable($tableName, $record['uid'], $testOnly);
1205  $uidList[] = $record['uid'];
1206  $recCount++;
1207  if ($result['addedNodes'] || $result['deletedNodes']) {
1208  $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
1209  $errors[] = $error;
1210  if ($cli_echo) {
1211  echo $error . LF;
1212  }
1213  }
1214  }
1215 
1216  // Searching for lost indexes for this table
1217  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1218  $queryBuilder->getRestrictions()->removeAll();
1219  $lostIndexes = $queryBuilder
1220  ->count('hash')
1221  ->from('sys_refindex')
1222  ->where(
1223  $queryBuilder->expr()->eq(
1224  'tablename',
1225  $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
1226  ),
1227  $queryBuilder->expr()->notIn(
1228  'recuid',
1229  $queryBuilder->createNamedParameter($uidList, Connection::PARAM_INT_ARRAY)
1230  )
1231  )
1232  ->execute()
1233  ->fetchColumn(0);
1234 
1235  if ($lostIndexes > 0) {
1236  $error = 'Table ' . $tableName . ' has ' . $lostIndexes . ' lost indexes which are now deleted';
1237  $errors[] = $error;
1238  if ($cli_echo) {
1239  echo $error . LF;
1240  }
1241  if (!$testOnly) {
1242  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1243  $queryBuilder->delete('sys_refindex')
1244  ->where(
1245  $queryBuilder->expr()->eq(
1246  'tablename',
1247  $queryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
1248  ),
1249  $queryBuilder->expr()->notIn(
1250  'recuid',
1251  $queryBuilder->createNamedParameter($uidList, Connection::PARAM_INT_ARRAY)
1252  )
1253  )->execute();
1254  }
1255  }
1256  }
1257 
1258  // Searching lost indexes for non-existing tables
1259  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1260  $queryBuilder->getRestrictions()->removeAll();
1261  $lostTables = $queryBuilder
1262  ->count('hash')
1263  ->from('sys_refindex')
1264  ->where(
1265  $queryBuilder->expr()->notIn(
1266  'tablename',
1267  $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
1268  )
1269  )->execute()
1270  ->fetchColumn(0);
1271 
1272  if ($lostTables > 0) {
1273  $error = 'Index table hosted ' . $lostTables . ' indexes for non-existing tables, now removed';
1274  $errors[] = $error;
1275  if ($cli_echo) {
1276  echo $error . LF;
1277  }
1278  if (!$testOnly) {
1279  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
1280  $queryBuilder->delete('sys_refindex')
1281  ->where(
1282  $queryBuilder->expr()->notIn(
1283  'tablename',
1284  $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
1285  )
1286  )->execute();
1287  }
1288  }
1289  $errorCount = count($errors);
1290  $recordsCheckedString = $recCount . ' records from ' . $tableCount . ' tables were checked/updated.' . LF;
1291  $flashMessage = GeneralUtility::makeInstance(
1292  FlashMessage::class,
1293  $errorCount ? implode('##LF##', $errors) : 'Index Integrity was perfect!',
1294  $recordsCheckedString,
1295  $errorCount ? FlashMessage::ERROR : FlashMessage::OK
1296  );
1298  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1300  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1301  $defaultFlashMessageQueue->enqueue($flashMessage);
1302  $bodyContent = $defaultFlashMessageQueue->renderFlashMessages();
1303  if ($cli_echo) {
1304  echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
1305  }
1306  if (!$testOnly) {
1307  $registry = GeneralUtility::makeInstance(Registry::class);
1308  $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
1309  }
1310  return [$headerContent, $bodyContent, $errorCount];
1311  }
1312 
1318  protected function getBackendUser()
1319  {
1320  return $GLOBALS['BE_USER'];
1321  }
1322 }
createEntryData_softreferences($table, $uid, $fieldName, $flexPointer, $deleted, $keys)
setReferenceValue($hash, $newValue, $returnDataArray=false, $bypassWorkspaceAdminCheck=false)
getRelations_flexFormCallBack($dsArr, $dataValue, $PA, $structurePath, $parentObject)
static isFirstPartOfStr($str, $partStr)
static xml2array($string, $NSprefix= '', $reportDocTag=false)
setReferenceValue_fileRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer= '')
createEntryData($table, $uid, $field, $flexPointer, $deleted, $ref_table, $ref_uid, $ref_string= '', $sort=-1, $softref_key= '', $softref_id= '')
setReferenceValue_dbRels($refRec, $itemArray, $newValue, &$dataArray, $flexPointer= '')
getRelations($table, $row, $onlyField= '')
createEntryData_dbRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)
getRelations_procDB($value, $conf, $uid, $table= '', $field= '')
updateRefIndexTable($tableName, $uid, $testOnly=false)
setReferenceValue_softreferences($refRec, $softref, $newValue, &$dataArray, $flexPointer= '')
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
createEntryData_fileRels($table, $uid, $fieldName, $flexPointer, $deleted, $items)