...
 
Commits (7)
......@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Routing\RouteNotFoundException;
use TYPO3\CMS\Core\Utility\CommandUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
......@@ -168,8 +169,15 @@ class SystemInformationToolbarItem implements ToolbarItemInterface
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$view = $this->getFluidTemplateObject('SystemInformationDropDown.html');
try {
$environmentToolUrl = (string)$uriBuilder->buildUriFromRoute('tools_toolsenvironment');
} catch (RouteNotFoundException $e) {
$environmentToolUrl = '';
}
$view->assignMultiple([
'environmentToolUrl' => (string)$uriBuilder->buildUriFromRoute('tools_toolsenvironment'),
'environmentToolUrl' => $environmentToolUrl,
'messages' => $this->systemMessages,
'count' => $this->totalCount > $this->maximumCountInBadge ? $this->maximumCountInBadge . '+' : $this->totalCount,
'severityBadgeClass' => $this->severityBadgeClass,
......
......@@ -736,7 +736,7 @@ class PageLayoutController
if ($this->id && $access) {
// Initialize permission settings:
$this->CALC_PERMS = $this->getBackendUser()->calcPerms($this->pageinfo);
$this->EDIT_CONTENT = $this->isContentEditable();
$this->EDIT_CONTENT = $this->isContentEditable($this->current_sys_language);
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
......@@ -784,7 +784,7 @@ class PageLayoutController
if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) {
$content .= '<form action="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute($this->moduleName, ['id' => $this->id, 'imagemode' => $this->imagemode])) . '" id="PageLayoutController" method="post">';
// Page title
$content .= '<h1 class="t3js-title-inlineedit">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
$content .= '<h1 class="' . ($this->isPageEditable($this->current_sys_language) ? 't3js-title-inlineedit' : '') . '">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
// All other listings
$content .= $this->renderContent();
}
......@@ -1022,7 +1022,7 @@ class PageLayoutController
}
if (empty($this->modTSconfig['properties']['disableIconToolbar'])) {
// Edit page properties and page language overlay icons
if ($this->isPageEditable() && $this->getBackendUser()->checkLanguageAccess(0)) {
if ($this->isPageEditable(0)) {
/** @var \TYPO3\CMS\Core\Http\NormalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');
// Edit localized pages only when one specific language is selected
......@@ -1198,47 +1198,35 @@ class PageLayoutController
/**
* Check if page can be edited by current user
*
* @param int|null $languageId
* @return bool
*/
protected function isPageEditable(): bool
protected function isPageEditable(int $languageId): bool
{
if ($this->getBackendUser()->isAdmin()) {
return true;
}
return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT);
}
/**
* Check if page can be edited by current user
*
* @return bool
*/
protected function pageIsNotLockedForEditors(): bool
{
return $this->isPageEditable();
return !$this->pageinfo['editlock']
&& $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT)
&& $this->getBackendUser()->checkLanguageAccess($languageId);
}
/**
* Check if content can be edited by current user
*
* @param int $languageId
* @return bool
*/
protected function isContentEditable(): bool
protected function isContentEditable(int $languageId): bool
{
if ($this->getBackendUser()->isAdmin()) {
return true;
}
return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT);
}
/**
* Check if content can be edited by current user
*
* @return bool
*/
protected function contentIsNotLockedForEditors(): bool
{
return $this->isContentEditable();
return !$this->pageinfo['editlock']
&& $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
&& $this->getBackendUser()->checkLanguageAccess($languageId);
}
/**
......
......@@ -187,7 +187,11 @@ class InlineRecordContainer extends AbstractContainer
$class .= ' t3-form-field-container-inline-hidden';
}
$class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
$tableUniqueOriginalValue = $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']] ?? '';
$tableUniqueOriginalValue = '';
if (isset($data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']])) {
$uniqueValueValues = $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']];
$tableUniqueOriginalValue = $uniqueValueValues['table'] . '_' . $uniqueValueValues['uid'];
}
$html = '
<div class="panel panel-default panel-condensed ' . trim($class) . '" id="' . htmlspecialchars($objectId) . '_div" data-table-unique-original-value="' . htmlspecialchars($tableUniqueOriginalValue) . '">
<div class="panel-heading" data-toggle="formengine-inline" id="' . htmlspecialchars($objectId) . '_header" data-expandSingle="' . ($inlineConfig['appearance']['expandSingle'] ? 1 : 0) . '">
......
......@@ -219,7 +219,7 @@ class InputLinkElement extends AbstractFormElement
$expansionHtml[] = '<div class="form-wizards-wrap">';
$expansionHtml[] = '<div class="form-wizards-element">';
$expansionHtml[] = '<div class="input-group t3js-form-field-inputlink">';
$expansionHtml[] = '<span class="input-group-addon">' . $linkExplanation['icon'] . '</span>';
$expansionHtml[] = '<span class="t3js-form-field-inputlink-icon input-group-addon">' . $linkExplanation['icon'] . '</span>';
$expansionHtml[] = '<input class="form-control form-field-inputlink-explanation t3js-form-field-inputlink-explanation" data-toggle="tooltip" data-title="' . $explanation . '" value="' . $explanation . '" readonly>';
$expansionHtml[] = '<input type="text"' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
$expansionHtml[] = '<span class="input-group-btn">';
......
......@@ -166,11 +166,16 @@ class InputSlugElement extends AbstractFormElement
[$commonElementPrefix] = GeneralUtility::revExplode('[', $parameterArray['itemFormElName'], 2);
$validInputNamesToListenTo = [];
$includeUidInValues = false;
foreach ($config['generatorOptions']['fields'] ?? [] as $fieldNameParts) {
if (is_string($fieldNameParts)) {
$fieldNameParts = GeneralUtility::trimExplode(',', $fieldNameParts);
}
foreach ($fieldNameParts as $listenerFieldName) {
if ($listenerFieldName === 'uid') {
$includeUidInValues = true;
continue;
}
$validInputNamesToListenTo[$listenerFieldName] = $commonElementPrefix . '[' . htmlspecialchars($listenerFieldName) . ']';
}
}
......@@ -202,6 +207,7 @@ class InputSlugElement extends AbstractFormElement
'signature' => $signature,
'command' => $this->data['command'],
'parentPageId' => $parentPageId,
'includeUidInValues' => $includeUidInValues,
];
$resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SlugElement' => '
function(SlugElement) {
......
......@@ -1529,7 +1529,7 @@ class PageLayoutView implements LoggerAwareInterface
// Get values:
$Nrow = $this->dataFields($this->fieldArray, $table, $row, $Nrow);
// Attach edit icon
if ($this->doEdit && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) {
if ($this->doEdit) {
$urlParameters = [
'edit' => [
$table => [
......
......@@ -5,11 +5,13 @@
<h3 class="dropdown-headline">
<f:translate key="systemmessage.header"/>
</h3>
<p class="dropdown-text typo3-module-menu-item submodule mod-tools_toolsenvironment" data-modulename="tools_toolsenvironment">
<f:format.raw>
<f:translate key="systemmessage.intro" arguments="{0: '{environmentToolUrl}'}"/>
</f:format.raw>
</p>
<f:if condition="{environmentToolUrl}">
<p class="dropdown-text typo3-module-menu-item submodule mod-tools_toolsenvironment" data-modulename="tools_toolsenvironment">
<f:format.raw>
<f:translate key="systemmessage.intro" arguments="{0: '{environmentToolUrl}'}"/>
</f:format.raw>
</p>
</f:if>
<f:if condition="{systemInformation}">
<hr>
......
......@@ -25,6 +25,7 @@ interface FieldOptions {
signature: string;
command: string;
parentPageId: number;
includeUidInValues: boolean;
}
interface Response {
......@@ -151,6 +152,9 @@ class SlugElement {
$.each(this.getAvailableFieldsForProposalGeneration(), (fieldName: string, field: string): void => {
input[fieldName] = $('[data-formengine-input-name="' + field + '"]').val();
});
if (this.options.includeUidInValues === true) {
input.uid = this.options.recordId.toString();
}
} else {
input.manual = this.$inputField.val();
}
......
......@@ -1073,7 +1073,8 @@ define(['jquery',
FormEngine.initializeInputLinkToggle = function() {
var toggleClass = '.t3js-form-field-inputlink-explanation-toggle',
inputFieldClass = '.t3js-form-field-inputlink-input',
explanationClass = '.t3js-form-field-inputlink-explanation';
explanationClass = '.t3js-form-field-inputlink-explanation',
iconClass = '.t3js-form-field-inputlink-icon';
// if empty, show input field
$(explanationClass).filter(function() {
......@@ -1098,7 +1099,7 @@ define(['jquery',
explanationShown = !$explanationField.hasClass('hidden');
$explanationField.toggleClass('hidden', explanationShown);
$inputField.toggleClass('hidden', !explanationShown);
$group.find('.form-control-clearable button.close').toggleClass('hidden', !explanationShown)
$group.find('.form-control-clearable button.close').toggleClass('hidden', !explanationShown);
});
$(inputFieldClass).on('change', function() {
......@@ -1114,6 +1115,9 @@ define(['jquery',
$inputField.toggleClass('hidden', !explanationShown);
$group.find('.form-control-clearable button.close').toggleClass('hidden', !explanationShown)
}
$group.find(toggleClass).addClass('disabled').attr('disabled', 'disabled');
$group.find(iconClass).empty();
});
};
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","jquery"],function(e,l,n){"use strict";var i,t,d,s;return(t=i||(i={})).toggleButton=".t3js-form-field-slug-toggle",t.recreateButton=".t3js-form-field-slug-recreate",t.inputField=".t3js-form-field-slug-input",t.readOnlyField=".t3js-form-field-slug-readonly",t.hiddenField=".t3js-form-field-slug-hidden",(s=d||(d={})).AUTO="auto",s.RECREATE="recreate",s.MANUAL="manual",function(){function e(e,l){var t=this;this.options=null,this.$fullElement=null,this.manuallyChanged=!1,this.$readOnlyField=null,this.$inputField=null,this.$hiddenField=null,this.xhr=null,this.fieldsToListenOn={},this.options=l,this.fieldsToListenOn=this.options.listenerFieldNames||{},n(function(){t.$fullElement=n(e),t.$inputField=t.$fullElement.find(i.inputField),t.$readOnlyField=t.$fullElement.find(i.readOnlyField),t.$hiddenField=t.$fullElement.find(i.hiddenField),t.registerEvents()})}return e.prototype.registerEvents=function(){var e=this,l=Object.keys(this.getAvailableFieldsForProposalGeneration()).map(function(l){return e.fieldsToListenOn[l]});l.length>0?("new"===this.options.command&&n(this.$fullElement).on("keyup",l.join(","),function(){e.manuallyChanged||e.sendSlugProposal(d.AUTO)}),n(this.$fullElement).on("click",i.recreateButton,function(l){l.preventDefault(),e.$readOnlyField.hasClass("hidden")&&(e.$readOnlyField.toggleClass("hidden",!1),e.$inputField.toggleClass("hidden",!0)),e.sendSlugProposal(d.RECREATE)})):n(this.$fullElement).find(i.recreateButton).addClass("disabled").prop("disabled",!0),n(this.$inputField).on("keyup",function(){e.manuallyChanged=!0,e.sendSlugProposal(d.MANUAL)}),n(this.$fullElement).on("click",i.toggleButton,function(l){l.preventDefault();var n=e.$readOnlyField.hasClass("hidden");e.$readOnlyField.toggleClass("hidden",!n),e.$inputField.toggleClass("hidden",n),n?(e.$inputField.val()!==e.$readOnlyField.val()?e.$readOnlyField.val(e.$inputField.val()):(e.manuallyChanged=!1,e.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),e.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),e.$hiddenField.val(e.$readOnlyField.val())):e.$hiddenField.val(e.$inputField.val())})},e.prototype.sendSlugProposal=function(e){var l=this,i={};e===d.AUTO||e===d.RECREATE?n.each(this.getAvailableFieldsForProposalGeneration(),function(e,l){i[e]=n('[data-formengine-input-name="'+l+'"]').val()}):i.manual=this.$inputField.val(),null!==this.xhr&&4!==this.xhr.readyState&&this.xhr.abort(),this.xhr=n.post(TYPO3.settings.ajaxUrls.record_slug_suggest,{values:i,mode:e,tableName:this.options.tableName,pageId:this.options.pageId,parentPageId:this.options.parentPageId,recordId:this.options.recordId,language:this.options.language,fieldName:this.options.fieldName,command:this.options.command,signature:this.options.signature},function(n){n.hasConflicts?(l.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),l.$fullElement.find(".t3js-form-proposal-different").removeClass("hidden").find("span").text(n.proposal)):(l.$fullElement.find(".t3js-form-proposal-accepted").removeClass("hidden").find("span").text(n.proposal),l.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),l.$hiddenField.val()!==n.proposal&&l.$fullElement.find("input").trigger("change"),e===d.AUTO||e===d.RECREATE?(l.$readOnlyField.val(n.proposal),l.$hiddenField.val(n.proposal),l.$inputField.val(n.proposal)):l.$hiddenField.val(n.proposal)},"json")},e.prototype.getAvailableFieldsForProposalGeneration=function(){var e={};return n.each(this.fieldsToListenOn,function(l,i){n('[data-formengine-input-name="'+i+'"]').length>0&&(e[l]=i)}),e},e}()});
\ No newline at end of file
define(["require","exports","jquery"],function(e,l,n){"use strict";var i,t,d,s;return(t=i||(i={})).toggleButton=".t3js-form-field-slug-toggle",t.recreateButton=".t3js-form-field-slug-recreate",t.inputField=".t3js-form-field-slug-input",t.readOnlyField=".t3js-form-field-slug-readonly",t.hiddenField=".t3js-form-field-slug-hidden",(s=d||(d={})).AUTO="auto",s.RECREATE="recreate",s.MANUAL="manual",function(){function e(e,l){var t=this;this.options=null,this.$fullElement=null,this.manuallyChanged=!1,this.$readOnlyField=null,this.$inputField=null,this.$hiddenField=null,this.xhr=null,this.fieldsToListenOn={},this.options=l,this.fieldsToListenOn=this.options.listenerFieldNames||{},n(function(){t.$fullElement=n(e),t.$inputField=t.$fullElement.find(i.inputField),t.$readOnlyField=t.$fullElement.find(i.readOnlyField),t.$hiddenField=t.$fullElement.find(i.hiddenField),t.registerEvents()})}return e.prototype.registerEvents=function(){var e=this,l=Object.keys(this.getAvailableFieldsForProposalGeneration()).map(function(l){return e.fieldsToListenOn[l]});l.length>0?("new"===this.options.command&&n(this.$fullElement).on("keyup",l.join(","),function(){e.manuallyChanged||e.sendSlugProposal(d.AUTO)}),n(this.$fullElement).on("click",i.recreateButton,function(l){l.preventDefault(),e.$readOnlyField.hasClass("hidden")&&(e.$readOnlyField.toggleClass("hidden",!1),e.$inputField.toggleClass("hidden",!0)),e.sendSlugProposal(d.RECREATE)})):n(this.$fullElement).find(i.recreateButton).addClass("disabled").prop("disabled",!0),n(this.$inputField).on("keyup",function(){e.manuallyChanged=!0,e.sendSlugProposal(d.MANUAL)}),n(this.$fullElement).on("click",i.toggleButton,function(l){l.preventDefault();var n=e.$readOnlyField.hasClass("hidden");e.$readOnlyField.toggleClass("hidden",!n),e.$inputField.toggleClass("hidden",n),n?(e.$inputField.val()!==e.$readOnlyField.val()?e.$readOnlyField.val(e.$inputField.val()):(e.manuallyChanged=!1,e.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),e.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),e.$hiddenField.val(e.$readOnlyField.val())):e.$hiddenField.val(e.$inputField.val())})},e.prototype.sendSlugProposal=function(e){var l=this,i={};e===d.AUTO||e===d.RECREATE?(n.each(this.getAvailableFieldsForProposalGeneration(),function(e,l){i[e]=n('[data-formengine-input-name="'+l+'"]').val()}),!0===this.options.includeUidInValues&&(i.uid=this.options.recordId.toString())):i.manual=this.$inputField.val(),null!==this.xhr&&4!==this.xhr.readyState&&this.xhr.abort(),this.xhr=n.post(TYPO3.settings.ajaxUrls.record_slug_suggest,{values:i,mode:e,tableName:this.options.tableName,pageId:this.options.pageId,parentPageId:this.options.parentPageId,recordId:this.options.recordId,language:this.options.language,fieldName:this.options.fieldName,command:this.options.command,signature:this.options.signature},function(n){n.hasConflicts?(l.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),l.$fullElement.find(".t3js-form-proposal-different").removeClass("hidden").find("span").text(n.proposal)):(l.$fullElement.find(".t3js-form-proposal-accepted").removeClass("hidden").find("span").text(n.proposal),l.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),l.$hiddenField.val()!==n.proposal&&l.$fullElement.find("input").trigger("change"),e===d.AUTO||e===d.RECREATE?(l.$readOnlyField.val(n.proposal),l.$hiddenField.val(n.proposal),l.$inputField.val(n.proposal)):l.$hiddenField.val(n.proposal)},"json")},e.prototype.getAvailableFieldsForProposalGeneration=function(){var e={};return n.each(this.fieldsToListenOn,function(l,i){n('[data-formengine-input-name="'+i+'"]').length>0&&(e[l]=i)}),e},e}()});
\ No newline at end of file
......@@ -16,6 +16,9 @@
"typo3/cms-core": "9.5.*@dev",
"typo3/cms-recordlist": "9.5.*@dev"
},
"suggest": {
"typo3/cms-install": "To generate url to install tool in environment toolbar"
},
"conflict": {
"typo3/cms": "*"
},
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Core\Resource\Exception;
/*
* 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!
*/
/**
* An exception when something is wrong with the Hash
* Is thrown for example when the driver returns an unexpected (non-string) hash value
*/
class InvalidHashException extends \TYPO3\CMS\Core\Resource\Exception
{
}
......@@ -14,8 +14,11 @@ namespace TYPO3\CMS\Core\Resource\Index;
* The TYPO3 project - inspiring people to share!
*/
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
use TYPO3\CMS\Core\Resource\Exception\InvalidHashException;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Resource\ResourceStorage;
......@@ -24,10 +27,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* The New FAL Indexer
* The FAL Indexer
*/
class Indexer
class Indexer implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @var array
*/
......@@ -244,34 +249,43 @@ class Indexer
protected function processChangedAndNewFiles()
{
foreach ($this->filesToUpdate as $identifier => $data) {
if ($data == null) {
// search for files with same content hash in indexed storage
$fileHash = $this->storage->hashFileByIdentifier($identifier, 'sha1');
$files = $this->getFileIndexRepository()->findByContentHash($fileHash);
$fileObject = null;
if (!empty($files)) {
foreach ($files as $fileIndexEntry) {
// check if file is missing then we assume it's moved/renamed
if (!$this->storage->hasFile($fileIndexEntry['identifier'])) {
$fileObject = $this->getResourceFactory()->getFileObject($fileIndexEntry['uid'], $fileIndexEntry);
$fileObject->updateProperties([
'identifier' => $identifier
]);
$this->updateIndexEntry($fileObject);
$this->identifiedFileUids[] = $fileObject->getUid();
break;
try {
if ($data === null) {
// search for files with same content hash in indexed storage
$fileHash = $this->storage->hashFileByIdentifier($identifier, 'sha1');
$files = $this->getFileIndexRepository()->findByContentHash($fileHash);
$fileObject = null;
if (!empty($files)) {
foreach ($files as $fileIndexEntry) {
// check if file is missing then we assume it's moved/renamed
if (!$this->storage->hasFile($fileIndexEntry['identifier'])) {
$fileObject = $this->getResourceFactory()->getFileObject(
$fileIndexEntry['uid'],
$fileIndexEntry
);
$fileObject->updateProperties(
[
'identifier' => $identifier,
]
);
$this->updateIndexEntry($fileObject);
$this->identifiedFileUids[] = $fileObject->getUid();
break;
}
}
}
// create new index when no missing file with same content hash is found
if ($fileObject === null) {
$fileObject = $this->createIndexEntry($identifier);
$this->identifiedFileUids[] = $fileObject->getUid();
}
} else {
// update existing file
$fileObject = $this->getResourceFactory()->getFileObject($data['uid'], $data);
$this->updateIndexEntry($fileObject);
}
// create new index when no missing file with same content hash is found
if ($fileObject === null) {
$fileObject = $this->createIndexEntry($identifier);
$this->identifiedFileUids[] = $fileObject->getUid();
}
} else {
// update existing file
$fileObject = $this->getResourceFactory()->getFileObject($data['uid'], $data);
$this->updateIndexEntry($fileObject);
} catch (InvalidHashException $e) {
$this->logger->error('Unable to create hash for file ' . $identifier);
}
}
}
......@@ -309,8 +323,9 @@ class Indexer
*
* @param string $identifier
* @return array
* @throws \TYPO3\CMS\Core\Resource\Exception\InvalidHashException
*/
protected function gatherFileInformationArray($identifier)
protected function gatherFileInformationArray($identifier): array
{
$fileInfo = $this->storage->getFileInfoByIdentifier($identifier);
$fileInfo = $this->transformFromDriverFileInfoArrayToFileObjectFormat($fileInfo);
......
......@@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Resource\Driver\StreamableDriverInterface;
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException;
use TYPO3\CMS\Core\Resource\Exception\InvalidHashException;
use TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException;
use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
......@@ -1275,6 +1276,7 @@ class ResourceStorage implements ResourceStorageInterface
*
* @param FileInterface $fileObject
* @param string $hash
* @throws \TYPO3\CMS\Core\Resource\Exception\InvalidHashException
* @return string
*/
public function hashFile(FileInterface $fileObject, $hash)
......@@ -1287,12 +1289,16 @@ class ResourceStorage implements ResourceStorageInterface
*
* @param string $fileIdentifier
* @param string $hash
*
* @throws \TYPO3\CMS\Core\Resource\Exception\InvalidHashException
* @return string
*/
public function hashFileByIdentifier($fileIdentifier, $hash)
{
return $this->driver->hash($fileIdentifier, $hash);
$hash = $this->driver->hash($fileIdentifier, $hash);
if (!is_string($hash) || $hash === '') {
throw new InvalidHashException('Hash has to be non-empty string.', 1551950301);
}
return $hash;
}
/**
......
......@@ -116,7 +116,8 @@ class ExtbasePluginEnhancer extends PluginEnhancer
) {
$this->applyControllerActionValues(
$this->configuration['defaultController'],
$originalParameters[$this->namespace]
$originalParameters[$this->namespace],
true
);
}
......@@ -165,7 +166,8 @@ class ExtbasePluginEnhancer extends PluginEnhancer
}
$this->applyControllerActionValues(
$internals['_controller'],
$parameters[$this->namespace]
$parameters[$this->namespace],
false
);
return $parameters;
}
......@@ -195,7 +197,6 @@ class ExtbasePluginEnhancer extends PluginEnhancer
}
return true;
}
/**
* Check if action and controller are not empty.
*
......@@ -211,15 +212,25 @@ class ExtbasePluginEnhancer extends PluginEnhancer
* Add controller and action parameters so they can be used later-on.
*
* @param string $controllerActionValue
* @param array $target
* @param array $target Reference to target array to be modified
* @param bool $tryUpdate Try updating action value - but only if controller value matches
*/
protected function applyControllerActionValues(string $controllerActionValue, array &$target)
protected function applyControllerActionValues(string $controllerActionValue, array &$target, bool $tryUpdate = false)
{
if (strpos($controllerActionValue, '::') === false) {
return;
}
list($controllerName, $actionName) = explode('::', $controllerActionValue, 2);
$target['controller'] = $controllerName;
$target['action'] = $actionName;
// use default action name if controller matches
if ($tryUpdate && empty($target['action']) && $controllerName === ($target['controller'] ?? null)) {
$target['action'] = $actionName;
// use default controller name if action is defined (implies: non-default-controllers must be given)
} elseif ($tryUpdate && empty($target['controller']) && !empty($target['action'])) {
$target['controller'] = $controllerName;
// fallback and override
} else {
$target['controller'] = $controllerName;
$target['action'] = $actionName;
}
}
}
......@@ -691,4 +691,88 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
static::assertStringStartsWith($expectation, (string)$response->getBody());
}
/**
* @return array
*/
public function defaultExtbaseControllerActionNamesAreAppliedDataProvider(): array
{
return [
'*::*' => [
'&tx_testing_link[value]=1',
'https://acme.us/welcome/link/index/one'
],
'*::list' => [
'&tx_testing_link[action]=list&tx_testing_link[value]=1',
'https://acme.us/welcome/link/list/one'
],
'Link::*' => [
// correctly falling back to defaultController here
'&tx_testing_link[controller]=Link&tx_testing_link[value]=1',
'https://acme.us/welcome/link/index/one'
],
'Page::*' => [
// correctly falling back to defaultController here
'&tx_testing_link[controller]=Page&tx_testing_link[value]=1',
'https://acme.us/welcome/link/index/one'
],
'Page::show' => [
'&tx_testing_link[controller]=Page&tx_testing_link[action]=show&tx_testing_link[value]=1',
'https://acme.us/welcome/page/show/one'
],
];
}
/**
* Tests whether ExtbasePluginEnhancer applies `defaultController` values correctly.
*
* @param string $additionalParameters
* @param string $expectation
*
* @test
* @dataProvider defaultExtbaseControllerActionNamesAreAppliedDataProvider
*/
public function defaultExtbaseControllerActionNamesAreApplied(string $additionalParameters, string $expectation)
{
$targetLanguageId = 0;
$this->mergeSiteConfiguration('acme-com', [
'routeEnhancers' => [
'Enhancer' => [
'type' => 'Extbase',
'routes' => [
['routePath' => '/link/index/{value}', '_controller' => 'Link::index'],
['routePath' => '/link/list/{value}', '_controller' => 'Link::list'],
['routePath' => '/page/show/{value}', '_controller' => 'Page::show'],
],
'defaultController' => 'Link::index',
'extension' => 'testing',
'plugin' => 'link',
'aspects' => [
'value' => [
'type' => 'StaticValueMapper',
'map' => [
'one' => 1,
],
],
],
]
]
]);
$response = $this->executeFrontendRequest(
(new InternalRequest('https://acme.us/'))
->withPageId(1100)
->withInstructions([
$this->createTypoLinkUrlInstruction([
'parameter' => 1100,
'language' => $targetLanguageId,
'additionalParams' => $additionalParameters,
'forceAbsoluteUrl' => 1,
])
]),
$this->internalRequestContext
);
static::assertSame($expectation, (string)$response->getBody());
}
}