TYPO3 CMS  TYPO3_6-2
RelationHandler.php
Go to the documentation of this file.
1 <?php
3 
22 
31 
37  protected $fetchAllFields = FALSE;
38 
44 
52  public $tableArray = array();
53 
60  public $itemArray = array();
61 
68  public $nonTableArray = array();
69 
74  public $additionalWhere = array();
75 
82  public $checkIfDeleted = TRUE;
83 
88  public $dbPaths = array();
89 
96  public $firstTable = '';
97 
104  public $secondTable = '';
105 
113  public $MM_is_foreign = FALSE;
114 
121  public $MM_oppositeField = '';
122 
129  public $MM_oppositeTable = '';
130 
138 
146 
154 
163 
171  public $MM_match_fields = array();
172 
180 
187  public $MM_insert_fields = array();
188 
195  public $MM_table_where = '';
196 
202  protected $MM_oppositeUsage;
203 
207  protected $updateReferenceIndex = TRUE;
208 
212  protected $useLiveParentIds = TRUE;
213 
217  protected $useLiveReferenceIds = TRUE;
218 
222  protected $workspaceId;
223 
227  protected $purged = FALSE;
228 
234  public $results = array();
235 
241  public function getWorkspaceId() {
242  if (!isset($this->workspaceId)) {
243  $this->workspaceId = (int)$GLOBALS['BE_USER']->workspace;
244  }
245  return $this->workspaceId;
246  }
247 
253  public function setWorkspaceId($workspaceId) {
254  $this->workspaceId = (int)$workspaceId;
255  }
256 
262  public function isPurged() {
263  return $this->purged;
264  }
265 
278  public function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array()) {
279  $conf = (array)$conf;
280  // SECTION: MM reverse relations
281  $this->MM_is_foreign = (boolean)$conf['MM_opposite_field'];
282  $this->MM_oppositeField = $conf['MM_opposite_field'];
283  $this->MM_table_where = $conf['MM_table_where'];
284  $this->MM_hasUidField = $conf['MM_hasUidField'];
285  $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : array();
286  $this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
287  $this->currentTable = $currentTable;
288  if (!empty($conf['MM_oppositeUsage']) && is_array($conf['MM_oppositeUsage'])) {
289  $this->MM_oppositeUsage = $conf['MM_oppositeUsage'];
290  }
291  if ($this->MM_is_foreign) {
292  $tmp = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
293  // Normally, $conf['allowed'] can contain a list of tables,
294  // but as we are looking at a MM relation from the foreign side,
295  // it only makes sense to allow one one table in $conf['allowed']
296  $tmp = GeneralUtility::trimExplode(',', $tmp);
297  $this->MM_oppositeTable = $tmp[0];
298  unset($tmp);
299  // Only add the current table name if there is more than one allowed field
300  // We must be sure this has been done at least once before accessing the "columns" part of TCA for a table.
301  $this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config'];
302  if ($this->MM_oppositeFieldConf['allowed']) {
303  $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
304  if (count($oppositeFieldConf_allowed) > 1 || $this->MM_oppositeFieldConf['allowed'] === '*') {
305  $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
306  }
307  }
308  }
309  // SECTION: normal MM relations
310  // If the table list is "*" then all tables are used in the list:
311  if (trim($tablelist) === '*') {
312  $tablelist = implode(',', array_keys($GLOBALS['TCA']));
313  }
314  // The tables are traversed and internal arrays are initialized:
315  $tempTableArray = GeneralUtility::trimExplode(',', $tablelist, TRUE);
316  foreach ($tempTableArray as $val) {
317  $tName = trim($val);
318  $this->tableArray[$tName] = array();
319  if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
320  $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
321  $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
322  }
323  }
324  if (is_array($this->tableArray)) {
325  reset($this->tableArray);
326  } else {
327  // No tables
328  return;
329  }
330  // Set first and second tables:
331  // Is the first table
332  $this->firstTable = key($this->tableArray);
333  next($this->tableArray);
334  // If the second table is set and the ID number is less than zero (later)
335  // then the record is regarded to come from the second table...
336  $this->secondTable = key($this->tableArray);
337  // Now, populate the internal itemArray and tableArray arrays:
338  // If MM, then call this function to do that:
339  if ($MMtable) {
340  if ($MMuid) {
341  $this->readMM($MMtable, $MMuid);
342  $this->purgeItemArray();
343  } else {
344  // Revert to readList() for new records in order to load possible default values from $itemlist
345  $this->readList($itemlist, $conf);
346  $this->purgeItemArray();
347  }
348  } elseif ($MMuid && $conf['foreign_field']) {
349  // If not MM but foreign_field, the read the records by the foreign_field
350  $this->readForeignField($MMuid, $conf);
351  } else {
352  // If not MM, then explode the itemlist by "," and traverse the list:
353  $this->readList($itemlist, $conf);
354  // Do automatic default_sortby, if any
355  if ($conf['foreign_default_sortby']) {
356  $this->sortList($conf['foreign_default_sortby']);
357  }
358  }
359  }
360 
369  public function __set($name, $value) {
370  if($name === 'fromTC') {
372  '$fromTC is protected since TYPO3 6.1. Use setFetchAllFields() instead!'
373  );
374  $this->setFetchAllFields(!$value);
375  }
376  }
377 
383  public function setFetchAllFields($allFields) {
384  $this->fetchAllFields = (boolean)$allFields;
385  }
386 
394  $this->updateReferenceIndex = (boolean)$updateReferenceIndex;
395  }
396 
401  $this->useLiveParentIds = (bool)$useLiveParentIds;
402  }
403 
408  $this->useLiveReferenceIds = (bool)$useLiveReferenceIds;
409  }
410 
419  public function readList($itemlist, array $configuration) {
420  if ((string) trim($itemlist) != '') {
421  $tempItemArray = GeneralUtility::trimExplode(',', $itemlist);
422  // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work
423  // if there were spaces in the list... I suppose this is better overall...
424  foreach ($tempItemArray as $key => $val) {
425  // Will be set to "1" if the entry was a real table/id:
426  $isSet = 0;
427  // Extract table name and id. This is un the formular [tablename]_[id]
428  // where table name MIGHT contain "_", hence the reversion of the string!
429  $val = strrev($val);
430  $parts = explode('_', $val, 2);
431  $theID = strrev($parts[0]);
432  // Check that the id IS an integer:
434  // Get the table name: If a part of the exploded string, use that.
435  // Otherwise if the id number is LESS than zero, use the second table, otherwise the first table
436  $theTable = trim($parts[1])
437  ? strrev(trim($parts[1]))
438  : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
439  // If the ID is not blank and the table name is among the names in the inputted tableList
440  if (((string) $theID != '' && $theID) && $theTable && isset($this->tableArray[$theTable])) {
441  // Get ID as the right value:
442  $theID = $this->secondTable ? abs((int)$theID) : (int)$theID;
443  // Register ID/table name in internal arrays:
444  $this->itemArray[$key]['id'] = $theID;
445  $this->itemArray[$key]['table'] = $theTable;
446  $this->tableArray[$theTable][] = $theID;
447  // Set update-flag:
448  $isSet = 1;
449  }
450  }
451  // If it turns out that the value from the list was NOT a valid reference to a table-record,
452  // then we might still set it as a NO_TABLE value:
453  if (!$isSet && $this->registerNonTableValues) {
454  $this->itemArray[$key]['id'] = $tempItemArray[$key];
455  $this->itemArray[$key]['table'] = '_NO_TABLE';
456  $this->nonTableArray[] = $tempItemArray[$key];
457  }
458  }
459 
460  // Skip if not dealing with IRRE in a CSV list on a workspace
461  if ($configuration['type'] !== 'inline' || empty($configuration['foreign_table']) || !empty($configuration['foreign_field'])
462  || !empty($configuration['MM']) || count($this->tableArray) !== 1 || empty($this->tableArray[$configuration['foreign_table']])
463  || (int)$GLOBALS['BE_USER']->workspace === 0 || !BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])) {
464  return;
465  }
466 
467  // Fetch live record data
468  if ($this->useLiveReferenceIds) {
469  foreach ($this->itemArray as &$item) {
470  $item['id'] = $this->getLiveDefaultId($item['table'], $item['id']);
471  }
472  // Directly overlay workspace data
473  } else {
474  $this->itemArray = array();
475  $foreignTable = $configuration['foreign_table'];
476  $ids = $this->getResolver($foreignTable, $this->tableArray[$foreignTable])->get();
477  foreach ($ids as $id) {
478  $this->itemArray[] = array(
479  'id' => $id,
480  'table' => $foreignTable,
481  );
482  }
483  }
484  }
485  }
486 
496  public function sortList($sortby) {
497  // Sort directly without fetching addional data
498  if ($sortby == 'uid') {
499  usort(
500  $this->itemArray,
501  function ($a, $b) {
502  return $a['id'] < $b['id'] ? -1 : 1;
503  }
504  );
505  } elseif (count($this->tableArray) == 1) {
506  reset($this->tableArray);
507  $table = key($this->tableArray);
508  $uidList = implode(',', current($this->tableArray));
509  if ($uidList) {
510  $this->itemArray = array();
511  $this->tableArray = array();
512  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $uidList . ')', '', $sortby);
513  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
514  $this->itemArray[] = array('id' => $row['uid'], 'table' => $table);
515  $this->tableArray[$table][] = $row['uid'];
516  }
517  $GLOBALS['TYPO3_DB']->sql_free_result($res);
518  }
519  }
520  }
521 
531  public function readMM($tableName, $uid) {
532  $key = 0;
533  $additionalWhere = '';
534  $theTable = NULL;
535  // In case of a reverse relation
536  if ($this->MM_is_foreign) {
537  $uidLocal_field = 'uid_foreign';
538  $uidForeign_field = 'uid_local';
539  $sorting_field = 'sorting_foreign';
540  if ($this->MM_isMultiTableRelationship) {
541  $additionalWhere .= ' AND ( tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $tableName);
542  // Be backwards compatible! When allowing more than one table after
543  // having previously allowed only one table, this case applies.
544  if ($this->currentTable == $this->MM_isMultiTableRelationship) {
545  $additionalWhere .= ' OR tablenames=\'\'';
546  }
547  $additionalWhere .= ' ) ';
548  }
549  $theTable = $this->MM_oppositeTable;
550  } else {
551  // Default
552  $uidLocal_field = 'uid_local';
553  $uidForeign_field = 'uid_foreign';
554  $sorting_field = 'sorting';
555  }
556  if ($this->MM_table_where) {
557  $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
558  }
559  foreach ($this->MM_match_fields as $field => $value) {
560  $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
561  }
562  // Select all MM relations:
563  $where = $uidLocal_field . '=' . (int)$uid . $additionalWhere;
564  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $where, '', $sorting_field);
565  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
566  // Default
567  if (!$this->MM_is_foreign) {
568  // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
569  $theTable = $row['tablenames'] ?: $this->firstTable;
570  }
571  if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
572  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
573  $this->itemArray[$key]['table'] = $theTable;
574  $this->tableArray[$theTable][] = $row[$uidForeign_field];
575  } elseif ($this->registerNonTableValues) {
576  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
577  $this->itemArray[$key]['table'] = '_NO_TABLE';
578  $this->nonTableArray[] = $row[$uidForeign_field];
579  }
580  $key++;
581  }
582  $GLOBALS['TYPO3_DB']->sql_free_result($res);
583  }
584 
594  public function writeMM($MM_tableName, $uid, $prependTableName = FALSE) {
595  // In case of a reverse relation
596  if ($this->MM_is_foreign) {
597  $uidLocal_field = 'uid_foreign';
598  $uidForeign_field = 'uid_local';
599  $sorting_field = 'sorting_foreign';
600  } else {
601  // default
602  $uidLocal_field = 'uid_local';
603  $uidForeign_field = 'uid_foreign';
604  $sorting_field = 'sorting';
605  }
606  // If there are tables...
607  $tableC = count($this->tableArray);
608  if ($tableC) {
609  // Boolean: does the field "tablename" need to be filled?
610  $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship ? 1 : 0;
611  $c = 0;
612  $additionalWhere_tablenames = '';
613  if ($this->MM_is_foreign && $prep) {
614  $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
615  }
616  $additionalWhere = '';
617  // Add WHERE clause if configured
618  if ($this->MM_table_where) {
619  $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
620  }
621  // Select, update or delete only those relations that match the configured fields
622  foreach ($this->MM_match_fields as $field => $value) {
623  $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
624  }
625  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
626  $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
627  $MM_tableName,
628  $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
629  '',
630  $sorting_field
631  );
632  $oldMMs = array();
633  // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField).
634  // If the UID is present it will be used to update sorting and delete MM-records.
635  // This is necessary if the "multiple" feature is used for the MM relations.
636  // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
637  $oldMMs_inclUid = array();
638  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
639  if (!$this->MM_is_foreign && $prep) {
640  $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
641  } else {
642  $oldMMs[] = $row[$uidForeign_field];
643  }
644  $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
645  }
646  $GLOBALS['TYPO3_DB']->sql_free_result($res);
647  // For each item, insert it:
648  foreach ($this->itemArray as $val) {
649  $c++;
650  if ($prep || $val['table'] == '_NO_TABLE') {
651  // Insert current table if needed
652  if ($this->MM_is_foreign) {
653  $tablename = $this->currentTable;
654  } else {
655  $tablename = $val['table'];
656  }
657  } else {
658  $tablename = '';
659  }
660  if (!$this->MM_is_foreign && $prep) {
661  $item = array($val['table'], $val['id']);
662  } else {
663  $item = $val['id'];
664  }
665  if (in_array($item, $oldMMs)) {
666  $oldMMs_index = array_search($item, $oldMMs);
667  // In principle, selecting on the UID is all we need to do
668  // if a uid field is available since that is unique!
669  // But as long as it "doesn't hurt" we just add it to the where clause. It should all match up.
670  $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id']
671  . ($this->MM_hasUidField ? ' AND uid=' . (int)$oldMMs_inclUid[$oldMMs_index][2] : '');
672  if ($tablename) {
673  $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
674  }
675  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
676  // Remove the item from the $oldMMs array so after this
677  // foreach loop only the ones that need to be deleted are in there.
678  unset($oldMMs[$oldMMs_index]);
679  // Remove the item from the $oldMMs_inclUid array so after this
680  // foreach loop only the ones that need to be deleted are in there.
681  unset($oldMMs_inclUid[$oldMMs_index]);
682  } else {
683  $insertFields = $this->MM_insert_fields;
684  $insertFields[$uidLocal_field] = $uid;
685  $insertFields[$uidForeign_field] = $val['id'];
686  $insertFields[$sorting_field] = $c;
687  if ($tablename) {
688  $insertFields['tablenames'] = $tablename;
689  $insertFields = $this->completeOppositeUsageValues($tablename, $insertFields);
690  }
691  $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
692  if ($this->MM_is_foreign) {
693  $this->updateRefIndex($val['table'], $val['id']);
694  }
695  }
696  }
697  // Delete all not-used relations:
698  if (is_array($oldMMs) && count($oldMMs) > 0) {
699  $removeClauses = array();
700  $updateRefIndex_records = array();
701  foreach ($oldMMs as $oldMM_key => $mmItem) {
702  // If UID field is present, of course we need only use that for deleting.
703  if ($this->MM_hasUidField) {
704  $removeClauses[] = 'uid=' . (int)$oldMMs_inclUid[$oldMM_key][2];
705  } else {
706  if (is_array($mmItem)) {
707  $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName)
708  . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
709  } else {
710  $removeClauses[] = $uidForeign_field . '=' . $mmItem;
711  }
712  }
713  if ($this->MM_is_foreign) {
714  if (is_array($mmItem)) {
715  $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
716  } else {
717  $updateRefIndex_records[] = array($this->firstTable, $mmItem);
718  }
719  }
720  }
721  $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
722  $where = $uidLocal_field . '=' . (int)$uid . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere;
723  $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $where);
724  // Update ref index:
725  foreach ($updateRefIndex_records as $pair) {
726  $this->updateRefIndex($pair[0], $pair[1]);
727  }
728  }
729  // Update ref index; In tcemain it is not certain that this will happen because
730  // if only the MM field is changed the record itself is not updated and so the ref-index is not either.
731  // This could also have been fixed in updateDB in tcemain, however I decided to do it here ...
732  $this->updateRefIndex($this->currentTable, $uid);
733  }
734  }
735 
747  public function remapMM($MM_tableName, $uid, $newUid, $prependTableName = FALSE) {
748  // In case of a reverse relation
749  if ($this->MM_is_foreign) {
750  $uidLocal_field = 'uid_foreign';
751  } else {
752  // default
753  $uidLocal_field = 'uid_local';
754  }
755  // If there are tables...
756  $tableC = count($this->tableArray);
757  if ($tableC) {
758  // Boolean: does the field "tablename" need to be filled?
759  $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship;
760  $additionalWhere_tablenames = '';
761  if ($this->MM_is_foreign && $prep) {
762  $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
763  }
764  $additionalWhere = '';
765  // Add WHERE clause if configured
766  if ($this->MM_table_where) {
767  $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
768  }
769  // Select, update or delete only those relations that match the configured fields
770  foreach ($this->MM_match_fields as $field => $value) {
771  $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
772  }
773  $where = $uidLocal_field . '=' . (int)$uid . $additionalWhere_tablenames . $additionalWhere;
774  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $where, array($uidLocal_field => $newUid));
775  }
776  }
777 
787  public function readForeignField($uid, $conf) {
788  if ($this->useLiveParentIds) {
789  $uid = $this->getLiveDefaultId($this->currentTable, $uid);
790  }
791 
792  $key = 0;
793  $uid = (int)$uid;
794  $foreign_table = $conf['foreign_table'];
795  $foreign_table_field = $conf['foreign_table_field'];
796  $useDeleteClause = !$this->undeleteRecord;
797  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
798  // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
799  if ($conf['symmetric_field']) {
800  $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
801  } else {
802  $whereClause = $conf['foreign_field'] . '=' . $uid;
803  }
804  // Use the deleteClause (e.g. "deleted=0") on this table
805  if ($useDeleteClause) {
806  $whereClause .= BackendUtility::deleteClause($foreign_table);
807  }
808  // If it's requested to look for the parent uid AND the parent table,
809  // add an additional SQL-WHERE clause
810  if ($foreign_table_field && $this->currentTable) {
811  $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
812  }
813  // Add additional where clause if foreign_match_fields are defined
814  foreach ($foreign_match_fields as $field => $value) {
815  $whereClause .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $foreign_table);
816  }
817  // Select children from the live(!) workspace only
818  if (BackendUtility::isTableWorkspaceEnabled($foreign_table)) {
819  $workspaceList = '0,' . $this->getWorkspaceId();
820  $whereClause .= ' AND ' . $foreign_table . '.t3ver_wsid IN (' . $workspaceList . ') AND ' . $foreign_table . '.pid<>-1';
821  }
822  // Get the correct sorting field
823  // Specific manual sortby for data handled by this field
824  $sortby = '';
825  if ($conf['foreign_sortby']) {
826  if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
827  // Sorting depends on, from which side of the relation we're looking at it
828  $sortby = '
829  CASE
830  WHEN ' . $conf['foreign_field'] . '=' . $uid . '
831  THEN ' . $conf['foreign_sortby'] . '
832  ELSE ' . $conf['symmetric_sortby'] . '
833  END';
834  } else {
835  // Regular single-side behaviour
836  $sortby = $conf['foreign_sortby'];
837  }
838  } elseif ($conf['foreign_default_sortby']) {
839  // Specific default sortby for data handled by this field
840  $sortby = $conf['foreign_default_sortby'];
841  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
842  // Manual sortby for all table records
843  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
844  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) {
845  // Default sortby for all table records
846  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
847  }
848  // Strip a possible "ORDER BY" in front of the $sortby value
849  $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
850  // Get the rows from storage
851  $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby, '', 'uid');
852  if (count($rows)) {
853  $ids = $this->getResolver($foreign_table, array_keys($rows), $sortby)->get();
854  foreach ($ids as $id) {
855  $this->itemArray[$key]['id'] = $id;
856  $this->itemArray[$key]['table'] = $foreign_table;
857  $this->tableArray[$foreign_table][] = $id;
858  $key++;
859  }
860  }
861  }
862 
873  public function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
874  if ($this->useLiveParentIds) {
875  $parentUid = $this->getLiveDefaultId($this->currentTable, $parentUid);
876  if (!empty($updateToUid)) {
877  $updateToUid = $this->getLiveDefaultId($this->currentTable, $updateToUid);
878  }
879  }
880 
881  $c = 0;
882  $foreign_table = $conf['foreign_table'];
883  $foreign_field = $conf['foreign_field'];
884  $symmetric_field = $conf['symmetric_field'];
885  $foreign_table_field = $conf['foreign_table_field'];
886  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
887  // If there are table items and we have a proper $parentUid
888  if (MathUtility::canBeInterpretedAsInteger($parentUid) && count($this->tableArray)) {
889  // If updateToUid is not a positive integer, set it to '0', so it will be ignored
890  if (!(MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
891  $updateToUid = 0;
892  }
893  $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && BackendUtility::isTableWorkspaceEnabled($foreign_table));
894  $fields = 'uid,pid,' . $foreign_field;
895  // Consider the symmetric field if defined:
896  if ($symmetric_field) {
897  $fields .= ',' . $symmetric_field;
898  }
899  // Consider workspaces if defined and currently used:
900  if ($considerWorkspaces) {
901  $fields .= ',t3ver_wsid,t3ver_state,t3ver_oid';
902  }
903  // Update all items
904  foreach ($this->itemArray as $val) {
905  $uid = $val['id'];
906  $table = $val['table'];
907  $row = array();
908  // Fetch the current (not overwritten) relation record if we should handle symmetric relations
909  if ($symmetric_field || $considerWorkspaces) {
910  $row = BackendUtility::getRecord($table, $uid, $fields, '', TRUE);
911  if (empty($row)) {
912  continue;
913  }
914  }
915  $isOnSymmetricSide = FALSE;
916  if ($symmetric_field) {
917  $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
918  }
919  $updateValues = $foreign_match_fields;
920  // No update to the uid is requested, so this is the normal behaviour
921  // just update the fields and care about sorting
922  if (!$updateToUid) {
923  // Always add the pointer to the parent uid
924  if ($isOnSymmetricSide) {
925  $updateValues[$symmetric_field] = $parentUid;
926  } else {
927  $updateValues[$foreign_field] = $parentUid;
928  }
929  // If it is configured in TCA also to store the parent table in the child record, just do it
930  if ($foreign_table_field && $this->currentTable) {
931  $updateValues[$foreign_table_field] = $this->currentTable;
932  }
933  // Update sorting columns if not to be skipped
934  if (!$skipSorting) {
935  // Get the correct sorting field
936  // Specific manual sortby for data handled by this field
937  $sortby = '';
938  if ($conf['foreign_sortby']) {
939  $sortby = $conf['foreign_sortby'];
940  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
941  // manual sortby for all table records
942  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
943  }
944  // Apply sorting on the symmetric side
945  // (it depends on who created the relation, so what uid is in the symmetric_field):
946  if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
947  $sortby = $conf['symmetric_sortby'];
948  } else {
949  $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
950  }
951  if ($sortby) {
952  $updateValues[$sortby] = ++$c;
953  }
954  }
955  } else {
956  if ($isOnSymmetricSide) {
957  $updateValues[$symmetric_field] = $updateToUid;
958  } else {
959  $updateValues[$foreign_field] = $updateToUid;
960  }
961  }
962  // Update accordant fields in the database:
963  if (count($updateValues)) {
964  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateValues);
965  $this->updateRefIndex($table, $uid);
966  }
967  // Update accordant fields in the database for workspaces overlays/placeholders:
968  if ($considerWorkspaces) {
969  // It's the specific versioned record -> update placeholder (if any)
970  if (!empty($row['t3ver_oid']) && VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER_VERSION)) {
971  $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$row['t3ver_oid'], $updateValues);
972  }
973  }
974  }
975  }
976  }
977 
985  public function getValueArray($prependTableName = FALSE) {
986  // INIT:
987  $valueArray = array();
988  $tableC = count($this->tableArray);
989  // If there are tables in the table array:
990  if ($tableC) {
991  // If there are more than ONE table in the table array, then always prepend table names:
992  $prep = $tableC > 1 || $prependTableName;
993  // Traverse the array of items:
994  foreach ($this->itemArray as $val) {
995  $valueArray[] = ($prep && $val['table'] != '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id'];
996  }
997  }
998  // Return the array
999  return $valueArray;
1000  }
1001 
1011  public function convertPosNeg($valueArray, $fTable, $nfTable) {
1012  if (is_array($valueArray) && $fTable) {
1013  foreach ($valueArray as $key => $val) {
1014  $val = strrev($val);
1015  $parts = explode('_', $val, 2);
1016  $theID = strrev($parts[0]);
1017  $theTable = strrev($parts[1]);
1019  && (!$theTable || $theTable === (string)$fTable || $theTable === (string)$nfTable)
1020  ) {
1021  $valueArray[$key] = $theTable && $theTable !== (string)$fTable ? $theID * -1 : $theID;
1022  }
1023  }
1024  }
1025  return $valueArray;
1026  }
1027 
1037  public function getFromDB() {
1038  // Traverses the tables listed:
1039  foreach ($this->tableArray as $key => $val) {
1040  if (is_array($val)) {
1041  $itemList = implode(',', $val);
1042  if ($itemList) {
1043  if ($this->fetchAllFields) {
1044  $from = '*';
1045  } else {
1046  $from = 'uid,pid';
1047  if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
1048  // Titel
1049  $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label'];
1050  }
1051  if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
1052  // Alternative Title-Fields
1053  $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt'];
1054  }
1055  if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
1056  // Thumbnail
1057  $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail'];
1058  }
1059  }
1060  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
1061  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1062  $this->results[$key][$row['uid']] = $row;
1063  }
1064  $GLOBALS['TYPO3_DB']->sql_free_result($res);
1065  }
1066  }
1067  }
1068  return $this->results;
1069  }
1070 
1077  public function readyForInterface() {
1078  if (!is_array($this->itemArray)) {
1079  return FALSE;
1080  }
1081  $output = array();
1082  $titleLen = (int)$GLOBALS['BE_USER']->uc['titleLen'];
1083  foreach ($this->itemArray as $val) {
1084  $theRow = $this->results[$val['table']][$val['id']];
1085  if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
1086  $label = GeneralUtility::fixed_lgd_cs(strip_tags(
1087  BackendUtility::getRecordTitle($val['table'], $theRow)), $titleLen);
1088  $label = $label ? $label : '[...]';
1089  $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
1090  }
1091  }
1092  return implode(',', $output);
1093  }
1094 
1102  public function countItems($returnAsArray = TRUE) {
1103  $count = count($this->itemArray);
1104  if ($returnAsArray) {
1105  $count = array($count);
1106  }
1107  return $count;
1108  }
1109 
1120  public function updateRefIndex($table, $id) {
1121  $statisticsArray = array();
1122  if ($this->updateReferenceIndex) {
1124  $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
1126  $refIndexObj->setWorkspaceId($this->getWorkspaceId());
1127  }
1128  $statisticsArray = $refIndexObj->updateRefIndexTable($table, $id);
1129  }
1130  return $statisticsArray;
1131  }
1132 
1137  public function purgeItemArray($workspaceId = NULL) {
1138  if ($workspaceId === NULL) {
1139  $workspaceId = $this->getWorkspaceId();
1140  } else {
1141  $workspaceId = (int)$workspaceId;
1142  }
1143 
1144  // Ensure, only live relations are in the items Array
1145  if ($workspaceId === 0) {
1146  $purgeCallback = 'purgeVersionedIds';
1147  // Otherwise, ensure that live relations are purged if version exists
1148  } else {
1149  $purgeCallback = 'purgeLiveVersionedIds';
1150  }
1151 
1152  $itemArrayHasBeenPurged = $this->purgeItemArrayHandler($purgeCallback);
1153  $this->purged = ($this->purged || $itemArrayHasBeenPurged);
1154  return $itemArrayHasBeenPurged;
1155  }
1156 
1162  public function processDeletePlaceholder() {
1163  if (!$this->useLiveReferenceIds || $this->getWorkspaceId() === 0) {
1164  return FALSE;
1165  }
1166 
1167  return $this->purgeItemArrayHandler('purgeDeletePlaceholder');
1168  }
1169 
1176  protected function purgeItemArrayHandler($purgeCallback) {
1177  $itemArrayHasBeenPurged = FALSE;
1178 
1179  foreach ($this->tableArray as $itemTableName => $itemIds) {
1180  if (!count($itemIds) || !BackendUtility::isTableWorkspaceEnabled($itemTableName)) {
1181  continue;
1182  }
1183 
1184  $purgedItemIds = call_user_func(array($this, $purgeCallback), $itemTableName, $itemIds);
1185  $removedItemIds = array_diff($itemIds, $purgedItemIds);
1186  foreach ($removedItemIds as $removedItemId) {
1187  $this->removeFromItemArray($itemTableName, $removedItemId);
1188  }
1189  $this->tableArray[$itemTableName] = $purgedItemIds;
1190  if (count($removedItemIds)) {
1191  $itemArrayHasBeenPurged = TRUE;
1192  }
1193  }
1194 
1195  return $itemArrayHasBeenPurged;
1196  }
1197 
1205  protected function purgeVersionedIds($tableName, array $ids) {
1206  $ids = $this->getDatabaseConnection()->cleanIntArray($ids);
1207  $ids = array_combine($ids, $ids);
1208 
1209  $versions = $this->getDatabaseConnection()->exec_SELECTgetRows(
1210  'uid,t3ver_oid,t3ver_state',
1211  $tableName,
1212  'pid=-1 AND t3ver_oid IN (' . implode(',', $ids) . ') AND t3ver_wsid<>0',
1213  '',
1214  't3ver_state DESC'
1215  );
1216 
1217  if (!empty($versions)) {
1218  foreach ($versions as $version) {
1219  $versionId = $version['uid'];
1220  if (isset($ids[$versionId])) {
1221  unset($ids[$versionId]);
1222  }
1223  }
1224  }
1225 
1226  return array_values($ids);
1227  }
1228 
1236  protected function purgeLiveVersionedIds($tableName, array $ids) {
1237  $ids = $this->getDatabaseConnection()->cleanIntArray($ids);
1238  $ids = array_combine($ids, $ids);
1239 
1240  $versions = $this->getDatabaseConnection()->exec_SELECTgetRows(
1241  'uid,t3ver_oid,t3ver_state',
1242  $tableName,
1243  'pid=-1 AND t3ver_oid IN (' . implode(',', $ids) . ') AND t3ver_wsid<>0',
1244  '',
1245  't3ver_state DESC'
1246  );
1247 
1248  if (!empty($versions)) {
1249  foreach ($versions as $version) {
1250  $versionId = $version['uid'];
1251  $liveId = $version['t3ver_oid'];
1252  if (isset($ids[$liveId]) && isset($ids[$versionId])) {
1253  unset($ids[$liveId]);
1254  }
1255  }
1256  }
1257 
1258  return array_values($ids);
1259  }
1260 
1268  protected function purgeDeletePlaceholder($tableName, array $ids) {
1269  $ids = $this->getDatabaseConnection()->cleanIntArray($ids);
1270  $ids = array_combine($ids, $ids);
1271 
1272  $versions = $this->getDatabaseConnection()->exec_SELECTgetRows(
1273  'uid,t3ver_oid,t3ver_state',
1274  $tableName,
1275  'pid=-1 AND t3ver_oid IN (' . implode(',', $ids) . ') AND t3ver_wsid=' . $this->getWorkspaceId() .
1277  );
1278 
1279  if (!empty($versions)) {
1280  foreach ($versions as $version) {
1281  $liveId = $version['t3ver_oid'];
1282  if (isset($ids[$liveId])) {
1283  unset($ids[$liveId]);
1284  }
1285  }
1286  }
1287 
1288  return array_values($ids);
1289  }
1290 
1291  protected function removeFromItemArray($tableName, $id) {
1292  foreach ($this->itemArray as $index => $item) {
1293  if ($item['table'] === $tableName && (string)$item['id'] === (string)$id) {
1294  unset($this->itemArray[$index]);
1295  return TRUE;
1296  }
1297  }
1298  return FALSE;
1299  }
1300 
1310  static public function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
1311  return MathUtility::canBeInterpretedAsInteger($childRec['uid'])
1312  && $parentConf['symmetric_field']
1313  && $parentUid == $childRec[$parentConf['symmetric_field']];
1314  }
1315 
1324  protected function completeOppositeUsageValues($tableName, array $referenceValues) {
1325  if (empty($this->MM_oppositeUsage[$tableName]) || count($this->MM_oppositeUsage[$tableName]) > 1) {
1326  return $referenceValues;
1327  }
1328 
1329  $fieldName = $this->MM_oppositeUsage[$tableName][0];
1330  if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1331  return $referenceValues;
1332  }
1333 
1334  $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1335  if (!empty($configuration['MM_insert_fields'])) {
1336  $referenceValues = array_merge($configuration['MM_insert_fields'], $referenceValues);
1337  } elseif (!empty($configuration['MM_match_fields'])) {
1338  $referenceValues = array_merge($configuration['MM_match_fields'], $referenceValues);
1339  }
1340 
1341  return $referenceValues;
1342  }
1343 
1352  protected function getLiveDefaultId($tableName, $id) {
1353  $liveDefaultId = BackendUtility::getLiveVersionIdOfRecord($tableName, $id);
1354  if ($liveDefaultId === NULL) {
1355  $liveDefaultId = $id;
1356  }
1357  return (int)$liveDefaultId;
1358  }
1359 
1366  protected function getResolver($tableName, array $ids, $sortingStatement = NULL) {
1368  $resolver = GeneralUtility::makeInstance(
1369  'TYPO3\\CMS\\Core\\DataHandling\\PlainDataResolver',
1370  $tableName,
1371  $ids,
1372  $sortingStatement
1373  );
1374  $resolver->setWorkspaceId($this->getWorkspaceId());
1375  $resolver->setKeepDeletePlaceholder(TRUE);
1376  $resolver->setKeepLiveIds($this->useLiveReferenceIds);
1377  return $resolver;
1378  }
1379 
1383  protected function getDatabaseConnection() {
1384  return $GLOBALS['TYPO3_DB'];
1385  }
1386 
1387 }
writeMM($MM_tableName, $uid, $prependTableName=FALSE)
completeOppositeUsageValues($tableName, array $referenceValues)
writeForeignField($conf, $parentUid, $updateToUid=0, $skipSorting=FALSE)
$uid
Definition: server.php:36
setUpdateReferenceIndex($updateReferenceIndex)
static trimExplode($delim, $string, $removeEmptyValues=FALSE, $limit=0)
static getRecordTitle($table, $row, $prep=FALSE, $forceResult=TRUE)
static isOnSymmetricSide($parentUid, $parentConf, $childRec)
readList($itemlist, array $configuration)
purgeLiveVersionedIds($tableName, array $ids)
remapMM($MM_tableName, $uid, $newUid, $prependTableName=FALSE)
convertPosNeg($valueArray, $fTable, $nfTable)
purgeDeletePlaceholder($tableName, array $ids)
static fixed_lgd_cs($string, $chars, $appendString='...')
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=array())
static deleteClause($table, $tableAlias='')