Commit 6501ef5c authored by Jochen Roth's avatar Jochen Roth Committed by Benni Mack
Browse files

[TASK] Make filelist PHP8 compatible

Added fallback value for array undefined
keys for filelist as well as for impexp
because it is part of the current clipboard
fuctionality. Introduced new tests for
clipboard and file upload.

Resolves: #94509
Releases: master
Change-Id: Ibed7c53f49665c4502aaa05dbe78f468d354f3a1
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69771


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 06bca9f1
......@@ -191,7 +191,7 @@ class Clipboard
$this->changed = 1;
}
// Set copy mode of the tab
if ($cmd['setCopyMode'] ?? false) {
if (isset($cmd['setCopyMode'])) {
$this->clipData[$this->current]['mode'] = $cmd['setCopyMode'] ? 'copy' : '';
$this->changed = 1;
}
......
......@@ -220,7 +220,7 @@ class FileController
} else {
$mode = key($this->file);
$elementKey = key($this->file[$mode]);
$this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect']);
$this->redirect = GeneralUtility::sanitizeLocalUrl($this->file[$mode][$elementKey]['redirect'] ?? '');
}
$this->CB = $parsedBody['CB'] ?? $queryParams['CB'] ?? null;
......@@ -247,7 +247,7 @@ class FileController
$clipObj->setCurrentPad($this->CB['pad']);
$this->file = $clipObj->makePasteCmdArray_file($this->CB['paste'], $this->file);
}
if ($this->CB['delete']) {
if ($this->CB['delete'] ?? false) {
$clipObj->setCurrentPad($this->CB['pad']);
$this->file = $clipObj->makeDeleteCmdArray_file($this->file);
}
......
......@@ -101,7 +101,7 @@ class MetaDataAspect implements \ArrayAccess, \Countable, \Iterator
*/
public function offsetGet($offset)
{
return $this->get()[$offset];
return $this->get()[$offset] ?? null;
}
/**
......
......@@ -498,7 +498,7 @@ class ProcessedFile extends AbstractFile
*/
public function getUid()
{
return $this->properties['uid'];
return $this->properties['uid'] ?? 0;
}
/**
......
......@@ -643,7 +643,7 @@ class ExtendedFileUtility extends BasicFileUtility
return false;
}
// If this is TRUE, we append _XX to the file name if
$appendSuffixOnConflict = (string)$cmds['altName'];
$appendSuffixOnConflict = (string)($cmds['altName'] ?? '');
$resultObject = null;
$conflictMode = $appendSuffixOnConflict !== '' ? DuplicationBehavior::RENAME : DuplicationBehavior::CANCEL;
// Copying the file
......@@ -731,7 +731,7 @@ class ExtendedFileUtility extends BasicFileUtility
$this->addMessageToFlashMessageQueue('FileUtility.DestinationWasNotADirectory', [$cmds['target']]);
return false;
}
$alternativeName = (string)$cmds['altName'];
$alternativeName = (string)($cmds['altName'] ?? '');
$resultObject = null;
// Moving the file
if ($sourceFileObject instanceof File) {
......@@ -1144,7 +1144,7 @@ class ExtendedFileUtility extends BasicFileUtility
return false;
}
$keepFileName = ($cmdArr['keepFilename'] == 1) ? true : false;
$keepFileName = (bool)($cmdArr['keepFilename'] ?? false);
$resultObjects = [];
try {
......
......@@ -1406,9 +1406,9 @@ class GeneralUtility
// If the value is an array then we will call this function recursively:
if (is_array($v)) {
// Sub elements:
if (isset($options['alt_options']) && $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName]) {
$subOptions = $options['alt_options'][$stackData['path'] . '/' . $tagName];
$clearStackPath = $subOptions['clearStackPath'];
if (isset($options['alt_options']) && ($options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName] ?? false)) {
$subOptions = $options['alt_options'][($stackData['path'] ?? '') . '/' . $tagName];
$clearStackPath = (bool)($subOptions['clearStackPath'] ?? false);
} else {
$subOptions = $options;
$clearStackPath = false;
......@@ -1443,7 +1443,7 @@ class GeneralUtility
if (isset($options['useCDATA']) && $options['useCDATA'] && $content != $v) {
$content = '<![CDATA[' . $v . ']]>';
}
} elseif (!$options['disableTypeAttrib']) {
} elseif (!($options['disableTypeAttrib'] ?? false)) {
$attr .= ' type="' . $dType . '"';
}
}
......
<?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\FileList;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\RemoteWebElement;
use TYPO3\CMS\Core\Tests\Acceptance\Support\BackendTester;
use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\FileTree;
/**
* Abstract class for file operations
*/
abstract class AbstractFileCest
{
/**
* @param BackendTester $I
*/
public function _before(BackendTester $I, FileTree $tree)
{
$I->useExistingSession('admin');
$I->amOnPage('/typo3/module/file/FilelistList');
$I->switchToContentFrame();
}
/**
* @param BackendTester $I
* @param string $name
* @throws \Exception
*/
protected function uploadFile(BackendTester $I, string $name): void
{
$I->attachFile('input.upload-file-picker', 'Acceptance/Fixtures/Images/' . $name);
$I->waitForElementNotVisible('.upload-queue-item .upload-queue-progress');
}
/**
* @param BackendTester $I
* @param string $title
* @param string $action
* @return RemoteWebElement
*/
protected function getActionByTitle(BackendTester $I, string $title, string $action): RemoteWebElement
{
$I->comment('Get action in table row "' . $title . '"');
return $I->executeInSelenium(
function (RemoteWebDriver $webDriver) use ($title, $action) {
return $webDriver->findElement(
\Facebook\WebDriver\WebDriverBy::xpath(
'//a[contains(text(),"' . $title . '")]/parent::node()/parent::node()//a[@data-bs-original-title="' . $action . '"]'
)
);
}
);
}
}
<?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\FileList;
use TYPO3\CMS\Core\Tests\Acceptance\Support\BackendTester;
use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\FileTree;
/**
* Cases concerning sys_file_metadata records
*/
class FileClipboardCest extends AbstractFileCest
{
protected string $modeSelector = '#copymodeSelector';
protected string $modeDropDownSelector = 'ul[aria-labelledby="copymodeSelector"]';
protected string $alertContainer = '#alert-container';
protected string $withinTree = '#typo3-filestoragetree .nodes';
/**
* @param BackendTester $I
*/
public function _before(BackendTester $I, FileTree $tree)
{
parent::_before($I, $tree);
$I->click('#checkClipBoard');
}
/**
* @param BackendTester $I
*/
public function seeSwitchModes(BackendTester $I)
{
$I->click($this->modeSelector);
$I->click('Copy elements', $this->modeDropDownSelector);
$I->see('Copy elements', $this->modeSelector);
$I->click($this->modeSelector);
$I->click('Move elements', $this->modeDropDownSelector);
$I->see('Move elements', $this->modeSelector);
}
/**
* @param BackendTester $I
*/
public function seeAddRemoveSingleRecord(BackendTester $I)
{
$fileName = 'bus_lane.jpg';
$I->switchToMainFrame();
$I->click('//*[text()="styleguide"]');
$I->switchToContentFrame();
$this->getActionByTitle($I, $fileName, 'Cut')->click();
$I->see($fileName, '#clipboard_form');
$I->click('#clipboard_form a[title="Remove item"]');
}
/**
* @param BackendTester $I
*/
public function seeAddRemoveMultipleRecords(BackendTester $I)
{
$expectedFiles = ['bus_lane.jpg', 'telephone_box.jpg', 'underground.jpg'];
$I->switchToMainFrame();
$I->click('//*[text()="styleguide"]');
$I->switchToContentFrame();
$I->amGoingTo('add multiple elements to clipboard');
$I->click('Clipboard #1 (multi-selection mode)');
$I->click('.t3js-toggle-all-checkboxes');
$I->click('span[title="Transfer the selection of files to clipboard"]');
foreach ($expectedFiles as $file) {
$I->see($file, '#clipboard_form');
}
$I->amGoingTo('remove all elements from clipboard');
$I->click('a[title="Clear"]');
foreach ($expectedFiles as $file) {
$I->dontSee($file, '#clipboard_form');
}
}
}
......@@ -23,18 +23,8 @@ use TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\ModalDialog;
/**
* Cases concerning sys_file_metadata records
*/
class FileOperationsCest
class FileOperationsCest extends AbstractFileCest
{
/**
* @param BackendTester $I
*/
public function _before(BackendTester $I)
{
$I->useExistingSession('admin');
$I->amOnPage('/typo3/module/file/FilelistList');
$I->switchToContentFrame();
}
/**
* @param BackendTester $I
*/
......@@ -77,4 +67,23 @@ class FileOperationsCest
$I->see('File deleted', $flashMessageSelector);
$I->dontSee($fileName, '.col-title');
}
/**
* @param BackendTester $I
* @throws \Exception
*/
public function seeUploadFile(BackendTester $I)
{
$alertContainer = '#alert-container';
$fileName = 'blue_mountains.jpg';
$this->uploadFile($I, $fileName);
$I->switchToMainFrame();
$I->waitForText($fileName, 10, $alertContainer);
$I->click('.close', $alertContainer);
$I->switchToContentFrame();
$I->see($fileName, '.upload-queue-item');
$I->click('a[title="Reload"]');
$I->see($fileName, '.responsive-title');
}
}
......@@ -656,12 +656,13 @@ class FileList
public function linkWrapFile($code, File $fileObject)
{
try {
if ($this->isEditMetadataAllowed($fileObject)) {
$metaData = $fileObject->getMetaData()->get();
if ($this->isEditMetadataAllowed($fileObject)
&& ($metaDataUid = $fileObject->getMetaData()->offsetGet('uid'))
) {
$urlParameters = [
'edit' => [
'sys_file_metadata' => [
$metaData['uid'] => 'edit'
$metaDataUid => 'edit'
]
],
'returnUrl' => $this->listURL()
......@@ -729,8 +730,10 @@ class FileList
'type' => 'file',
'file-uid' => $fileUid
];
if ($this->isEditMetadataAllowed($fileObject) && $fileObject->getMetaData()->offsetExists('uid')) {
$theData['metadata-uid'] = htmlspecialchars((string)$fileObject->getMetaData()->offsetGet('uid'));
if ($this->isEditMetadataAllowed($fileObject)
&& ($metaDataUid = $fileObject->getMetaData()->offsetGet('uid'))
) {
$theData['metadata-uid'] = htmlspecialchars((string)$metaDataUid);
}
foreach ($this->fieldArray as $field) {
switch ($field) {
......@@ -775,15 +778,14 @@ class FileList
$url = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
$languageCode .= '<a href="' . htmlspecialchars($url) . '" class="btn btn-default" title="' . $title . '">'
. $flagButtonIcon . '</a>';
} else {
} elseif ($metaDataRecord['uid'] ?? false) {
$parameters = [
'justLocalized' => 'sys_file_metadata:' . $metaDataRecord['uid'] . ':' . $languageId,
'returnUrl' => $this->listURL()
];
$returnUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters);
$href = BackendUtility::getLinkToDataHandlerAction(
'&cmd[sys_file_metadata][' . $metaDataRecord['uid'] . '][localize]=' . $languageId,
$returnUrl
(string)$this->uriBuilder->buildUriFromRoute('record_edit', $parameters)
);
$flagButtonIcon = '<span title="' . htmlspecialchars(sprintf($this->getLanguageService()->getLL('createMetadataForLanguage'), $language['title'])) . '">' . $this->iconFactory->getIcon($flagIcon, Icon::SIZE_SMALL, 'overlay-new')->render() . '</span>';
$languageCode .= '<a href="' . htmlspecialchars($href) . '" class="btn btn-default">' . $flagButtonIcon . '</a> ';
......@@ -855,7 +857,7 @@ class FileList
->where(
$queryBuilder->expr()->eq(
$GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'],
$queryBuilder->createNamedParameter($metaDataRecord['uid'], \PDO::PARAM_INT)
$queryBuilder->createNamedParameter($metaDataRecord['uid'] ?? 0, \PDO::PARAM_INT)
),
$queryBuilder->expr()->gt(
$GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'],
......@@ -1007,12 +1009,14 @@ class FileList
}
// Edit metadata of file
if ($fileOrFolderObject instanceof File && $this->isEditMetadataAllowed($fileOrFolderObject)) {
$metaData = $fileOrFolderObject->getMetaData()->get();
if ($fileOrFolderObject instanceof File
&& $this->isEditMetadataAllowed($fileOrFolderObject)
&& ($metaDataUid = $fileOrFolderObject->getMetaData()->offsetGet('uid'))
) {
$urlParameters = [
'edit' => [
'sys_file_metadata' => [
$metaData['uid'] => 'edit'
$metaDataUid => 'edit'
]
],
'returnUrl' => $this->listURL()
......
......@@ -445,7 +445,7 @@ class ExportController extends ImportExportController
{
$nameSuggestion = '';
// Page tree export options:
if (MathUtility::canBeInterpretedAsInteger($inData['pagetree']['id'])) {
if (MathUtility::canBeInterpretedAsInteger($inData['pagetree']['id'] ?? '')) {
$this->standaloneView->assign('treeHTML', $this->treeHTML);
$opt = [
......@@ -460,10 +460,10 @@ class ExportController extends ImportExportController
];
$this->standaloneView->assign('levelSelectOptions', $opt);
$this->standaloneView->assign('tableSelectOptions', $this->getTableSelectOptions('pages'));
$nameSuggestion .= 'tree_PID' . $inData['pagetree']['id'] . '_L' . $inData['pagetree']['levels'];
$nameSuggestion .= 'tree_PID' . ($inData['pagetree']['id'] ?? 0) . '_L' . ($inData['pagetree']['levels'] ?? 0);
}
// Single record export:
if (is_array($inData['record'])) {
if (is_array($inData['record'] ?? null)) {
$records = [];
foreach ($inData['record'] as $ref) {
$rParts = explode(':', $ref);
......@@ -483,7 +483,7 @@ class ExportController extends ImportExportController
}
// Single tables/pids:
if (is_array($inData['list'])) {
if (is_array($inData['list'] ?? false)) {
// Display information about pages from which the export takes place
$tableList = [];
foreach ($inData['list'] as $reference) {
......@@ -534,7 +534,7 @@ class ExportController extends ImportExportController
*/
protected function makeSaveForm(array $inData): void
{
$opt = $this->presetRepository->getPresets((int)$inData['pagetree']['id']);
$opt = $this->presetRepository->getPresets((int)($inData['pagetree']['id'] ?? 0));
$this->standaloneView->assign('presetSelectOptions', $opt);
......
......@@ -231,10 +231,10 @@ class Export extends ImportExport
{
if (is_array($idH)) {
foreach ($idH as $k => $v) {
if ($this->excludeMap['pages:' . $idH[$k]['uid']]) {
if ($this->isExcluded('pages', (int)($v['uid'] ?? 0))) {
unset($idH[$k]);
} elseif (is_array($idH[$k]['subrow'])) {
$idH[$k]['subrow'] = $this->unsetExcludedSections($idH[$k]['subrow']);
} elseif (is_array($v['subrow'] ?? null)) {
$idH[$k]['subrow'] = $this->unsetExcludedSections($v['subrow']);
}
}
}
......@@ -286,7 +286,7 @@ class Export extends ImportExport
if ($this->excludeDisabledRecords && !$this->isActive($table, $row['uid'])) {
return;
}
if ((string)$table !== '' && is_array($row) && $row['uid'] > 0 && !$this->excludeMap[$table . ':' . $row['uid']]) {
if ((string)$table !== '' && is_array($row) && (int)($row['uid'] ?? 0) > 0 && !$this->isExcluded($table, (int)$row['uid'])) {
if ($this->checkPID($table === 'pages' ? $row['uid'] : $row['pid'])) {
if (!isset($this->dat['records'][$table . ':' . $row['uid']])) {
// Prepare header info:
......@@ -458,7 +458,7 @@ class Export extends ImportExport
}
}
// In any case, if there are soft refs:
if (is_array($vR['softrefs']['keys'])) {
if (is_array($vR['softrefs']['keys'] ?? false)) {
foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
foreach ($elements as $el) {
if ($el['subst']['type'] === 'db' && $this->includeSoftref($el['subst']['tokenID'])) {
......@@ -593,7 +593,7 @@ class Export extends ImportExport
}
}
// In any case, if there are soft refs:
if (is_array($vR['softrefs']['keys'])) {
if (is_array($vR['softrefs']['keys'] ?? false)) {
foreach ($vR['softrefs']['keys'] as $spKey => $elements) {
foreach ($elements as $subKey => $el) {
if ($el['subst']['type'] === 'file' && $this->includeSoftref($el['subst']['tokenID'])) {
......@@ -832,7 +832,7 @@ class Export extends ImportExport
{
$list = [];
foreach ($dbrels as $field => $dat) {
if (is_array($dat['softrefs']['keys'])) {
if (is_array($dat['softrefs']['keys'] ?? false)) {
foreach ($dat['softrefs']['keys'] as $spKey => $elements) {
if (is_array($elements)) {
foreach ($elements as $subKey => $el) {
......
......@@ -309,7 +309,7 @@ abstract class ImportExport
// Traverse header:
$this->remainHeader = $this->dat['header'];
// If there is a page tree set, show that:
if (is_array($this->dat['header']['pagetree'])) {
if (is_array($this->dat['header']['pagetree'] ?? null)) {
reset($this->dat['header']['pagetree']);
$lines = [];
$this->traversePageTree($this->dat['header']['pagetree'], $lines);
......@@ -320,7 +320,11 @@ abstract class ImportExport
if (!empty($lines)) {
foreach ($lines as &$r) {
$r['controls'] = $this->renderControls($r);
$r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
if (($r['msg'] ?? false) && !$this->doesImport) {
$r['message'] = '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>';
} else {
$r['message'] = '';
}
}
$viewData['pagetreeLines'] = $lines;
} else {
......@@ -328,16 +332,20 @@ abstract class ImportExport
}
}
// Print remaining records that were not contained inside the page tree:
if (is_array($this->remainHeader['records'])) {
if (is_array($this->remainHeader['records'] ?? null)) {
$lines = [];
if (is_array($this->remainHeader['records']['pages'])) {
if (is_array($this->remainHeader['records']['pages'] ?? null)) {
$this->traversePageRecords($this->remainHeader['records']['pages'], $lines);
}
$this->traverseAllRecords($this->remainHeader['records'], $lines);
if (!empty($lines)) {
foreach ($lines as &$r) {
$r['controls'] = $this->renderControls($r);
$r['message'] = ($r['msg'] && !$this->doesImport ? '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>' : '');
if (($r['msg'] ?? false) && !$this->doesImport) {
$r['message'] = '<span class="text-danger">' . htmlspecialchars($r['msg']) . '</span>';
} else {
$r['message'] = '';
}
}
$viewData['remainingRecords'] = $lines;
}
......@@ -389,13 +397,11 @@ abstract class ImportExport
* @param int $uid Database uid of the record
* @return bool true if the record is active, false otherwise
*/
protected function isActive($table, $uid)
protected function isActive($table, $uid): bool
{
return
!isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])
|| !(bool)$this->dat['records'][$table . ':' . $uid]['data'][
$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']
];
return !($this->dat['records'][$table . ':' . $uid]['data'][
$GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'] ?? ''
] ?? false);
}
/**
......@@ -604,12 +610,12 @@ abstract class ImportExport
}
$pInfo['type'] = 'record';
$lines[] = $pInfo;
// File relations:
if (is_array($record['filerefs'])) {
// File relations
if (is_array($record['filerefs'] ?? null)) {
$this->addFiles($record['filerefs'], $lines, $preCode);
}
// DB relations
if (is_array($record['rels'])) {
if (is_array($record['rels'] ?? null)) {
$this->addRelations($record['rels'], $lines, $preCode);
}
// Soft ref
......@@ -677,9 +683,9 @@ abstract class ImportExport
$staticFixed = false;
$record = null;
if ($uid > 0) {
$record = $this->dat['header']['records'][$table][$uid];
$record = $this->dat['header']['records'][$table][$uid] ?? null;
if (!is_array($record)) {
if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || $dat['tokenID'] && !$this->includeSoftref($dat['tokenID'])) {
if ($this->isTableStatic($table) || $this->isExcluded($table, $uid) || (($dat['tokenID'] ?? '') && !$this->includeSoftref($dat['tokenID'] ?? ''))) {
$pInfo['title'] = htmlspecialchars('STATIC: ' . $pInfo['ref']);
$iconClass = 'text-info';
$staticFixed = true;
......@@ -992,7 +998,7 @@ abstract class ImportExport
public function isTableStatic($table)
{
if (is_array($GLOBALS['TCA'][$table])) {
return $GLOBALS['TCA'][$table]['ctrl']['is_static'] || in_array($table,