...
 
Commits (10)
/*
* 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!
*/
class ClipboardComponent {
private static setCheckboxValue(checkboxName: string, check: boolean): void {
const fullName = 'CBC[' + checkboxName + ']';
const checkboxElement: HTMLInputElement = document.querySelector('form[name="dblistForm"] [name="' + fullName + '"]');
if (checkboxElement !== null) {
checkboxElement.checked = check;
}
}
constructor() {
this.registerCheckboxTogglers();
}
private registerCheckboxTogglers(): void {
const selector = 'a.t3js-toggle-all-checkboxes';
document.addEventListener('click', (e: Event): void => {
let target = <HTMLElement>e.target;
if (!target.matches(selector)) {
let closest: HTMLElement = target.closest(selector);
if (closest !== null) {
target = closest;
} else {
return;
}
}
e.preventDefault();
let flagAll: boolean;
if (target.getAttribute('rel') === '') {
target.setAttribute('rel', 'allChecked');
flagAll = true;
} else {
target.setAttribute('rel', '');
flagAll = false;
}
const listOfCheckboxNames: Array<string> = target.dataset.checkboxesNames.split(',');
for (let checkboxName of listOfCheckboxNames) {
ClipboardComponent.setCheckboxValue(checkboxName, flagAll);
}
});
}
}
export = new ClipboardComponent();
......@@ -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();
}
......
......@@ -17,7 +17,6 @@ import Icons = require('TYPO3/CMS/Backend/Icons');
declare global {
const T3_THIS_LOCATION: string;
const editList: Function;
}
interface IconIdentifier {
......@@ -131,7 +130,7 @@ class Recordlist {
$.each(pipes, (pipeIndex: string, pipe: string): void => {
if (pipe === 'editList') {
value = editList(tableName, value);
value = this.editList(tableName, value);
}
});
......@@ -141,11 +140,37 @@ class Recordlist {
window.location.href = uri;
}
private editList(table: string, idList: string): string {
const list: Array<string> = [];
let pointer = 0;
let pos = idList.indexOf(',');
while (pos !== -1) {
if (this.getCheckboxState(table + '|' + idList.substr(pointer, pos - pointer))) {
list.push(idList.substr(pointer, pos - pointer));
}
pointer = pos + 1;
pos = idList.indexOf(',', pointer);
}
if (this.getCheckboxState(table + '|' + idList.substr(pointer))) {
list.push(idList.substr(pointer));
}
return list.length > 0 ? list.join(',') : idList;
}
private disableButton = (event: JQueryEventObject): void => {
const $me = $(event.currentTarget);
$me.prop('disable', true).addClass('disabled');
}
private getCheckboxState(CBname: string): boolean {
const fullName = 'CBC[' + CBname + ']';
const checkbox: HTMLInputElement = document.querySelector('form[name="dblistForm"] [name="' + fullName + '"]');
return checkbox.checked;
}
}
export = new Recordlist();
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c0ef861e95751f5b4784a545886acdfe",
"content-hash": "5e930083e33c28b1c7137aff379cf235",
"packages": [
{
"name": "cogpowered/finediff",
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules\Debug;
/*
* 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 Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter;
use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule;
use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Admin Panel Page Title module for showing the Page title providers
*
* @internal
*/
class PageTitle extends AbstractSubModule implements DataProviderInterface
{
/**
* Log component
*/
protected const LOG_COMPONENT = 'TYPO3.CMS.Core.PageTitle.PageTitleProviderManager';
/**
* Identifier for this Sub-module,
* for example "preview" or "cache"
*
* @return string
*/
public function getIdentifier(): string
{
return 'debug_pagetitle';
}
/**
* Sub-Module label
*
* @return string
*/
public function getLabel(): string
{
return $this->getLanguageService()->sL(
'LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.label'
);
}
/**
* @param ServerRequestInterface $request
* @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData
*/
public function getDataToStore(ServerRequestInterface $request): ModuleData
{
if ($this->isNoCacheEnabled()) {
$data = [
'orderedProviders' => [],
'usedProvider' => null,
'skippedProviders' => []
];
$log = InMemoryLogWriter::$log;
/** @var \TYPO3\CMS\Core\Log\LogRecord $logEntry */
foreach ($log as $logEntry) {
if ($logEntry->getComponent() === self::LOG_COMPONENT) {
if (isset($logEntry->getData()['orderedTitleProviders'])) {
$data['orderedProviders'] = $logEntry->getData()['orderedTitleProviders'];
} elseif (isset($logEntry->getData()['providerUsed'])) {
$data['usedProvider'] = $logEntry->getData();
} elseif (isset($logEntry->getData()['skippedProvider'])) {
$data['skippedProviders'][] = $logEntry->getData();
}
}
}
} else {
$data['cacheEnabled'] = true;
}
return new ModuleData($data);
}
/**
* @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data
* @return string Returns content of admin panel
*/
public function getContent(ModuleData $data): string
{
$view = new StandaloneView();
$view->setTemplatePathAndFilename(
'EXT:adminpanel/Resources/Private/Templates/Modules/Debug/PageTitle.html'
);
$this->getLanguageService()->includeLLFile('EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf');
$view->assignMultiple($data->getArrayCopy());
return $view->render();
}
/**
* @return bool
*/
protected function isNoCacheEnabled(): bool
{
return (bool)$this->getTypoScriptFrontendController()->no_cache;
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}
}
......@@ -58,6 +58,33 @@
<trans-unit id="submodule.queryInformation.queryTime" resname="submodule.queryInformation.queryTime">
<source>Total Time [ms]</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.label">
<source>Page title</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.usedProvider">
<source>Used provider</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.title">
<source>Title</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.provider">
<source>Provider</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.skippedProviders">
<source>Skipped providers (provided no title)</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.name">
<source>Name</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.allProviders">
<source>All providers checked in order</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.cacheEnabled">
<source>It's not possible to retrieve the page title (providers) information when the page is cached.</source>
</trans-unit>
<trans-unit id="submodule.pageTitle.cacheEnabled.extraInfo">
<source>Visit this page uncached, or disable the cache through this admin panel.</source>
</trans-unit>
</body>
</file>
</xliff>
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:if condition="{cacheEnabled}">
<f:then>
<p><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.cacheEnabled" /></p>
<p><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.cacheEnabled.extraInfo" /></p>
</f:then>
<f:else>
<h2 class="typo3-adminPanel-headline"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.usedProvider" /></h2>
<div class="typo3-adminPanel-table-overflow">
<table class="typo3-adminPanel-table typo3-adminPanel-table-debug">
<thead>
<tr>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.title" /></th>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.provider" /></th>
</tr>
</thead>
<tbody>
<tr>
<td>{usedProvider.title}</td>
<td>{usedProvider.providerUsed}</td>
</tr>
</tbody>
</table>
</div>
<h2 class="typo3-adminPanel-headline"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.skippedProviders" /></h2>
<div class="typo3-adminPanel-table-overflow">
<table class="typo3-adminPanel-table typo3-adminPanel-table-debug">
<thead>
<tr>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.name" /></th>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.provider" /></th>
</tr>
</thead>
<tbody>
<f:for each="{skippedProviders}" as="providerInformation">
<tr>
<td>{providerInformation.name}</td>
<td>{providerInformation.skippedProvider}</td>
</tr>
</f:for>
</tbody>
</table>
</div>
<h2 class="typo3-adminPanel-headline"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.allProviders" /></h2>
<div class="typo3-adminPanel-table-overflow">
<table class="typo3-adminPanel-table typo3-adminPanel-table-debug">
<thead>
<tr>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.name" /></th>
<th scope="col" class="typo3-adminPanel-table-cell-key"><f:translate key="LLL:EXT:adminpanel/Resources/Private/Language/locallang_debug.xlf:submodule.pageTitle.provider" /></th>
</tr>
</thead>
<tbody>
<f:for each="{orderedProviders}" as="providerInformation" key="name">
<tr>
<td>{name}</td>
<td>{providerInformation.provider}</td>
</tr>
</f:for>
</tbody>
</table>
</div>
</f:else>
</f:if>
</html>
......@@ -18,7 +18,7 @@
"typo3/cms-fluid": "10.2.*@dev",
"typo3/cms-frontend": "10.2.*@dev",
"typo3fluid/fluid": "^2.6.6",
"psr/http-message": "~1.0",
"psr/http-message": "^1.0",
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0"
},
......
......@@ -48,6 +48,9 @@ $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] = [
'queryInformation' => [
'module' => \TYPO3\CMS\Adminpanel\Modules\Debug\QueryInformation::class,
],
'pageTitle' => [
'module' => \TYPO3\CMS\Adminpanel\Modules\Debug\PageTitle::class,
]
],
],
];
......
......@@ -660,7 +660,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);
......@@ -708,7 +708,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();
}
......@@ -948,7 +948,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
......@@ -1124,47 +1124,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);
}
/**
......
......@@ -188,6 +188,11 @@ class InlineRecordContainer extends AbstractContainer
}
$class .= ($isNewRecord ? ' inlineIsNewRecord' : '');
$originalUniqueValue = '';
if (isset($data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']])) {
$uniqueValueValues = $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']];
$originalUniqueValue = $uniqueValueValues['table'] . '_' . $uniqueValueValues['uid'];
}
$containerAttributes = [
'id' => $objectId . '_div',
'class' => 'form-irre-object panel panel-default panel-condensed ' . trim($class),
......@@ -196,7 +201,7 @@ class InlineRecordContainer extends AbstractContainer
'data-field-name' => $appendFormFieldNames,
'data-topmost-parent-table' => $data['inlineTopMostParentTableName'],
'data-topmost-parent-uid' => $data['inlineTopMostParentUid'],
'data-table-unique-original-value' => $data['inlineData']['unique'][$domObjectId . '-' . $foreignTable]['used'][$record['uid']] ?? '',
'data-table-unique-original-value' => $originalUniqueValue,
];
$html = '
......
......@@ -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) {
......
......@@ -1511,7 +1511,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 => [
......@@ -4010,45 +4010,6 @@ class PageLayoutView implements LoggerAwareInterface
return strpos($this->thisScript, '?') === false ? $this->thisScript . '?' : $this->thisScript . '&';
}
/**
* Returning JavaScript for ClipBoard functionality.
*
* @return string
*/
public function CBfunctions()
{
return '
// checkOffCB()
function checkOffCB(listOfCBnames, link) { //
var checkBoxes, flag, i;
var checkBoxes = listOfCBnames.split(",");
if (link.rel === "") {
link.rel = "allChecked";
flag = true;
} else {
link.rel = "";
flag = false;
}
for (i = 0; i < checkBoxes.length; i++) {
setcbValue(checkBoxes[i], flag);
}
}
// cbValue()
function cbValue(CBname) { //
var CBfullName = "CBC["+CBname+"]";
return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
}
// setcbValue()
function setcbValue(CBname,flag) { //
CBfullName = "CBC["+CBname+"]";
if(document.dblistForm[CBfullName]) {
document.dblistForm[CBfullName].checked = flag ? "on" : 0;
}
}
';
}
/**
* Initializes page languages
*/
......
/*
* 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!
*/
define(["require","exports"],function(e,t){"use strict";class r{static setCheckboxValue(e,t){const r="CBC["+e+"]",s=document.querySelector('form[name="dblistForm"] [name="'+r+'"]');null!==s&&(s.checked=t)}constructor(){this.registerCheckboxTogglers()}registerCheckboxTogglers(){const e="a.t3js-toggle-all-checkboxes";document.addEventListener("click",t=>{let s,c=t.target;if(!c.matches(e)){let t=c.closest(e);if(null===t)return;c=t}t.preventDefault(),""===c.getAttribute("rel")?(c.setAttribute("rel","allChecked"),s=!0):(c.setAttribute("rel",""),s=!1);const l=c.dataset.checkboxesNames.split(",");for(let e of l)r.setCheckboxValue(e,s)})}}return new r});
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","jquery"],function(e,l,i){"use strict";var t,s;!function(e){e.toggleButton=".t3js-form-field-slug-toggle",e.recreateButton=".t3js-form-field-slug-recreate",e.inputField=".t3js-form-field-slug-input",e.readOnlyField=".t3js-form-field-slug-readonly",e.hiddenField=".t3js-form-field-slug-hidden"}(t||(t={})),function(e){e.AUTO="auto",e.RECREATE="recreate",e.MANUAL="manual"}(s||(s={}));return class{constructor(e,l){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||{},i(()=>{this.$fullElement=i(e),this.$inputField=this.$fullElement.find(t.inputField),this.$readOnlyField=this.$fullElement.find(t.readOnlyField),this.$hiddenField=this.$fullElement.find(t.hiddenField),this.registerEvents()})}registerEvents(){const e=Object.keys(this.getAvailableFieldsForProposalGeneration()).map(e=>this.fieldsToListenOn[e]);e.length>0?("new"===this.options.command&&i(this.$fullElement).on("keyup",e.join(","),()=>{this.manuallyChanged||this.sendSlugProposal(s.AUTO)}),i(this.$fullElement).on("click",t.recreateButton,e=>{e.preventDefault(),this.$readOnlyField.hasClass("hidden")&&(this.$readOnlyField.toggleClass("hidden",!1),this.$inputField.toggleClass("hidden",!0)),this.sendSlugProposal(s.RECREATE)})):i(this.$fullElement).find(t.recreateButton).addClass("disabled").prop("disabled",!0),i(this.$inputField).on("keyup",()=>{this.manuallyChanged=!0,this.sendSlugProposal(s.MANUAL)}),i(this.$fullElement).on("click",t.toggleButton,e=>{e.preventDefault();const l=this.$readOnlyField.hasClass("hidden");this.$readOnlyField.toggleClass("hidden",!l),this.$inputField.toggleClass("hidden",l),l?(this.$inputField.val()!==this.$readOnlyField.val()?this.$readOnlyField.val(this.$inputField.val()):(this.manuallyChanged=!1,this.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),this.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),this.$hiddenField.val(this.$readOnlyField.val())):this.$hiddenField.val(this.$inputField.val())})}sendSlugProposal(e){const l={};e===s.AUTO||e===s.RECREATE?i.each(this.getAvailableFieldsForProposalGeneration(),(e,t)=>{l[e]=i('[data-formengine-input-name="'+t+'"]').val()}):l.manual=this.$inputField.val(),null!==this.xhr&&4!==this.xhr.readyState&&this.xhr.abort(),this.xhr=i.post(TYPO3.settings.ajaxUrls.record_slug_suggest,{values:l,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},l=>{l.hasConflicts?(this.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),this.$fullElement.find(".t3js-form-proposal-different").removeClass("hidden").find("span").text(l.proposal)):(this.$fullElement.find(".t3js-form-proposal-accepted").removeClass("hidden").find("span").text(l.proposal),this.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),this.$hiddenField.val()!==l.proposal&&this.$fullElement.find("input").trigger("change"),e===s.AUTO||e===s.RECREATE?(this.$readOnlyField.val(l.proposal),this.$hiddenField.val(l.proposal),this.$inputField.val(l.proposal)):this.$hiddenField.val(l.proposal)},"json")}getAvailableFieldsForProposalGeneration(){const e={};return i.each(this.fieldsToListenOn,(l,t)=>{i('[data-formengine-input-name="'+t+'"]').length>0&&(e[l]=t)}),e}}});
\ No newline at end of file
define(["require","exports","jquery"],function(e,i,l){"use strict";var t,s;!function(e){e.toggleButton=".t3js-form-field-slug-toggle",e.recreateButton=".t3js-form-field-slug-recreate",e.inputField=".t3js-form-field-slug-input",e.readOnlyField=".t3js-form-field-slug-readonly",e.hiddenField=".t3js-form-field-slug-hidden"}(t||(t={})),function(e){e.AUTO="auto",e.RECREATE="recreate",e.MANUAL="manual"}(s||(s={}));return class{constructor(e,i){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=i,this.fieldsToListenOn=this.options.listenerFieldNames||{},l(()=>{this.$fullElement=l(e),this.$inputField=this.$fullElement.find(t.inputField),this.$readOnlyField=this.$fullElement.find(t.readOnlyField),this.$hiddenField=this.$fullElement.find(t.hiddenField),this.registerEvents()})}registerEvents(){const e=Object.keys(this.getAvailableFieldsForProposalGeneration()).map(e=>this.fieldsToListenOn[e]);e.length>0?("new"===this.options.command&&l(this.$fullElement).on("keyup",e.join(","),()=>{this.manuallyChanged||this.sendSlugProposal(s.AUTO)}),l(this.$fullElement).on("click",t.recreateButton,e=>{e.preventDefault(),this.$readOnlyField.hasClass("hidden")&&(this.$readOnlyField.toggleClass("hidden",!1),this.$inputField.toggleClass("hidden",!0)),this.sendSlugProposal(s.RECREATE)})):l(this.$fullElement).find(t.recreateButton).addClass("disabled").prop("disabled",!0),l(this.$inputField).on("keyup",()=>{this.manuallyChanged=!0,this.sendSlugProposal(s.MANUAL)}),l(this.$fullElement).on("click",t.toggleButton,e=>{e.preventDefault();const i=this.$readOnlyField.hasClass("hidden");this.$readOnlyField.toggleClass("hidden",!i),this.$inputField.toggleClass("hidden",i),i?(this.$inputField.val()!==this.$readOnlyField.val()?this.$readOnlyField.val(this.$inputField.val()):(this.manuallyChanged=!1,this.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),this.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),this.$hiddenField.val(this.$readOnlyField.val())):this.$hiddenField.val(this.$inputField.val())})}sendSlugProposal(e){const i={};e===s.AUTO||e===s.RECREATE?(l.each(this.getAvailableFieldsForProposalGeneration(),(e,t)=>{i[e]=l('[data-formengine-input-name="'+t+'"]').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=l.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},i=>{i.hasConflicts?(this.$fullElement.find(".t3js-form-proposal-accepted").addClass("hidden"),this.$fullElement.find(".t3js-form-proposal-different").removeClass("hidden").find("span").text(i.proposal)):(this.$fullElement.find(".t3js-form-proposal-accepted").removeClass("hidden").find("span").text(i.proposal),this.$fullElement.find(".t3js-form-proposal-different").addClass("hidden")),this.$hiddenField.val()!==i.proposal&&this.$fullElement.find("input").trigger("change"),e===s.AUTO||e===s.RECREATE?(this.$readOnlyField.val(i.proposal),this.$hiddenField.val(i.proposal),this.$inputField.val(i.proposal)):this.$hiddenField.val(i.proposal)},"json")}getAvailableFieldsForProposalGeneration(){const e={};return l.each(this.fieldsToListenOn,(i,t)=>{l('[data-formengine-input-name="'+t+'"]').length>0&&(e[i]=t)}),e}}});
\ No newline at end of file
......@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Core\PageTitle;
* The TYPO3 project - inspiring people to share!
*/
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
......@@ -24,12 +26,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* This class will take care of the different providers and returns the title with the highest priority
*/
class PageTitleProviderManager implements SingletonInterface
class PageTitleProviderManager implements SingletonInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @return string
* @throws \TYPO3\CMS\Core\Cache\Exception
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException
*/
public function getTitle(): string
{
......@@ -41,13 +43,26 @@ class PageTitleProviderManager implements SingletonInterface
$orderedTitleProviders = GeneralUtility::makeInstance(DependencyOrderingService::class)
->orderByDependencies($titleProviders);
$this->logger->debug(
'Page title providers ordered',
['orderedTitleProviders' => $orderedTitleProviders]
);
foreach ($orderedTitleProviders as $provider => $configuration) {
if (class_exists($configuration['provider']) && is_subclass_of($configuration['provider'], PageTitleProviderInterface::class)) {
/** @var PageTitleProviderInterface $titleProviderObject */
$titleProviderObject = GeneralUtility::makeInstance($configuration['provider']);
if ($pageTitle = $titleProviderObject->getTitle()) {
$this->logger->debug(
'Page title provider ' . $configuration['provider'] . ' used',
['title' => $pageTitle, 'providerUsed' => $configuration['provider']]
);
break;
}
$this->logger->debug(
'Page title provider ' . $configuration['provider'] . ' skipped',
['name' => $provider, 'skippedProvider' => $configuration['provider']]
);
}
}
......
......@@ -91,10 +91,14 @@ PHP notice level warnings::
Change :php:`BackendUtility->getModTSconfig()` related calls to use :php:`BackendUtility::getPagesTSconfig($pid)` instead.
Note this method does not return the 'properties' and 'value' sub array as :php:`->getModTSconfig()` did::
// Switch an old getModTSconfig() to getPagesTSConfig():
$configArray = BackendUtility::getModTSconfig($id, 'mod.web_list');
// Switch an old getModTSconfig() to getPagesTSConfig() for array of properties:
$configArray = BackendUtility::getModTSconfig($pid, 'mod.web_list');
$configArray['properties'] = BackendUtility::getPagesTSconfig($pid)['mod.']['web_list.'] ?? [];
// Switch an old getModTSconfig() to getPagesTSConfig() for single value:
$configArray = BackendUtility::getModTSconfig($pid, 'TCEFORM.sys_dmail_group.select_categories.PAGE_TSCONFIG_IDLIST');
$configArray['value'] = BackendUtility::getPagesTSconfig($pid)['TCEFORM.']['sys_dmail_group.']['select_categories.']['PAGE_TSCONFIG_IDLIST'] ?? null;
Methods :php:`BackendUtility::unsetMenuItems()` and :php:`DataHandler->getTCEMAIN_TSconfig()` have been rarely used
and are dropped without substitution. Copy the code into consuming methods if you really need them.
......
.. include:: ../../Includes.txt
===============================================================
Feature: #89458 - Show link to online docs in extension manager
===============================================================
See :issue:`89458`
Description
===========
The export of extensions provided by https://extensions.typo3.org has been extended with the link
to the documentation of an extension. This link is now shown in the list of extension as well as the detail view
of extensions in the extension manager.
Impact
======
Extension authors can add a custom link to the manual in their TER extensions which is now shown in the TYPO3 backend.
.. index:: Backend, ext:extensionmanager
......@@ -30,10 +30,10 @@
"psr/event-dispatcher": "^1.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/http-message": "~1.0",
"psr/http-message": "^1.0",
"psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "~1.0.0",
"psr/log": "^1.0",
"symfony/config": "^4.1",
"symfony/console": "^4.1",
"symfony/dependency-injection": "^4.1",
......
......@@ -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;
}
}
}
......@@ -152,6 +152,11 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
*/
protected $dependencies;
/**
* @var string
*/
protected $documentationLink = '';
/**
* @internal
* @var int
......@@ -599,4 +604,20 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
return $this->alldownloadcounter;
}
/**
* @return string
*/
public function getDocumentationLink(): string
{
return $this->documentationLink;
}
/**
* @param string $documentationLink
*/
public function setDocumentationLink(string $documentationLink): void
{
$this->documentationLink = $documentationLink;
}
}
......@@ -71,7 +71,8 @@ class ExtensionListUtility implements \SplObserver
'category',
'description',
'serialized_dependencies',
'update_comment'
'update_comment',
'documentation_link'
];
/**
......@@ -251,7 +252,8 @@ class ExtensionListUtility implements \SplObserver
$this->extensionModel->getCategoryIndexFromStringOrNumber($subject->getCategory() ?: ''),
$subject->getDescription() ?: '',
$subject->getDependencies() ?: '',
$subject->getUploadcomment() ?: ''
$subject->getUploadcomment() ?: '',
$subject->getDocumentationLink() ?: '',
];
++$this->sumRecords;
}
......
......@@ -139,6 +139,13 @@ abstract class AbstractExtensionXmlParser extends AbstractXmlParser
*/
protected $versionDownloadCounter;
/**
* Link to the documentation
*
* @var string
*/
protected $documentationLink;
/**
* Returns an associative array of all extension version properties.
*
......@@ -169,6 +176,7 @@ abstract class AbstractExtensionXmlParser extends AbstractXmlParser
$versionProperties['authorcompany'] = $this->authorcompany;
$versionProperties['ownerusername'] = $this->ownerusername;
$versionProperties['t3xfilemd5'] = $this->t3xfilemd5;
$versionProperties['documentationlink'] = $this->documentationLink;
return $versionProperties;
}
......@@ -359,6 +367,14 @@ abstract class AbstractExtensionXmlParser extends AbstractXmlParser
return $this->version;
}
/**
* @return string
*/
public function getDocumentationLink()
{
return $this->documentationLink;
}
/**
* Method resets version class properties.
*
......@@ -369,7 +385,7 @@ abstract class AbstractExtensionXmlParser extends AbstractXmlParser
// resetting at least class property "version" is mandatory
// as we need to do some magic in regards to
// an extension's and version's child node "downloadcounter"
$this->version = $this->title = $this->versionDownloadCounter = $this->description = $this->state = $this->reviewstate = $this->category = $this->lastuploaddate = $this->uploadcomment = $this->dependencies = $this->authorname = $this->authoremail = $this->authorcompany = $this->ownerusername = $this->t3xfilemd5 = null;
$this->version = $this->title = $this->versionDownloadCounter = $this->description = $this->state = $this->reviewstate = $this->category = $this->lastuploaddate = $this->uploadcomment = $this->dependencies = $this->authorname = $this->authoremail = $this->authorcompany = $this->ownerusername = $this->t3xfilemd5 = $this->documentationLink = null;
if ($resetAll) {
$this->extensionKey = $this->extensionDownloadCounter = null;
}
......
......@@ -133,6 +133,9 @@ class ExtensionXmlPullParser extends AbstractExtensionXmlParser
case 't3xfilemd5':
$this->t3xfilemd5 = $this->getElementValue($elementName);
break;
case 'documentation_link':
$this->documentationLink = $this->getElementValue($elementName);
break;
}
}
......
......@@ -168,6 +168,9 @@ class ExtensionXmlPushParser extends AbstractExtensionXmlParser
case 't3xfilemd5':
$this->t3xfilemd5 = $this->elementData;
break;
case 'documentation_link':
$this->documentationLink = $this->elementData;
break;
}
}
......
......@@ -12,7 +12,7 @@ return [
]
],
'interface' => [
'showRecordFieldList' => 'extension_key,version,integer_version,title,description,state,category,last_updated,update_comment,author_name,author_email,md5hash,serialized_dependencies'
'showRecordFieldList' => 'extension_key,version,integer_version,title,description,state,category,last_updated,update_comment,author_name,author_email,md5hash,serialized_dependencies,documentation_link'
],
'columns' => [
'extension_key' => [
......@@ -132,9 +132,15 @@ return [
'size' => 30,
],
],
'documentation_link' => [
'label' => 'LLL:EXT:extensionmanager/Resources/Private/Language/locallang_db.xlf:tx_extensionmanager_domain_model_extension.documentation_link',
'config' => [
'type' => 'input',
],
],
],
'types' => [
'0' => ['showitem' => 'extensionkey, version, integer_version, title, description, state, category, last_updated, update_comment, author_name, author_email, review_state, md5hash, serialized_dependencies']
'0' => ['showitem' => 'extensionkey, version, integer_version, title, description, state, category, last_updated, update_comment, author_name, author_email, review_state, md5hash, serialized_dependencies, documentation_link']
],
'palettes' => [
'1' => ['showitem' => '']
......
......@@ -141,6 +141,9 @@
<trans-unit id="extensionList.header.author" resname="extensionList.header.author">
<source>Author</source>
</trans-unit>
<trans-unit id="extensionList.header.manual" resname="extensionList.header.manual">
<source>Manual</source>
</trans-unit>
<trans-unit id="searchTemplate.searchExtensions" resname="searchTemplate.searchExtensions">
<source>Search extensions</source>
</trans-unit>
......
......@@ -31,6 +31,13 @@
<div class="author-email">{extension.authorEmail}</div>
</div>
</td>
<td>
<f:if condition="{extension.documentationLink}">
<a href="{extension.documentationLink}" class="btn btn-default" target="_blank" rel="noreferrer" title="{f:translate(key: 'extensionList.showAllVersions.readOnline')}">
<core:icon identifier="actions-system-extension-documentation" size="small" />
</a>
</f:if>
</td>
<td>
<span class="label label-{extension.stateString}">{extension.stateString}</span>
</td>
......@@ -7,6 +7,7 @@
<th><f:translate key="extensionList.header.version"/></th>
<th><f:translate key="extensionList.header.lastUpdate"/></th>
<th><f:translate key="extensionList.header.description"/></th>
<th><f:translate key="extensionList.header.manual"/></th>
<th><f:translate key="extensionList.header.extensionState"/></th>
</tr>
</thead>
......
......@@ -47,7 +47,7 @@
<tr class="ter-ext-single-info-manual">
<th><f:translate key="extensionList.showAllVersions.manual" /></th>
<td>
<a href="https://docs.typo3.org/typo3cms/extensions/{currentVersion.extensionKey}/" target="_blank" rel="noopener noreferrer">
<a href="{f:if(condition:currentVersion.documentationLink,then:currentVersion.documentationLink,else:'https://docs.typo3.org/typo3cms/extensions/{currentVersion.extensionKey}/')}" target="_blank" rel="noopener noreferrer">
<f:translate key="extensionList.showAllVersions.readOnline" />
</a>
</td>
......
......@@ -35,6 +35,7 @@ CREATE TABLE tx_extensionmanager_domain_model_extension (
integer_version int(11) NOT NULL default '0',
current_version int(3) NOT NULL default '0',
lastreviewedversion int(3) NOT NULL default '0',
documentation_link varchar(2048),
KEY index_extrepo (extension_key,repository),
KEY index_versionrepo (integer_version,repository,extension_key),
......
......@@ -389,9 +389,10 @@ class FileListController extends ActionController implements LoggerAwareInterfac
// Set top JavaScript:
$this->view->getModuleTemplate()->addJavaScriptCode(
'FileListIndex',
'if (top.fsMod) top.fsMod.recentIds["file"] = "' . rawurlencode($this->id) . '";' . $this->filelist->CBfunctions() . '
'if (top.fsMod) top.fsMod.recentIds["file"] = "' . rawurlencode($this->id) . '";
'
);
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClipboardComponent');
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileDelete');
$pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf', 'buttons');
......
......@@ -506,8 +506,7 @@ class FileList
if ($this->folderObject->checkActionPermission('delete')) {
$cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_deleteMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(), $table, 'delete', $this->getLanguageService()->getLL('clip_deleteMarkedWarning'));
}
$onClick = 'checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;';
$cells[] = '<a class="btn btn-default" rel="" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_markRecords')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
$cells[] = '<a class="btn btn-default t3js-toggle-all-checkboxes" data-checkboxes-names="' . htmlspecialchars(implode(',', $this->CBnames)) . '" rel="" href="#" title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_markRecords')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
}
$theData[$v] = implode('', $cells);
} elseif ($v === '_REF_') {
......@@ -708,45 +707,6 @@ class FileList
return $content;
}
/**
* Returning JavaScript for ClipBoard functionality.
*
* @return string
*/
public function CBfunctions()
{
return '
// checkOffCB()
function checkOffCB(listOfCBnames, link) { //
var checkBoxes, flag, i;
var checkBoxes = listOfCBnames.split(",");
if (link.rel === "") {
link.rel = "allChecked";
flag = true;
} else {
link.rel = "";
flag = false;
}
for (i = 0; i < checkBoxes.length; i++) {
setcbValue(checkBoxes[i], flag);
}
}
// cbValue()
function cbValue(CBname) { //
var CBfullName = "CBC["+CBname+"]";
return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
}
// setcbValue()
function setcbValue(CBname,flag) { //
CBfullName = "CBC["+CBname+"]";
if(document.dblistForm[CBfullName]) {
document.dblistForm[CBfullName].checked = flag ? "on" : 0;
}
}
';
}
/**
* Initializes page languages and icons
*/
......
......@@ -13,7 +13,7 @@
"sort-packages": true
},
"require": {
"psr/http-message": "~1.0",
"psr/http-message": "^1.0",
"symfony/expression-language": "^4.1",
"typo3/cms-core": "10.2.*@dev"
},
......
......@@ -711,7 +711,6 @@ class GifBuilder extends GraphicalFunctions
*/
public function fileName($pre)
{
/** @var \TYPO3\CMS\Core\Utility\File\BasicFileUtility $basicFileFunctions */
$basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
$filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
$filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
......@@ -719,7 +718,21 @@ class GifBuilder extends GraphicalFunctions
// shorten prefix to avoid overly long file names
$filePrefix = substr($filePrefix, 0, 100);
return 'typo3temp/' . $pre . $filePrefix . '_' . GeneralUtility::shortMD5(serialize($this->setup)) . '.' . $this->extension();
// Only take relevant parameters to ease the pain for json_encode and make the final string short
// so shortMD5 is not as slow. see https://forge.typo3.org/issues/64158
$hashInputForFileName = [
array_keys($this->setup),
$filePrefix,
$this->im,
$this->w,
$this->h,
$this->map,
$this->workArea,
$this->combinedTextStrings,
$this->combinedFileNames,
$this->data
];
return 'typo3temp/' . $pre . $filePrefix . '_' . GeneralUtility::shortMD5(json_encode($hashInputForFileName)) . '.' . $this->extension();
}
/**
......
......@@ -691,4 +691,88 @@ class EnhancerLinkGeneratorTest extends AbstractTestCase
self::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());
}
}
......@@ -366,7 +366,9 @@ class RecordListController
// Render the list of tables:
$dblist->generateList();
$listUrl = $dblist->listURL();
// Add JavaScript functions to the page:
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClipboardComponent');
$this->moduleTemplate->addJavaScriptCode(
'RecordListInlineJS',
......@@ -392,29 +394,9 @@ class RecordListController
}
}
' . $this->moduleTemplate->redirectUrls($listUrl) . '
' . $dblist->CBfunctions() . '
function editRecords(table,idList,addParams,CBflag) {
window.location.href="' . (string)$uriBuilder->buildUriFromRoute('record_edit', ['returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')]) . '&edit["+table+"]["+idList+"]=edit"+addParams;
}
function editList(table,idList) {
var list="";
// Checking how many is checked, how many is not
var pointer=0;
var pos = idList.indexOf(",");
while (pos!=-1) {
if (cbValue(table+"|"+idList.substr(pointer,pos-pointer))) {
list+=idList.substr(pointer,pos-pointer)+",";
}
pointer=pos+1;
pos = idList.indexOf(",",pointer);
}
if (cbValue(table+"|"+idList.substr(pointer))) {
list+=idList.substr(pointer)+",";
}
return list ? list : idList;
}
if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
'
......
......@@ -1488,8 +1488,7 @@ class DatabaseRecordList
$lang->getLL('clip_deleteMarked')
);
// The "Select all" link:
$onClick = htmlspecialchars('checkOffCB(' . GeneralUtility::quoteJSvalue(implode(',', $this->CBnames)) . ', this); return false;');
$cells['markAll'] = '<a class="btn btn-default" rel="" href="#" onclick="' . $onClick . '" title="'
$cells['markAll'] = '<a class="btn btn-default t3js-toggle-all-checkboxes" data-checkboxes-names="' . htmlspecialchars(implode(',', $this->CBnames)) . '" rel="" href="#" title="'
. htmlspecialchars($lang->getLL('clip_markRecords')) . '">'
. $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
} else {
......@@ -3898,45 +3897,6 @@ class DatabaseRecordList
return $out;
}
/**
* Returning JavaScript for ClipBoard functionality.
*
* @return string
*/
public function CBfunctions()
{
return '
// checkOffCB()
function checkOffCB(listOfCBnames, link) { //
var checkBoxes, flag, i;
var checkBoxes = listOfCBnames.split(",");
if (link.rel === "") {
link.rel = "allChecked";
flag = true;
} else {
link.rel = "";
flag = false;
}
for (i = 0; i < checkBoxes.length; i++) {
setcbValue(checkBoxes[i], flag);
}
}
// cbValue()
function cbValue(CBname) { //
var CBfullName = "CBC["+CBname+"]";
return (document.dblistForm[CBfullName] && document.dblistForm[CBfullName].checked ? 1 : 0);
}
// setcbValue()
function setcbValue(CBname,flag) { //
CBfullName = "CBC["+CBname+"]";
if(document.dblistForm[CBfullName]) {
document.dblistForm[CBfullName].checked = flag ? "on" : 0;
}
}
';
}
/**
* Initializes page languages and icons
*/
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","jquery","TYPO3/CMS/Backend/Storage/Persistent","TYPO3/CMS/Backend/Icons"],function(t,e,i,a,s){"use strict";return new class{constructor(){this.identifier={entity:".t3js-entity",toggle:".t3js-toggle-recordlist",localize:".t3js-action-localize",icons:{collapse:"actions-view-list-collapse",expand:"actions-view-list-expand",editMultiple:".t3js-record-edit-multiple"}},this.toggleClick=(t=>{t.preventDefault();const e=i(t.currentTarget),l=e.data("table"),n=i(e.data("target")),d="expanded"===n.data("state"),o=e.find(".collapseIcon"),c=d?this.identifier.icons.expand:this.identifier.icons.collapse;s.getIcon(c,s.sizes.small).done(t=>{o.html(t)});let r={};a.isset("moduleData.list")&&(r=a.get("moduleData.list"));const u={};u[l]=d?1:0,i.extend(!0,r,u),a.set("moduleData.list",r).done(()=>{n.data("state",d?"collapsed":"expanded")})}),this.onEditMultiple=(t=>{let e,a,s,l,n;t.preventDefault(),0!==(e=i(t.currentTarget).closest("[data-table]")).length&&(l=i(t.currentTarget).data("uri"),a=e.data("table"),s=e.find(this.identifier.entity+'[data-uid][data-table="'+a+'"]').map((t,e)=>i(e).data("uid")).toArray().join(","),n=l.match(/{[^}]+}/g),i.each(n,(t,e)=>{const n=e.substr(1,e.length-2).split(":");let d;switch(n.shift()){case"entityIdentifiers":d=s;break;case"T3_THIS_LOCATION":d=T3_THIS_LOCATION;break;default:return}i.each(n,(t,e)=>{"editList"===e&&(d=editList(a,d))}),l=l.replace(e,d)}),window.location.href=l)}),this.disableButton=(t=>{i(t.currentTarget).prop("disable",!0).addClass("disabled")}),i(document).on("click",this.identifier.toggle,this.toggleClick),i(document).on("click",this.identifier.icons.editMultiple,this.onEditMultiple),i(document).on("click",this.identifier.localize,this.disableButton)}}});
\ No newline at end of file
define(["require","exports","jquery","TYPO3/CMS/Backend/Storage/Persistent","TYPO3/CMS/Backend/Icons"],function(t,e,i,s,a){"use strict";return new class{constructor(){this.identifier={entity:".t3js-entity",toggle:".t3js-toggle-recordlist",localize:".t3js-action-localize",icons:{collapse:"actions-view-list-collapse",expand:"actions-view-list-expand",editMultiple:".t3js-record-edit-multiple"}},this.toggleClick=(t=>{t.preventDefault();const e=i(t.currentTarget),n=e.data("table"),l=i(e.data("target")),d="expanded"===l.data("state"),o=e.find(".collapseIcon"),c=d?this.identifier.icons.expand:this.identifier.icons.collapse;a.getIcon(c,a.sizes.small).done(t=>{o.html(t)});let r={};s.isset("moduleData.list")&&(r=s.get("moduleData.list"));const u={};u[n]=d?1:0,i.extend(!0,r,u),s.set("moduleData.list",r).done(()=>{l.data