Commit 3a93bc53 authored by Jochen Roth's avatar Jochen Roth Committed by Benni Mack
Browse files

[BUGFIX] Fix Undefined array key error in PHP8 for EXT:workspaces

Will fix creation/edit content as well as publishing
workflow for workspaces and add a basic ac test
for page change, switch workspace and publish

Also EXT:workspaces is now enabled in test environment

Resolves: #94361
Releases: master
Change-Id: I9f85a4edaa90e77cbdfc5f988007917263ed51ce
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69508


Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent b28ab075
......@@ -76,10 +76,6 @@ parameters:
message: "#^Variable \\$pidConf in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
-
message: "#^Variable \\$column in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: typo3/sysext/workspaces/Classes/Service/GridDataService.php
-
message: "#^Constructor of class TYPO3\\\\CMS\\\\Frontend\\\\Plugin\\\\AbstractPlugin has an unused parameter \\$_\\.$#"
count: 1
......
......@@ -2332,7 +2332,7 @@ class EditDocumentController
$ids = GeneralUtility::trimExplode(',', $cKey, true);
foreach ($ids as $idKey => $theUid) {
if (is_array($mapArray)) {
if ($mapArray[$table][$theUid]) {
if ($mapArray[$table][$theUid] ?? false) {
$ids[$idKey] = $mapArray[$table][$theUid];
}
} else {
......@@ -2375,7 +2375,7 @@ class EditDocumentController
// Check for versioning support of the table:
if ($tableSupportsVersioning) {
// If the record is already a version of "something" pass it by.
if ($reqRecord['t3ver_oid'] > 0 || (int)$reqRecord['t3ver_state'] === VersionState::NEW_PLACEHOLDER) {
if ($reqRecord['t3ver_oid'] > 0 || (int)($reqRecord['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
// (If it turns out not to be a version of the current workspace there will be trouble, but
// that is handled inside DataHandler then and in the interface it would clearly be an error of
// links if the user accesses such a scenario)
......
......@@ -243,9 +243,9 @@ class NewRecordController
$this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause) ?: [];
}
// If a page-record was returned, the user had read-access to the page.
if ($this->pageinfo['uid']) {
if ($this->pageinfo['uid'] ?? false) {
// Get record of parent page
$this->pidInfo = BackendUtility::getRecord('pages', $this->pageinfo['pid']) ?: [];
$this->pidInfo = BackendUtility::getRecord('pages', ($this->pageinfo['pid'] ?? 0)) ?? [];
// Checking the permissions for the user with regard to the parent page: Can he create new pages, new
// content record, new page after?
if ($beUser->doesUserHaveAccess($this->pageinfo, 8)) {
......@@ -282,11 +282,11 @@ class NewRecordController
if (!empty($this->pageinfo['uid']) || $this->getBackendUserAuthentication()->isAdmin()) {
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
// Acquiring TSconfig for this module/current page:
$this->web_list_modTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid'])['mod.']['web_list.'] ?? [];
$this->web_list_modTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid'] ?? 0)['mod.']['web_list.'] ?? [];
$this->allowedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['allowedNewTables'] ?? '', true);
$this->deniedNewTables = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig['deniedNewTables'] ?? '', true);
// Acquiring TSconfig for this module/parent page:
$this->web_list_modTSconfig_pid = BackendUtility::getPagesTSconfig($this->pageinfo['pid'])['mod.']['web_list.'] ?? [];
$this->web_list_modTSconfig_pid = BackendUtility::getPagesTSconfig($this->pageinfo['pid'] ?? 0)['mod.']['web_list.'] ?? [];
$this->allowedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['allowedNewTables'] ?? '', true);
$this->deniedNewTables_pid = GeneralUtility::trimExplode(',', $this->web_list_modTSconfig_pid['deniedNewTables'] ?? '', true);
// More init:
......@@ -363,7 +363,7 @@ class NewRecordController
$buttonBar->addButton($returnButton, ButtonBar::BUTTON_POSITION_LEFT, 10);
}
if (is_array($this->pageinfo) && $this->pageinfo['uid']) {
if ($this->pageinfo['uid'] ?? false) {
// View
$pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
......
......@@ -1975,7 +1975,7 @@ class BackendUtility
$fVnew = self::getProcessedValue($table, $fN, $fV, $fixed_lgd_chars, true, false, $uid, $forceResult, $pid);
if (!isset($fVnew)) {
if (is_array($GLOBALS['TCA'][$table])) {
if ($fN == $GLOBALS['TCA'][$table]['ctrl']['tstamp'] || $fN == $GLOBALS['TCA'][$table]['ctrl']['crdate']) {
if ($fN == ($GLOBALS['TCA'][$table]['ctrl']['tstamp'] ?? 0) || $fN == ($GLOBALS['TCA'][$table]['ctrl']['crdate'] ?? 0)) {
$fVnew = self::datetime((int)$fV);
} elseif ($fN === 'pid') {
// Fetches the path with no regard to the users permissions to select pages.
......
......@@ -1085,10 +1085,10 @@ class DataHandler implements LoggerAwareInterface
$fieldArray = $this->overrideFieldArray($table, $fieldArray);
// Setting system fields
if ($status === 'new') {
if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
if ($GLOBALS['TCA'][$table]['ctrl']['crdate'] ?? false) {
$fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
}
if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id'] ?? false) {
$fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid;
}
} elseif ($this->checkSimilar) {
......
<?php
declare(strict_types=1);
/*
* 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!
*/
namespace TYPO3\CMS\Core\Tests\Acceptance\Backend\Page;
use TYPO3\CMS\Core\Tests\Acceptance\Support\BackendTester;
use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\ModalDialog;
use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\PageTree;
use TYPO3\TestingFramework\Core\Acceptance\Helper\Topbar;
class WorkspaceModuleCest
{
public static string $topBarModuleSelector = '#typo3-cms-workspaces-backend-toolbaritems-workspaceselectortoolbaritem';
public static string $currentPageTitle = 'styleguide TCA demo';
public static string $newPageTitle = 'styleguide TCA demo workspace';
/**
* @param BackendTester $I
*/
public function _before(BackendTester $I)
{
$I->useExistingSession('admin');
}
/**
* @param BackendTester $I
*/
public function switchToWorkspace(BackendTester $I)
{
$I->click(Topbar::$dropdownToggleSelector, self::$topBarModuleSelector);
$I->canSee('Test Workspace', self::$topBarModuleSelector);
$I->click('Test Workspace', self::$topBarModuleSelector);
$I->canSeeElement('body.typo3-in-workspace');
$I->click(Topbar::$dropdownToggleSelector, self::$topBarModuleSelector);
$I->see('Test Workspace', '#typo3-cms-workspaces-backend-toolbaritems-workspaceselectortoolbaritem .selected');
}
/**
* @depends switchToWorkspace
* @param BackendTester $I
* @param PageTree $pageTree
*/
public function editPageTitleAndSeeChangeInWorkspaceModule(BackendTester $I, PageTree $pageTree): void
{
$currentPageTitle = 'styleguide TCA demo';
$newPageTitle = 'styleguide TCA demo workspace';
$I->click('Page');
$pageTree->openPath([$currentPageTitle]);
$I->waitForElement('#typo3-pagetree-tree .nodes .node', 5);
$I->comment('Rename page');
$I->switchToContentFrame();
$I->click('button[data-action="edit"]');
$I->fillField('input[class*="t3js-title-edit-input"]', $newPageTitle);
$I->click('button[data-action="submit"]');
$I->switchToMainFrame();
$I->click(Topbar::$dropdownToggleSelector, self::$topBarModuleSelector);
$I->canSee('Go to Workspace Module', self::$topBarModuleSelector);
$I->click('Go to Workspace Module', self::$topBarModuleSelector);
$I->comment('See the new page title in Workspace module');
$I->switchToContentFrame();
$I->see($newPageTitle, '#workspace-panel');
}
/**
* @depends editPageTitleAndSeeChangeInWorkspaceModule
* @param BackendTester $I
* @param ModalDialog $modalDialog
*/
public function chooseMassActionPublish(BackendTester $I, ModalDialog $modalDialog)
{
$I->click('Workspaces');
$I->switchToContentFrame();
$I->selectOption('select[name=mass-action]', 'Publish');
$modalDialog->canSeeDialog();
$modalDialog->clickButtonInDialog('Next');
$I->dontSee(self::$newPageTitle, '#workspace-panel');
}
}
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<sys_workspace>
<uid>1</uid>
<pid>0</pid>
<title>Test Workspace</title>
<deleted>0</deleted>
</sys_workspace>
</dataset>
......@@ -52,7 +52,8 @@ class BackendCoreEnvironment extends BackendEnvironment
'sys_note',
'scheduler',
'tstemplate',
'lowlevel'
'lowlevel',
'workspaces'
],
'testExtensionsToLoad' => [
'typo3conf/ext/styleguide'
......@@ -64,6 +65,7 @@ class BackendCoreEnvironment extends BackendEnvironment
'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/sys_category.xml',
'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml',
'typo3/sysext/core/Tests/Acceptance/Fixtures/pages.xml',
'typo3/sysext/core/Tests/Acceptance/Fixtures/workspaces.xml',
],
'configurationToUseInTestInstance' => [
'MAIL' => [
......
......@@ -135,8 +135,8 @@ class CachingFrameworkGarbageCollectionAdditionalFieldProvider extends AbstractA
$cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'];
if (is_array($cacheConfigurations)) {
foreach ($cacheConfigurations as $cacheConfiguration) {
$backend = $cacheConfiguration['backend'];
if (!in_array($backend, $backends)) {
$backend = (string)($cacheConfiguration['backend'] ?? '');
if (!in_array($backend, $backends, true)) {
$backends[] = $backend;
}
}
......
......@@ -42,7 +42,7 @@ class AjaxController
$parsedBody = $request->getParsedBody();
$queryParams = $request->getQueryParams();
$workspaceId = (int)($parsedBody['workspaceId'] ?? $queryParams['workspaceId']);
$pageId = (int)($parsedBody['pageId'] ?? $queryParams['pageId']);
$pageId = (int)($parsedBody['pageId'] ?? $queryParams['pageId'] ?? 0);
$finalPageUid = 0;
$originalPageId = $pageId;
......
......@@ -448,7 +448,7 @@ class ActionHandler
if (!is_object($parameters->affects) || empty($parameters->affects)) {
throw new \InvalidArgumentException('Missing "affected items" in $parameters array.', 1319488195);
}
$recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $stageId);
$recipients = $this->getRecipientList((array)($parameters->recipients ?? []), $parameters->additional, $stageId);
foreach ($parameters->affects as $tableName => $items) {
foreach ($items as $item) {
// Publishing uses live id in command map
......@@ -524,7 +524,7 @@ class ActionHandler
$uid = $parameters->affects->uid;
$t3ver_oid = $parameters->affects->t3ver_oid;
$recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
$recipients = $this->getRecipientList((array)($parameters->recipients ?? []), $parameters->additional, $setStageId);
if ($setStageId === StagesService::STAGE_PUBLISH_EXECUTE_ID) {
$cmdArray[$table][$t3ver_oid]['version']['action'] = 'publish';
$cmdArray[$table][$t3ver_oid]['version']['swapWith'] = $uid;
......@@ -566,7 +566,7 @@ class ActionHandler
$table = $parameters->affects->table;
$uid = $parameters->affects->uid;
$recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
$recipients = $this->getRecipientList((array)($parameters->recipients ?? []), $parameters->additional, $setStageId);
$cmdArray[$table][$uid]['version']['action'] = 'setStage';
$cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
$cmdArray[$table][$uid]['version']['comment'] = $comments;
......@@ -606,7 +606,7 @@ class ActionHandler
$setStageId = (int)$parameters->affects->nextStage;
$comments = $parameters->comments;
$elements = $parameters->affects->elements;
$recipients = $this->getRecipientList((array)$parameters->recipients, $parameters->additional, $setStageId);
$recipients = $this->getRecipientList((array)($parameters->recipients ?? []), $parameters->additional, $setStageId);
foreach ($elements as $element) {
// Avoid any action on records that have already been published to live
$elementRecord = BackendUtility::getRecord($element->table, $element->uid);
......
......@@ -60,7 +60,7 @@ class MassActionHandler
$massActionsEnabled = (bool)($backendUser->getTSConfig()['options.']['workspaces.']['enableMassActions'] ?? true);
if ($massActionsEnabled) {
$publishAccess = $backendUser->workspacePublishAccess($currentWorkspace);
if ($publishAccess && !($backendUser->workspaceRec['publish_access'] & 1)) {
if ($publishAccess && !(($backendUser->workspaceRec['publish_access'] ?? 0) & 1)) {
$actions[] = ['action' => 'publish', 'title' => $this->getLanguageService()->sL($this->pathToLocallang . ':label_doaction_publish')];
}
if ($currentWorkspace !== WorkspaceService::LIVE_WORKSPACE_ID) {
......
......@@ -215,7 +215,7 @@ class DataHandlerHook
$id = $record['uid'];
}
}
$recordVersionState = VersionState::cast($record['t3ver_state']);
$recordVersionState = VersionState::cast($record['t3ver_state'] ?? 0);
// Look, if record is an offline version, then delete directly:
if ((int)($record['t3ver_oid'] ?? 0) > 0) {
if (BackendUtility::isTableWorkspaceEnabled($table)) {
......
......@@ -138,7 +138,7 @@ class GridDataService implements LoggerAwareInterface
{
$backendUser = $this->getBackendUser();
$workspaceAccess = $backendUser->checkWorkspace($backendUser->workspace);
$swapStage = $workspaceAccess['publish_access'] & 1 ? StagesService::STAGE_PUBLISH_ID : 0;
$swapStage = ($workspaceAccess['publish_access'] ?? 0) & 1 ? StagesService::STAGE_PUBLISH_ID : 0;
$swapAccess = $backendUser->workspacePublishAccess($backendUser->workspace);
$this->initializeWorkspacesCachingFramework();
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
......@@ -177,7 +177,7 @@ class GridDataService implements LoggerAwareInterface
$liveRecordLabel = BackendUtility::getRecordTitle($table, $origRecord);
[$pathWorkspaceCropped, $pathWorkspace] = BackendUtility::getRecordPath((int)$record['wspid'], '', 15, 1000);
$calculatedT3verOid = $record['t3ver_oid'];
if ((int)$record['t3ver_state'] === VersionState::NEW_PLACEHOLDER) {
if ((int)($record['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
// If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
// live counterpart, but the publish methods later need a live uid to publish to. We thus
// use the uid as t3ver_oid here to be transparent on javascript side.
......@@ -198,8 +198,8 @@ class GridDataService implements LoggerAwareInterface
$versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
$versionArray['value_nextStage'] = (int)$tempStage['uid'];
$tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
$versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
$versionArray['value_prevStage'] = (int)$tempStage['uid'];
$versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid'] ?? 0));
$versionArray['value_prevStage'] = (int)($tempStage['uid'] ?? 0);
$versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath($record['livepid'], '', 999));
$versionArray['path_Workspace'] = htmlspecialchars($pathWorkspace);
$versionArray['path_Workspace_crop'] = htmlspecialchars($pathWorkspaceCropped);
......@@ -527,7 +527,7 @@ class GridDataService implements LoggerAwareInterface
protected function isFilterTextInVisibleColumns($filterText, array $versionArray)
{
$backendUser = $this->getBackendUser();
if (is_array($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'])) {
if (is_array($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'] ?? false)) {
$visibleColumns = $backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'];
} else {
$visibleColumns = [
......
......@@ -109,8 +109,10 @@ class HistoryService implements SingletonInterface
{
$differences = [];
$tableName = $entry['tablename'];
if (is_array($entry['newRecord'])) {
if (is_array($entry['newRecord'] ?? false)) {
$fields = array_keys($entry['newRecord']);
/** @var array<int, string> $fields */
foreach ($fields as $field) {
if (!empty($GLOBALS['TCA'][$tableName]['columns'][$field]['config']['type']) && $GLOBALS['TCA'][$tableName]['columns'][$field]['config']['type'] !== 'passthrough') {
// Create diff-result:
......@@ -120,7 +122,7 @@ class HistoryService implements SingletonInterface
);
if (!empty($fieldDifferences)) {
$differences[] = [
'label' => $this->getLanguageService()->sL((string)BackendUtility::getItemLabel($tableName, $field)),
'label' => $this->getLanguageService()->sL((string)BackendUtility::getItemLabel($tableName, (string)$field)),
'html' => nl2br(trim($fieldDifferences)),
];
}
......
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