Commit ed335f18 authored by Benni Mack's avatar Benni Mack Committed by Andreas Fernandez
Browse files

[BUGFIX] Use list module search in page module

The page module search bar now redirects to the list module,
where all functionality is given, and all records on this
page (not just tt_content) are found, streamlining
it with the list module logic.

Editors without permission for the list module do not see
the search in the page module (similar behaviour to LiveSearch).

All code of the page layout classes related to search is removed.
As this is non-public API, the removal is done without any
deprecations necessary.

Resolves: #90235
Releases: master
Change-Id: Ia23c931c5a009e8ffe9a1a56219fb922d63e44bf
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63063


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 05e56f76
......@@ -26,10 +26,8 @@ use TYPO3\CMS\Backend\View\BackendLayoutView;
use TYPO3\CMS\Backend\View\PageLayoutView;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Imaging\Icon;
......@@ -72,27 +70,6 @@ class PageLayoutController
*/
protected $imagemode;
/**
* Search-fields
*
* @var string
*/
protected $search_field;
/**
* Search-levels
*
* @var int
*/
protected $search_levels;
/**
* Show-limit
*
* @var int
*/
protected $showLimit;
/**
* Return URL
*
......@@ -284,23 +261,14 @@ class PageLayoutController
// Setting module configuration / page select clause
$this->MCONF['name'] = $this->moduleName;
$this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
// Get session data
$sessionData = $this->getBackendUser()->getSessionData(__CLASS__);
$this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : '';
$this->id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
$this->pointer = $parsedBody['pointer'] ?? $queryParams['pointer'] ?? null;
$this->imagemode = $parsedBody['imagemode'] ?? $queryParams['imagemode'] ?? null;
$this->popView = $parsedBody['popView'] ?? $queryParams['popView'] ?? null;
$this->search_field = $parsedBody['search_field'] ?? $queryParams['search_field'] ?? null;
$this->search_levels = $parsedBody['search_levels'] ?? $queryParams['search_levels'] ?? null;
$this->showLimit = $parsedBody['showLimit'] ?? $queryParams['showLimit'] ?? null;
$returnUrl = $parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? null;
$this->returnUrl = GeneralUtility::sanitizeLocalUrl($returnUrl);
$sessionData['search_field'] = $this->search_field;
// Store session data
$this->getBackendUser()->setAndSaveSessionData(__CLASS__, $sessionData);
// Load page info array:
$this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
// Initialize menu
......@@ -820,7 +788,7 @@ class PageLayoutController
}
// Start the dblist object:
$dbList->itemsLimitSingleTable = 1000;
$dbList->start($this->id, $table, $this->pointer, $this->search_field, $this->search_levels, $this->showLimit);
$dbList->start($this->id, $table, $this->pointer);
$dbList->counter = $CMcounter;
$dbList->ext_function = $this->MOD_SETTINGS['function'];
// Generate the list of elements here:
......@@ -846,15 +814,17 @@ class PageLayoutController
$content .= $output;
}
// Making search form:
if (!$this->modTSconfig['properties']['disableSearchBox'] && ($dbList->counter > 0 || $this->currentPageHasSubPages())) {
$this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
$toggleSearchFormButton = $this->buttonBar->makeLinkButton()
->setClasses('t3js-toggle-search-toolbox')
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchIcon'))
->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL))
->setHref('#');
$this->buttonBar->addButton($toggleSearchFormButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
$this->searchContent = $dbList->getSearchBox();
if (!$this->modTSconfig['properties']['disableSearchBox']) {
$this->searchContent = $this->getSearchBox();
if ($this->searchContent) {
$this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
$toggleSearchFormButton = $this->buttonBar->makeLinkButton()
->setClasses('t3js-toggle-search-toolbox')
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchIcon'))
->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL))
->setHref('#');
$this->buttonBar->addButton($toggleSearchFormButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
}
}
// Additional footer content
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawFooterHook'] ?? [] as $hook) {
......@@ -919,9 +889,6 @@ class PageLayoutController
'edit_record',
'pointer',
'new_unique_uid',
'search_field',
'search_levels',
'showLimit'
])
->setSetVariables(array_keys($this->MOD_MENU));
$this->buttonBar->addButton($shortcutButton);
......@@ -1200,35 +1167,6 @@ class PageLayoutController
}
}
/**
* Checks whether the current page has sub pages
*
* @return bool
*/
protected function currentPageHasSubPages(): bool
{
// get workspace id
$workspaceId = (int)$this->getBackendUser()->workspace;
/** @var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $workspaceId));
$count = $queryBuilder
->count('uid')
->from('pages')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT))
)
->execute()
->fetchColumn(0);
return (bool)$count;
}
/**
* Returns the target page if visible
*
......@@ -1240,4 +1178,74 @@ class PageLayoutController
{
return !(bool)($targetPage['hidden'] ?? false) ? $targetPage : [];
}
/**
* Creates the search box
*
* @return string HTML for the search box
*/
protected function getSearchBox(): string
{
if (!$this->getBackendUser()->check('modules', 'web_list')) {
return '';
}
$lang = $this->getLanguageService();
$listModule = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('web_list', ['id' => $this->id]);
// Make level selector:
$opt = [];
// "New" generation of search levels ... based on TS config
$config = BackendUtility::getPagesTSconfig($this->id);
$searchLevelsFromTSconfig = $config['mod.']['web_list.']['searchLevel.']['items.'];
$searchLevelItems = [];
// get translated labels for search levels from pagets
foreach ($searchLevelsFromTSconfig as $keySearchLevel => $labelConfigured) {
$label = $lang->sL('LLL:' . $labelConfigured);
if ($label === '') {
$label = $labelConfigured;
}
$searchLevelItems[$keySearchLevel] = $label;
}
foreach ($searchLevelItems as $kv => $label) {
$opt[] = '<option value="' . $kv . '"' . ($kv === 0 ? ' selected="selected"' : '') . '>'
. htmlspecialchars($label)
. '</option>';
}
$searchLevelLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search_levels');
$searchStringLabel = $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.searchString');
$lMenu = '<select class="form-control" name="search_levels" title="' . htmlspecialchars($searchLevelLabel) . '" id="search_levels">' . implode('', $opt) . '</select>';
return '<div class="db_list-searchbox-form db_list-searchbox-toolbar module-docheader-bar module-docheader-bar-search t3js-module-docheader-bar t3js-module-docheader-bar-search" id="db_list-searchbox-toolbar" style="display: none;">
<form action="' . htmlspecialchars((string)$listModule) . '" method="post">
<div id="typo3-dblist-search">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="form-group col-xs-12">
<label for="search_field">' . htmlspecialchars($searchStringLabel) . ': </label>
<input class="form-control" type="search" placeholder="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')) . '" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchString')) . '" name="search_field" id="search_field" value="" />
</div>
<div class="form-group col-xs-12 col-sm-6">
<label for="search_levels">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.search_levels')) . ': </label>
' . $lMenu . '
</div>
<div class="form-group col-xs-12 col-sm-6">
<label for="showLimit">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.label.limit')) . ': </label>
<input class="form-control" type="number" min="0" max="10000" placeholder="10" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.limit')) . '" name="showLimit" id="showLimit" value="" />
</div>
<div class="form-group col-xs-12">
<div class="form-control-wrap">
<button type="submit" class="btn btn-default" name="search" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.search')) . '">
' . $this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.search')) . '
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>';
}
}
<?php
namespace TYPO3\CMS\Backend\Tests\Functional\Controller;
/*
* 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\Backend\Controller\PageLayoutController;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
/**
* Functional database test for PageLayoutController behaviour
*/
class PageLayoutControllerTest extends FunctionalTestCase
{
use SiteBasedTestTrait;
/**
* @var string[]
*/
protected $coreExtensionsToLoad = ['workspaces'];
/**
* @var PageLayoutController|AccessibleObjectInterface
*/
private $subject;
/**
* @var BackendUserAuthentication
*/
private $backendUser;
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
static::initializeDatabaseSnapshot();
}
public static function tearDownAfterClass(): void
{
static::destroyDatabaseSnapshot();
parent::tearDownAfterClass();
}
protected function setUp(): void
{
parent::setUp();
$this->subject = $this->getAccessibleMock(PageLayoutController::class, ['dummy']);
$this->backendUser = $this->setUpBackendUserFromFixture(1);
$this->withDatabaseSnapshot(function () {
$this->setUpDatabase();
});
}
protected function tearDown(): void
{
unset($this->subject, $this->backendUser);
parent::tearDown();
}
protected function setUpDatabase()
{
Bootstrap::initializeLanguageObject();
$scenarioFile = __DIR__ . '/../Fixtures/CommonScenario.yaml';
$factory = DataHandlerFactory::fromYamlFile($scenarioFile);
$writer = DataHandlerWriter::withBackendUser($this->backendUser);
$writer->invokeFactory($factory);
static::failIfArrayIsNotEmpty(
$writer->getErrors()
);
}
/**
* @return array
*/
public function currentPageHasSubPagesDataProvider(): array
{
return [
'live workspace with live & version sub pages' => [
1000,
0,
true
],
'draft workspace with live & version sub pages' => [
1000,
1,
true
],
'live workspace with version sub pages only' => [
1950,
0,
false
],
'draft workspace with version sub pages only' => [
1950,
1,
true
],
'live workspace with live sub pages only' => [
2000,
0,
true
],
'draft workspace with live sub pages only' => [
2000,
1,
true
],
];
}
/**
* @param int $pageId
* @param int $workspaceId
* @param bool $expectation
*
* @dataProvider currentPageHasSubPagesDataProvider
* @test
*/
public function currentPageHasSubPages(int $pageId, int $workspaceId, bool $expectation)
{
$this->backendUser->workspace = $workspaceId;
$this->subject->_set('id', $pageId);
$actualResult = $this->subject->_call('currentPageHasSubPages');
self::assertSame($expectation, $actualResult);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment