From 2de97778ea5f1675ee2ccfa1da94167a4f6d08c5 Mon Sep 17 00:00:00 2001
From: Frank Naegler <frank.naegler@typo3.org>
Date: Thu, 29 Jul 2021 12:34:50 +0200
Subject: [PATCH] [FEATURE] Access site configuration in foreign_table_where

This patch adds the possibility to access site configuration
with a placeholder in TCA's `foreign_table_where` query.

To access a configuration value the following syntax is available:

* ###SITE:rootPageId###
* ###SITE:foo.bar.baz### (array path notation)

Resolves: #94662
Releases: master
Change-Id: I2047085d20c914960bbcd88df5b4c7a7f8d53282
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70126
Tested-by: core-ci <typo3@b13.com>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../FormDataProvider/AbstractItemProvider.php | 56 ++++++++++++++++++
 .../FormDataProvider/TcaSelectItemsTest.php   | 58 +++++++++++++++++++
 ...orSiteConfigurationInForeignTableWhere.rst | 32 ++++++++++
 3 files changed, 146 insertions(+)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-94662-AddPlaceholderForSiteConfigurationInForeignTableWhere.rst

diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
index dff5629905ad..ffd50efafaf4 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractItemProvider.php
@@ -19,12 +19,14 @@ use Doctrine\DBAL\Exception as DBALException;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Configuration\Features;
+use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
 use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
@@ -33,8 +35,11 @@ use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Resource\FileRepository;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
@@ -836,6 +841,8 @@ abstract class AbstractItemProvider
                 ],
                 $foreignTableClause
             );
+
+            $foreignTableClause = $this->parseSiteConfiguration($connection, $siteRootUid, $foreignTableClause);
         }
 
         // Split the clause into an array with keys WHERE, GROUPBY, ORDERBY, LIMIT
@@ -871,6 +878,55 @@ abstract class AbstractItemProvider
         return $foreignTableClauseArray;
     }
 
+    protected function parseSiteConfiguration(Connection $connection, int $siteRootUid, string $foreignTableClause): string
+    {
+        if ($siteRootUid === 0) {
+            return $foreignTableClause;
+        }
+
+        $siteClausesRegEx = '/###SITE:([^#]+)###/m';
+        preg_match_all($siteClausesRegEx, $foreignTableClause, $matches, PREG_SET_ORDER);
+
+        if (empty($matches)) {
+            return $foreignTableClause;
+        }
+
+        try {
+            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByRootPageId($siteRootUid);
+            $replacements = [];
+            $configuration = $site->getConfiguration();
+            array_walk($matches, static function ($match) use ($connection, &$replacements, &$configuration) {
+                $key = $match[1];
+                try {
+                    $value = ArrayUtility::getValueByPath($configuration, $key, '.');
+                } catch (MissingArrayPathException $exception) {
+                    $value = '';
+                }
+
+                if (is_string($value)) {
+                    $value = $connection->quote($value);
+                } elseif (is_array($value)) {
+                    $value = implode(',', array_map(static function ($item) use ($connection) {
+                        return $connection->quote($item);
+                    }, $value));
+                } elseif (is_bool($value)) {
+                    $value = (int)$value;
+                }
+
+                $replacements[$match[0]] = $value;
+            });
+            $foreignTableClause = str_replace(
+                array_keys($replacements),
+                array_values($replacements),
+                $foreignTableClause
+            );
+        } catch (SiteNotFoundException $exception) {
+            // No site found, means also no site marker to replace
+            return $foreignTableClause;
+        }
+        return $foreignTableClause;
+    }
+
     /**
      * Convert the current database values into an array
      *
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
index 3ff837d7434b..82c12baf7bf3 100644
--- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
@@ -38,6 +38,8 @@ use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Resource\FileRepository;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
@@ -903,6 +905,52 @@ class TcaSelectItemsTest extends UnitTestCase
                     ],
                 ],
             ],
+            'replace SITE:rootPageId' => [
+                'AND fTable.uid = ###SITE:rootPageId###',
+                [
+                    ['fTable.uid = 1'],
+                    [' 1=1'],
+                    ['`pages.uid` = `fTable.pid`'],
+                ],
+                []
+            ],
+            'replace SITE:mySetting.foobar' => [
+                'AND fTable.foo = ###SITE:mySetting.foobar###',
+                [
+                    ['fTable.foo = 4711'],
+                    [' 1=1'],
+                    ['`pages.uid` = `fTable.pid`'],
+                ],
+                []
+            ],
+            'replace SITE:mySetting.doesNotExist' => [
+                'AND fTable.foo = ###SITE:mySetting.doesNotExist###',
+                [
+                    ['fTable.foo = \'\''],
+                    [' 1=1'],
+                    ['`pages.uid` = `fTable.pid`'],
+                ],
+                []
+            ],
+            'replace replace SITE:rootPageId, SITE:mySetting.foobar and PAGE_TSCONFIG_IDLIST' => [
+                'AND fTable.uid = ###SITE:rootPageId### AND fTable.foo = ###SITE:mySetting.foobar### AND fTable.bar IN (###PAGE_TSCONFIG_IDLIST###)',
+                [
+                    ['fTable.uid = 1 AND fTable.foo = 4711 AND fTable.bar IN (471,481)'],
+                    [' 1=1'],
+                    ['`pages.uid` = `fTable.pid`'],
+                ],
+                [
+                    'pageTsConfig' => [
+                        'TCEFORM.' => [
+                            'aTable.' => [
+                                'aField.' => [
+                                    'PAGE_TSCONFIG_IDLIST' => 'a, 471, b, 481, c',
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
         ];
     }
 
@@ -952,6 +1000,16 @@ class TcaSelectItemsTest extends UnitTestCase
 
         $GLOBALS['TCA']['fTable'] = [];
 
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteProphecy->getConfiguration()->willReturn([
+            'rootPageId' => 1,
+            'mySetting' => [
+                'foobar' => 4711,
+            ],
+        ]);
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        $siteFinderProphecy->getSiteByRootPageId(Argument::any())->willReturn($siteProphecy->reveal());
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
         $fileRepositoryProphecy = $this->prophesize(FileRepository::class);
         $fileRepositoryProphecy->findByRelation(Argument::cetera())->shouldNotBeCalled();
         GeneralUtility::setSingletonInstance(FileRepository::class, $fileRepositoryProphecy->reveal());
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-94662-AddPlaceholderForSiteConfigurationInForeignTableWhere.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-94662-AddPlaceholderForSiteConfigurationInForeignTableWhere.rst
new file mode 100644
index 000000000000..928559fe48b8
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-94662-AddPlaceholderForSiteConfigurationInForeignTableWhere.rst
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+===============================================================================
+Feature: #94662 - Add placeholder for site configuration in foreign_table_where
+===============================================================================
+
+See :issue:`94662`
+
+Description
+===========
+
+The `foreign_table_where` setting in TCA allows some old marker-based
+placeholder to customize the query. The best place to define site-dependent
+settings is the site configuration, which now can be used within
+`foreign_table_where`.
+
+To access a configuration value the following syntax is available:
+
+* `###SITE:<KEY>###` - <KEY> is your setting name from site config e.g. `###SITE:rootPageId###`
+* `###SITE:<KEY>.<SUBKEY>###` - an array path notation is possible. e.g. `###SITE:mySetting.categoryPid###`
+
+Example:
+--------
+.. code-block:: php
+
+    ...
+    'fieldConfiguration' => [
+        'foreign_table_where' => ' AND ({#sys_category}.uid = ###SITE:rootPageId### OR {#sys_category}.pid = ###SITE:mySetting.categoryPid###) ORDER BY sys_category.title ASC',
+    ],
+    ...
+
+.. index:: Backend, FlexForm, TCA, NotScanned, ext:backend
-- 
GitLab