Skip to content

Commit

Permalink
[BUGFIX] TypoScript configuration for "Hide default language" sites
Browse files Browse the repository at this point in the history
This change fixes several troubles concerning "Hide default language" 
behavior of RecordMonitor processes.

Previously the EXT:solr approach of TSFE initialization and subsequently
applied side-effects for changing language context did not allow proper
retrieval of TypoScript configuration, required by RecordMonitor.
Current implementation of isolated/capsuled TSFE objects allows to
fallback to TSFE in any active site language, and therefore to retrieve 
the EXT:solr TypoScript configuration within TYPO3 BE requests 
even a single non default language is available in site.

Despite all the current improvements the whole TSFE stack must be refactored
to proper bounded contexts facility avoiding hard dependencies
and service locator disadvantages.
The TSFE is involved in following contexts:
* TYPO3 BE without hard dependency to site language
** ReocordMonitor
** Queue initialization
* Indexing with hard dependency to site language
** within BE-Web context
** CLI
In all that contexts the "TSFE" Initialization and usage MUST NOT interfere 
with TYPO3 processes but MUST provide reliable dependency injection
for all EXT:solr components requiring TypoScript configuration.


Fixes: #2983, #2452, #2171, #1396, #1395, #1374
  • Loading branch information
dkd-kaehm committed Nov 27, 2021
1 parent 94107f3 commit ad5c0d2
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ protected function getPageRecordByPageId($pageId, $fieldList = 'is_siteroot')
}

/**
* Determines the rootpage ID for a given page.
* Determines the root page ID for a given page.
*
* @param int $pageId A page ID somewhere in a tree.
* @param bool $forceFallback Force the explicit detection and do not use the current frontend root line
Expand Down
51 changes: 43 additions & 8 deletions Classes/Domain/Site/SiteRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@
use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration;
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
use ApacheSolrForTypo3\Solr\System\Util\SiteUtility;
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
use Exception;
use InvalidArgumentException;
use Throwable;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Site\Entity\Site as CoreSite;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
* SiteRepository
Expand Down Expand Up @@ -207,12 +209,12 @@ protected function getAvailableTYPO3ManagedSites(bool $stopOnInvalidSite): array
return $typo3ManagedSolrSites;
}

/**
/**
* Creates an instance of the Site object.
*
* @param int $rootPageId
* @return SiteInterface
* @throws InvalidArgumentException
* @throws DBALDriverException
*/
protected function buildSite(int $rootPageId)
{
Expand Down Expand Up @@ -256,18 +258,21 @@ protected function validateRootPageRecord(int $rootPageId, array $rootPageRecord
}

/**
* Builds a TYPO3 managed site with TypoScript configuration.
*
* @param array $rootPageRecord
*
* @return Typo3ManagedSite
*
* @throws DBALDriverException
*/
protected function buildTypo3ManagedSite(array $rootPageRecord): ?Typo3ManagedSite
{
$solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId($rootPageRecord['uid']);
/* @var CoreSite $typo3Site */
try {
$typo3Site = $this->siteFinder->getSiteByPageId($rootPageRecord['uid']);
} catch (SiteNotFoundException $e) {
$typo3Site = $this->getTypo3Site($rootPageRecord['uid']);
if (!$typo3Site instanceof CoreSite) {
return null;
}

$domain = $typo3Site->getBase()->getHost();

$siteHash = $this->getSiteHashForDomain($domain);
Expand All @@ -277,6 +282,17 @@ protected function buildTypo3ManagedSite(array $rootPageRecord): ?Typo3ManagedSi
return $language->getLanguageId();
}, $typo3Site->getLanguages());

// Try to get first instantiable TSFE for one of site languages, to get TypoScript with `plugin.tx_solr.index.*`,
// to be able to collect indexing configuration,
// which are required for BE-Modules/CLI-Commands or RecordMonitor within BE/TCE-commands.
// If TSFE for none of languages can be initialized, then the Typo3ManagedSite object unusable at all,
// so the rest of the steps in this method are not necessary, and therefore the null will be returned.
$tsfeFactory = GeneralUtility::makeInstance(FrontendEnvironment\Tsfe::class);
$tsfeToUseForTypoScriptConfiguration = $tsfeFactory->getTsfeByPageIdAndLanguageFallbackChain($typo3Site->getRootPageId(), ...$availableLanguageIds);
if (!$tsfeToUseForTypoScriptConfiguration instanceof TypoScriptFrontendController) {
return null;
}

$solrConnectionConfigurations = [];

foreach ($availableLanguageIds as $languageUid) {
Expand Down Expand Up @@ -314,6 +330,11 @@ protected function buildTypo3ManagedSite(array $rootPageRecord): ?Typo3ManagedSi
}
}

$solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId(
$rootPageRecord['uid'],
$tsfeToUseForTypoScriptConfiguration->getLanguage()->getLanguageId()
);

return GeneralUtility::makeInstance(
Typo3ManagedSite::class,
/** @scrutinizer ignore-type */
Expand All @@ -337,4 +358,18 @@ protected function buildTypo3ManagedSite(array $rootPageRecord): ?Typo3ManagedSi
);
}

/**
* Returns {@link \TYPO3\CMS\Core\Site\Entity\Site}.
*
* @param int $pageUid
* @return CoreSite|null
*/
protected function getTypo3Site(int $pageUid): ?CoreSite
{
try {
return $this->siteFinder->getSiteByPageId($pageUid);
} catch (Throwable $e) {}
return null;
}

}
6 changes: 3 additions & 3 deletions Classes/Domain/Site/Typo3ManagedSite.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ public function getTypo3SiteObject(): Typo3Site
*
* @return bool
*/
public function hasFreeModeLanguages(): bool
public function hasFreeContentModeLanguages(): bool
{
return !empty($this->getFreeModeLanguages());
return !empty($this->getFreeContentModeLanguages());
}

/**
Expand All @@ -121,7 +121,7 @@ public function hasFreeModeLanguages(): bool
*
* @return array|null
*/
public function getFreeModeLanguages(): array
public function getFreeContentModeLanguages(): array
{
if (!empty($this->freeContentModeLanguages)) {
return $this->freeContentModeLanguages;
Expand Down
20 changes: 19 additions & 1 deletion Classes/FrontendEnvironment.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
* The TYPO3 project - inspiring people to share!
*/

use ApacheSolrForTypo3\Solr\FrontendEnvironment\Tsfe;
use ApacheSolrForTypo3\Solr\FrontendEnvironment\TypoScript;
use ApacheSolrForTypo3\Solr\System\Configuration\TypoScriptConfiguration;
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
* Class FrontendEnvironment is responsible for initializing/simulating the frontend in backend context
Expand Down Expand Up @@ -57,7 +60,22 @@ public function getConfigurationFromPageId(int $pageId, ?string $path = '', ?int
*/
public function isAllowedPageType(array $pageRecord, ?string $configurationName = 'pages'): bool
{
$configuration = $this->getConfigurationFromPageId($pageRecord['uid']);
// $pageRecord could come from DataHandler and with all columns. So we want to fetch it again.
$pageRecord = BackendUtility::getRecord('pages', $pageRecord['uid']);
$rootPageRecordUid = $pageRecord['uid'];
if (isset($pageRecord['sys_language_uid'])
&& (int)$pageRecord['sys_language_uid'] > 0
&& isset($pageRecord['l10n_parent'])
&& (int)$pageRecord['l10n_parent'] > 0
) {
$rootPageRecordUid = $pageRecord['l10n_parent'];
}

$tsfe = GeneralUtility::makeInstance(Tsfe::class)->getTsfeByPageIdIgnoringLanguage($rootPageRecordUid);
if (!$tsfe instanceof TypoScriptFrontendController) {
return false;
}
$configuration = $this->getConfigurationFromPageId($rootPageRecordUid, '', $tsfe->getLanguage()->getLanguageId());
$allowedPageTypes = $configuration->getIndexQueueAllowedPageTypesArrayByConfigurationName($configurationName);
return in_array($pageRecord['doktype'], $allowedPageTypes);
}
Expand Down
36 changes: 28 additions & 8 deletions Classes/FrontendEnvironment/Tsfe.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use TYPO3\CMS\Core\Context\UserAspect;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
use TYPO3\CMS\Core\Http\ServerRequest;
use function Webmozart\Assert\Tests\StaticAnalysis\null;

class Tsfe implements SingletonInterface
{
Expand Down Expand Up @@ -61,8 +62,6 @@ public function __construct(?SiteFinder $siteFinder = null)
*
* @throws DBALDriverException
* @throws Exception\Exception
* @throws InternalServerErrorException
* @throws ServiceUnavailableException
* @throws SiteNotFoundException
*
* @todo: Move whole caching stuff from this method and let return TSFE.
Expand Down Expand Up @@ -183,8 +182,6 @@ protected function initializeTsfe(int $pageId, int $language = 0, ?int $rootPage
*
* @return TypoScriptFrontendController
*
* @throws InternalServerErrorException
* @throws ServiceUnavailableException
* @throws SiteNotFoundException
* @throws DBALDriverException
* @throws Exception\Exception
Expand Down Expand Up @@ -223,6 +220,33 @@ public function getTsfeByPageIdAndLanguageFallbackChain(int $pageId, int ...$lan
return null;
}

/**
* Returns TSFE for first initializable site language.
*
* Is usable for BE-Modules/CLI-Commands stack only, where the rendered TypoScript configuration
* of EXT:solr* stack is wanted and the language id does not matter.
*
* @param int $pageId
* @return TypoScriptFrontendController|null
*/
public function getTsfeByPageIdIgnoringLanguage(int $pageId): ?TypoScriptFrontendController
{
try {
$typo3Site = $this->siteFinder->getSiteByPageId($pageId);
} catch (Throwable $e)
{
return null;
}
$availableLanguageIds = array_map(function($siteLanguage) {
return $siteLanguage->getLanguageId();
}, $typo3Site->getLanguages());

if (empty($availableLanguageIds)) {
return null;
}
return $this->getTsfeByPageIdAndLanguageFallbackChain($pageId, ...$availableLanguageIds);
}

/**
* Returns TypoScriptFrontendController with sand cast context.
*
Expand All @@ -233,8 +257,6 @@ public function getTsfeByPageIdAndLanguageFallbackChain(int $pageId, int ...$lan
*
* @return ServerRequest
*
* @throws InternalServerErrorException
* @throws ServiceUnavailableException
* @throws SiteNotFoundException
* @throws DBALDriverException
* @throws Exception\Exception
Expand All @@ -255,8 +277,6 @@ public function getServerRequestForTsfeByPageIdAndLanguageId(int $pageId, int $l
* @param int|null $rootPageId
*
* @throws DBALDriverException
* @throws InternalServerErrorException
* @throws ServiceUnavailableException
* @throws SiteNotFoundException
* @throws Exception\Exception
*/
Expand Down
5 changes: 4 additions & 1 deletion Classes/GarbageCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\GarbageRemover\StrategyFactory;
use ApacheSolrForTypo3\Solr\IndexQueue\Queue;
use ApacheSolrForTypo3\Solr\System\TCA\TCAService;
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\SingletonInterface;
Expand Down Expand Up @@ -342,10 +343,11 @@ protected function isPageExcludedFromSearch($record)

/**
* Checks whether a page has a page type that can be indexed.
* Currently standard pages and mount pages can be indexed.
* Currently, standard pages and mount pages can be indexed.
*
* @param array $record A page record
* @return bool TRUE if the page can be indexed according to its page type, FALSE otherwise
* @throws DBALDriverException
*/
protected function isIndexablePageType(array $record)
{
Expand All @@ -358,6 +360,7 @@ protected function isIndexablePageType(array $record)
* @param string $table
* @param array $record
* @return bool
* @throws DBALDriverException
*/
protected function getIsGarbageRecord($table, $record):bool
{
Expand Down
4 changes: 2 additions & 2 deletions Classes/IndexQueue/Initializer/AbstractInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ protected function buildTcaWhereClause()
$GLOBALS['TCA'][$this->type]['ctrl']['languageField'] . ' = -1'
];
// all "free"-Mode languages for "non-pages"-records only
if ($this->type !== 'pages' && $this->site->hasFreeModeLanguages()) {
if ($this->type !== 'pages' && $this->site->hasFreeContentModeLanguages()) {
$conditions['languageField'][]
= $GLOBALS['TCA'][$this->type]['ctrl']['languageField']
. ' IN(/* free content mode */ '
. implode(',', $this->site->getFreeModeLanguages())
. implode(',', $this->site->getFreeContentModeLanguages())
. ')';
}

Expand Down
13 changes: 11 additions & 2 deletions Classes/IndexQueue/RecordMonitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ protected function processRecord($recordTable, $recordPageId, $recordUid, $field
$this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
return;
}
} catch ( \InvalidArgumentException $e) {
} catch (\InvalidArgumentException $e) {
$this->removeFromIndexAndQueueWhenItemInQueue($recordTable, $recordUid);
return;
}
Expand Down Expand Up @@ -418,7 +418,8 @@ protected function processRecord($recordTable, $recordPageId, $recordUid, $field
$this->indexQueue->deleteItem('pages', $recordUid);
}

if (!$site->hasFreeModeLanguages() || !in_array($record['sys_language_uid'], $site->getFreeModeLanguages())) {
// The pages localized record can not consist without l10n_parent, so apply "free-content-mode" on records only.
if ($recordTable === 'pages' || !$site->hasFreeContentModeLanguages() || !in_array($record['sys_language_uid'], $site->getFreeContentModeLanguages())) {
$recordUid = $this->tcaService->getTranslationOriginalUidIfTranslated($recordTable, $record, $recordUid);
}

Expand Down Expand Up @@ -451,6 +452,14 @@ protected function processRecord($recordTable, $recordPageId, $recordUid, $field
protected function getConfigurationPageId($recordTable, $recordPageId, $recordUid)
{
$rootPageId = $this->rootPageResolver->getRootPageId($recordPageId);
$rootPageRecord = BackendUtility::getRecord('pages', $rootPageId, '*');
if (isset($rootPageRecord['sys_language_uid'])
&& (int)$rootPageRecord['sys_language_uid'] > 0
&& isset($rootPageRecord['l10n_parent'])
&& (int)$rootPageRecord['l10n_parent'] > 0
) {
$rootPageId = $recordPageId = $rootPageRecord['l10n_parent'];
}
if ($this->rootPageResolver->getIsRootPageId($rootPageId)) {
return $recordPageId;
}
Expand Down
1 change: 0 additions & 1 deletion Classes/System/Configuration/ConfigurationPageResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
use RuntimeException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
use function Webmozart\Assert\Tests\StaticAnalysis\null;

/**
* This class is responsible to find the closest page id from the rootline where
Expand Down
2 changes: 1 addition & 1 deletion Tests/Integration/IndexQueue/RecordMonitorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public function queueIsNotFilledWhenItemIsSetToHidden()

// simulate the database change and build a faked changeset
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
$connection->update('pages', ['hidden' => 17], ['uid' => 1]);
$connection->update('pages', ['hidden' => 1], ['uid' => 17]);

$changeSet = ['hidden' => 1];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use ApacheSolrForTypo3\Solr\Domain\Search\ResultSet\Facets\RangeBased\DateRange\DateRangeFacet;
use ApacheSolrForTypo3\Solr\Tests\Unit\UnitTest;
use DateTime;
use function Webmozart\Assert\Tests\StaticAnalysis\null;

/**
* Class DateRangeTest
Expand Down

0 comments on commit ad5c0d2

Please sign in to comment.