[TASK] Extract data overlay handling to common class 01/39901/14
authorOliver Hader <oliver@typo3.org>
Sat, 22 Aug 2015 14:45:38 +0000 (16:45 +0200)
committerOliver Hader <oliver.hader@typo3.org>
Mon, 19 Oct 2015 17:35:13 +0000 (19:35 +0200)
The data overlay handling of RelationHandler is extracted to
a separate common class. The scope of this object shall be
independent from frontend-only or backend-only constraints.

The new handler resolves version overlays for given IDs of
a database table and applies sorting for the target context.

This is just an intermediate solution to overcome sorting issues
of relations handled in RelationHandler. The goal of DataResolvers
should be to provide independent APIs for OneToMany and ManyToMany
relation resolving in the future as well.

Resolves: #69967
Releases: master, 6.2
Change-Id: I5a7f76a55b4af93aee6270168285fff9486a2e73
Reviewed-on: http://review.typo3.org/39901
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/RelationHandler.php

diff --git a/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php b/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php
new file mode 100644 (file)
index 0000000..09d0329
--- /dev/null
@@ -0,0 +1,327 @@
+<?php
+namespace TYPO3\CMS\Core\DataHandling;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Versioning\VersionState;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+
+/**
+ * Plain data resolving.
+ *
+ * This component resolves data constraints for given IDs of a
+ * particular table on a plain/raw database level. Thus, workspaces
+ * placeholders and overlay related resorting is applied automatically.
+ */
+class PlainDataResolver
+{
+    /**
+     * @var string
+     */
+    protected $tableName;
+
+    /**
+     * @var int[]
+     */
+    protected $liveIds;
+
+    /**
+     * @var string
+     */
+    protected $sortingStatement;
+
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @var bool
+     */
+    protected $keepLiveIds = false;
+
+    /**
+     * @var bool
+     */
+    protected $keepDeletePlaceholder = false;
+
+    /**
+     * @var int[]
+     */
+    protected $resolvedIds;
+
+    /**
+     * @param string $tableName
+     * @param int[] $liveIds
+     * @param NULL|string $sortingStatement
+     */
+    public function __construct($tableName, array $liveIds, $sortingStatement = null)
+    {
+        $this->tableName = $tableName;
+        $this->liveIds = $this->reindex($liveIds);
+        $this->sortingStatement = $sortingStatement;
+    }
+
+    /**
+     * Sets the target workspace ID the final result shall use.
+     *
+     * @param int $workspaceId
+     */
+    public function setWorkspaceId($workspaceId)
+    {
+        $this->workspaceId = (int)$workspaceId;
+    }
+
+    /**
+     * Sets whether live IDs shall be kept in the final result set.
+     *
+     * @param bool $keepLiveIds
+     */
+    public function setKeepLiveIds($keepLiveIds)
+    {
+        $this->keepLiveIds = (bool)$keepLiveIds;
+    }
+
+    /**
+     * Sets whether delete placeholders shall be kept in the final result set.
+     *
+     * @param bool $keepDeletePlaceholder
+     */
+    public function setKeepDeletePlaceholder($keepDeletePlaceholder)
+    {
+        $this->keepDeletePlaceholder = (bool)$keepDeletePlaceholder;
+    }
+
+    /**
+     * @return int[]
+     */
+    public function get()
+    {
+        if (isset($this->resolvedIds)) {
+            return $this->resolvedIds;
+        }
+
+        $ids = $this->processVersionOverlays($this->liveIds);
+        $ids = $this->processSorting($ids);
+        $ids = $this->applyLiveIds($ids);
+
+        $this->resolvedIds = $ids;
+        return $this->resolvedIds;
+    }
+
+    /**
+     * Processes version overlays on the final result set.
+     *
+     * @param int[] $ids
+     * @return int[]
+     */
+    protected function processVersionOverlays(array $ids)
+    {
+        if (empty($this->workspaceId) || !$this->isWorkspaceEnabled() || empty($ids)) {
+            return $ids;
+        }
+
+        $ids = $this->processVersionMovePlaceholders($ids);
+        $versions = $this->getDatabaseConnection()->exec_SELECTgetRows(
+            'uid,t3ver_oid,t3ver_state',
+            $this->tableName,
+            'pid=-1 AND t3ver_oid IN (' . $this->intImplode(',', $ids) . ')'
+            . ' AND t3ver_wsid=' . $this->workspaceId
+        );
+
+        if (!empty($versions)) {
+            foreach ($versions as $version) {
+                $liveReferenceId = $version['t3ver_oid'];
+                $versionId = $version['uid'];
+                if (isset($ids[$liveReferenceId])) {
+                    if (!$this->keepDeletePlaceholder && VersionState::cast($version['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
+                        unset($ids[$liveReferenceId]);
+                    } else {
+                        $ids[$liveReferenceId] = $versionId;
+                    }
+                }
+            }
+            $ids = $this->reindex($ids);
+        }
+
+        return $ids;
+    }
+
+    /**
+     * Processes and resolves move placeholders on the final result set.
+     *
+     * @param int[] $ids
+     * @return int[]
+     */
+    protected function processVersionMovePlaceholders(array $ids)
+    {
+        // Early return on insufficient data-set
+        if (empty($this->workspaceId) || !$this->isVersionMovePlaceholderAware() || empty($ids)) {
+            return $ids;
+        }
+
+        $movePlaceholders = $this->getDatabaseConnection()->exec_SELECTgetRows(
+            'uid,t3ver_move_id',
+            $this->tableName,
+            'pid<>-1 AND t3ver_state=' . VersionState::MOVE_PLACEHOLDER
+            . ' AND t3ver_wsid=' . $this->workspaceId
+            . ' AND t3ver_move_id IN (' . $this->intImplode(',', $ids) . ')'
+        );
+
+        if (!empty($movePlaceholders)) {
+            foreach ($movePlaceholders as $movePlaceholder) {
+                $liveReferenceId = $movePlaceholder['t3ver_move_id'];
+                $movePlaceholderId = $movePlaceholder['uid'];
+                // If both, MOVE_PLACEHOLDER and MOVE_POINTER are set
+                if (isset($ids[$liveReferenceId]) && isset($ids[$movePlaceholderId])) {
+                    $ids[$movePlaceholderId] = $liveReferenceId;
+                    unset($ids[$liveReferenceId]);
+                }
+            }
+            $ids = $this->reindex($ids);
+        }
+
+        return $ids;
+    }
+
+    /**
+     * Processes sorting of the final result set, if
+     * a sorting statement (table column/expression) is given.
+     *
+     * @param int[] $ids
+     * @return int[]
+     */
+    protected function processSorting(array $ids)
+    {
+        // Early return on missing sorting statement or insufficient data-set
+        if (empty($this->sortingStatement) || count($ids) < 2) {
+            return $ids;
+        }
+
+        $records = $this->getDatabaseConnection()->exec_SELECTgetRows(
+            'uid',
+            $this->tableName,
+            'uid IN (' . $this->intImplode(',', $ids) . ')',
+            '',
+            $this->sortingStatement,
+            '',
+            'uid'
+        );
+
+        if (!is_array($records)) {
+            return array();
+        }
+
+        $ids = $this->reindex(array_keys($records));
+        return $ids;
+    }
+
+    /**
+     * Applies live IDs to the final result set, if
+     * the current table is enabled for workspaces and
+     * the keepLiveIds class member is enabled.
+     *
+     * @param int[] $ids
+     * @return int[]
+     */
+    protected function applyLiveIds(array $ids)
+    {
+        if (!$this->keepLiveIds || !$this->isWorkspaceEnabled() || empty($ids)) {
+            return $ids;
+        }
+
+        $records = $this->getDatabaseConnection()->exec_SELECTgetRows(
+            'uid,t3ver_oid',
+            $this->tableName,
+            'uid IN (' . $this->intImplode(',', $ids) . ')',
+            '',
+            '',
+            '',
+            'uid'
+        );
+
+        if (!is_array($records)) {
+            return array();
+        }
+
+        foreach ($ids as $id) {
+            if (!empty($records[$id]['t3ver_oid'])) {
+                $ids[$id] = $records[$id]['t3ver_oid'];
+            }
+        }
+
+        $ids = $this->reindex($ids);
+        return $ids;
+    }
+
+    /**
+     * Re-indexes the given IDs.
+     *
+     * @param int[] $ids
+     * @return int[]
+     */
+    protected function reindex(array $ids)
+    {
+        if (empty($ids)) {
+            return $ids;
+        }
+        $ids = array_values($ids);
+        $ids = array_combine($ids, $ids);
+        return $ids;
+    }
+
+    /**
+     * @return bool
+     */
+    protected function isWorkspaceEnabled()
+    {
+        return BackendUtility::isTableWorkspaceEnabled($this->tableName);
+    }
+
+    /**
+     * @return bool
+     */
+    protected function isVersionMovePlaceholderAware()
+    {
+        return BackendUtility::isTableMovePlaceholderAware($this->tableName);
+    }
+
+    /**
+     * @return bool
+     */
+    protected function isLocalizationEnabled()
+    {
+        return BackendUtility::isTableLocalizable($this->tableName);
+    }
+
+    /**
+     * Implodes an array of casted integer values.
+     *
+     * @param string $delimiter
+     * @param array $values
+     * @return string
+     */
+    protected function intImplode($delimiter, array $values)
+    {
+        return implode($delimiter, $this->getDatabaseConnection()->cleanIntArray($values));
+    }
+
+    /**
+     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+     */
+    protected function getDatabaseConnection()
+    {
+        return $GLOBALS['TYPO3_DB'];
+    }
+}
index 127fcbc..2cb4dd6 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
+use TYPO3\CMS\Core\DataHandling\PlainDataResolver;
 
 /**
  * Load database groups (relations)
@@ -440,15 +441,12 @@ class RelationHandler
                 }
             // Directly overlay workspace data
             } else {
-                $rows = array();
-                $foreignTable = $configuration['foreign_table'];
-                foreach ($this->tableArray[$foreignTable] as $itemId) {
-                    $rows[$itemId] = array('uid' => $itemId);
-                }
                 $this->itemArray = array();
-                foreach ($this->getRecordVersionsIds($foreignTable, $rows) as $row) {
+                $foreignTable = $configuration['foreign_table'];
+                $ids = $this->getResolver($foreignTable, $this->tableArray[$foreignTable])->get();
+                foreach ($ids as $id) {
                     $this->itemArray[] = array(
-                        'id' => $row['uid'],
+                        'id' => $id,
                         'table' => $foreignTable,
                     );
                 }
@@ -822,13 +820,11 @@ class RelationHandler
         // Get the rows from storage
         $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby, '', 'uid');
         if (!empty($rows)) {
-            if (BackendUtility::isTableWorkspaceEnabled($foreign_table) && !$this->useLiveReferenceIds) {
-                $rows = $this->getRecordVersionsIds($foreign_table, $rows);
-            }
-            foreach ($rows as $row) {
-                $this->itemArray[$key]['id'] = $row['uid'];
+            $ids = $this->getResolver($foreign_table, array_keys($rows), $sortby)->get();
+            foreach ($ids as $id) {
+                $this->itemArray[$key]['id'] = $id;
                 $this->itemArray[$key]['table'] = $foreign_table;
-                $this->tableArray[$foreign_table][] = $row['uid'];
+                $this->tableArray[$foreign_table][] = $id;
                 $key++;
             }
         }
@@ -1323,73 +1319,6 @@ class RelationHandler
     }
 
     /**
-     * @param string $tableName
-     * @param array $records
-     * @return array
-     */
-    protected function getRecordVersionsIds($tableName, array $records)
-    {
-        $workspaceId = (int)$GLOBALS['BE_USER']->workspace;
-        $liveIds = array_map('intval', $this->extractValues($records, 'uid'));
-        $liveIdList = implode(',', $liveIds);
-
-        if (BackendUtility::isTableMovePlaceholderAware($tableName)) {
-            $versions = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
-                'uid,t3ver_move_id',
-                $tableName,
-                't3ver_state=3 AND t3ver_wsid=' . $workspaceId . ' AND t3ver_move_id IN (' . $liveIdList . ')'
-            );
-
-            if (!empty($versions)) {
-                foreach ($versions as $version) {
-                    $liveReferenceId = $version['t3ver_move_id'];
-                    $movePlaceholderId = $version['uid'];
-                    if (isset($records[$liveReferenceId]) && $records[$movePlaceholderId]) {
-                        $records[$movePlaceholderId] = $records[$liveReferenceId];
-                        unset($records[$liveReferenceId]);
-                    }
-                }
-                $liveIds = array_map('intval', $this->extractValues($records, 'uid'));
-                $records = array_combine($liveIds, array_values($records));
-                $liveIdList = implode(',', $liveIds);
-            }
-        }
-
-        $versions = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
-            'uid,t3ver_oid,t3ver_state',
-            $tableName,
-            'pid=-1 AND t3ver_oid IN (' . $liveIdList . ') AND t3ver_wsid=' . $workspaceId,
-            '',
-            't3ver_state DESC'
-        );
-
-        if (!empty($versions)) {
-            foreach ($versions as $version) {
-                $liveId = $version['t3ver_oid'];
-                if (isset($records[$liveId])) {
-                    $records[$liveId] = $version;
-                }
-            }
-        }
-
-        return $records;
-    }
-
-    /**
-     * @param array $array
-     * @param string $fieldName
-     * @return array
-     */
-    protected function extractValues(array $array, $fieldName)
-    {
-        $values = array();
-        foreach ($array as $item) {
-            $values[] = $item[$fieldName];
-        }
-        return $values;
-    }
-
-    /**
      * Gets the record uid of the live default record. If already
      * pointing to the live record, the submitted record uid is returned.
      *
@@ -1407,6 +1336,27 @@ class RelationHandler
     }
 
     /**
+     * @param string $tableName
+     * @param int[] $ids
+     * @param string $sortingStatement
+     * @return PlainDataResolver
+     */
+    protected function getResolver($tableName, array $ids, $sortingStatement = null)
+    {
+        /** @var PlainDataResolver $resolver */
+        $resolver = GeneralUtility::makeInstance(
+            PlainDataResolver::class,
+            $tableName,
+            $ids,
+            $sortingStatement
+        );
+        $resolver->setWorkspaceId($this->getWorkspaceId());
+        $resolver->setKeepDeletePlaceholder(true);
+        $resolver->setKeepLiveIds($this->useLiveReferenceIds);
+        return $resolver;
+    }
+
+    /**
      * @return DatabaseConnection
      */
     protected function getDatabaseConnection()