From 6f7325d09ba0bfb6f68815352b57c6d586b33b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ausw=C3=B6ger?= Date: Fri, 11 Sep 2020 16:23:05 +0200 Subject: [PATCH 01/57] Update dependecies for PHP 8.0 compatibility (see #2281) Description ----------- See #2263 ~~I think we can remove `phpunit/php-token-stream`? Or do we need this package anywhere in the core?~~ Commits ------- 9c2356c9 Update dependecies for PHP 8.0 compatibility 8cb7edf8 Add phpunit/php-token-stream back --- composer.json | 4 ++-- core-bundle/composer.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 4e94cfbab03..e382d9c1c0e 100644 --- a/composer.json +++ b/composer.json @@ -70,13 +70,13 @@ "paragonie/constant_time_encoding": "^2.2", "patchwork/utf8": "^1.2", "phpspec/php-diff": "^1.0", - "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0", + "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0 || ^4.0", "psr/log": "^1.0", "ramsey/uuid": "^3.8", "scheb/two-factor-bundle": "^4.11", "scssphp/scssphp": "^1.0", "simplepie/simplepie": "^1.3", - "spomky-labs/otphp": "^9.1", + "spomky-labs/otphp": "^9.1 || ^10.0", "symfony-cmf/routing-bundle": "^2.1", "symfony/asset": "4.4.*", "symfony/cache": "4.4.*", diff --git a/core-bundle/composer.json b/core-bundle/composer.json index ce11cbbc2ca..5f3d84bca3f 100644 --- a/core-bundle/composer.json +++ b/core-bundle/composer.json @@ -66,13 +66,13 @@ "paragonie/constant_time_encoding": "^2.2", "patchwork/utf8": "^1.2", "phpspec/php-diff": "^1.0", - "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0", + "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0 || ^4.0", "psr/log": "^1.0", "ramsey/uuid": "^3.8", "scheb/two-factor-bundle": "^4.11", "scssphp/scssphp": "^1.0", "simplepie/simplepie": "^1.3", - "spomky-labs/otphp": "^9.1", + "spomky-labs/otphp": "^9.1 || ^10.0", "symfony-cmf/routing-bundle": "^2.1", "symfony/asset": "4.4.*", "symfony/cache": "4.4.*", From 5aae3bff5772702db67a8d6588c39629da419845 Mon Sep 17 00:00:00 2001 From: Fritz Michael Gschwantner Date: Fri, 11 Sep 2020 22:08:44 +0100 Subject: [PATCH 02/57] Do not change CSRF token cookie, if response is not successful (see #2252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #2246 | Docs PR or issue | - I am not sure if this is the right approach to fix #2246, but I think it makes sense to never change the CSRF token cookie, if the response is not within the `>= 200, < 300` status code range. Implementing this check would fix #2246 as far as I tested. The problem is, that an AJAX request on the same page that requires the presence of a CSRF token cookie can invalidate said CSRF token cookie, if the generated response in turn requires a new CSRF token cookie (e.g., if the 404 page redirects to another Contao page which in turn requires a CSRF token cookie). The problem does not occur in Contao 4.4, at least not with the reproduction I posted. Commits ------- a7c17cf6 do not change CSRF token cookie, if response is not succesful 59271940 Merge remote-tracking branch 'composer/4.9' into fix-csrf-token-cookie-deletion 04934527 change isSuccessful check 3880e354 Update core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php Co-authored-by: Martin Auswöger 10cc70fd Update core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php Co-authored-by: Leo Feyer --- core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php b/core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php index eb19e4a0c86..429cfe2fe85 100644 --- a/core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php +++ b/core-bundle/src/EventListener/CsrfTokenCookieSubscriber.php @@ -70,7 +70,8 @@ public function onKernelResponse(ResponseEvent $event): void if ($this->requiresCsrf($request, $response)) { $this->setCookies($request, $response); - } else { + } elseif ($response->isSuccessful()) { + // Only delete the CSRF token cookie if the response is successful (#2252) $this->removeCookies($request, $response); $this->replaceTokenOccurrences($response); } From 321062bd8b25cb09c09f11afc79ce2c19940521b Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Tue, 15 Sep 2020 11:40:00 +0200 Subject: [PATCH 03/57] Do not predefine the number of Psalm threads (see #2298) Description ----------- - Commits ------- cc7030db Do not predefine the number of Psalm threads --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 865c73b6356..fd2bcfac2f7 100644 --- a/composer.json +++ b/composer.json @@ -276,7 +276,7 @@ "vendor/bin/phpstan analyze core-bundle/src core-bundle/tests --level=5 --memory-limit=1G --ansi" ], "psalm": [ - "vendor/bin/psalm --no-suggestions --threads=4" + "vendor/bin/psalm --no-suggestions" ], "unit-tests": [ "vendor/bin/phpunit --colors=always" From cb8645abf68d12afc8f9e131f596b3940f9652cd Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Mon, 21 Sep 2020 10:20:21 +0200 Subject: [PATCH 04/57] Stop using == '' with regard to PHP 8 (see #2294) Description ----------- See https://3v4l.org/XfAXs Commits ------- 37d8a4e3 Stop using == '' with regard to PHP 8 962fbf24 Also replace != '' 504a4e80 Replace == '' in conjunction with arrays 61cec6cb Replace != '' in conjunction with arrays 943559a2 Clean up a0f40e0a Handle cases in which '0' is not empty 0a0052bb Revert the StyleSheets changes 984fc194 Handle an in_array(0 case --- .../src/Resources/contao/classes/Calendar.php | 4 +- .../src/Resources/contao/classes/Events.php | 2 +- .../contao/dca/tl_calendar_events.php | 4 +- .../Resources/contao/dca/tl_calendar_feed.php | 2 +- .../contao/modules/ModuleEventReader.php | 8 +- .../contao/modules/ModuleEventlist.php | 8 +- .../src/Resources/contao/classes/Comments.php | 6 +- .../src/Resources/contao/dca/tl_comments.php | 2 +- .../src/Resources/contao/classes/Ajax.php | 6 +- .../src/Resources/contao/classes/Backend.php | 36 ++++---- .../Resources/contao/classes/BackendUser.php | 10 +-- .../contao/classes/DataContainer.php | 16 ++-- .../Resources/contao/classes/FileUpload.php | 4 +- .../src/Resources/contao/classes/Frontend.php | 14 ++-- .../contao/classes/FrontendTemplate.php | 4 +- .../Resources/contao/classes/FrontendUser.php | 2 +- .../src/Resources/contao/classes/Hybrid.php | 8 +- .../src/Resources/contao/classes/Messages.php | 2 +- .../Resources/contao/classes/PurgeData.php | 2 +- .../src/Resources/contao/classes/Theme.php | 8 +- .../src/Resources/contao/classes/Versions.php | 2 +- .../contao/controllers/BackendMain.php | 4 +- .../contao/controllers/BackendPopup.php | 2 +- .../contao/controllers/FrontendIndex.php | 6 +- .../src/Resources/contao/dca/tl_article.php | 4 +- .../src/Resources/contao/dca/tl_content.php | 4 +- .../src/Resources/contao/dca/tl_files.php | 2 +- .../src/Resources/contao/dca/tl_form.php | 2 +- .../src/Resources/contao/dca/tl_member.php | 2 +- .../src/Resources/contao/dca/tl_page.php | 16 ++-- .../Resources/contao/dca/tl_style_sheet.php | 6 +- .../src/Resources/contao/dca/tl_templates.php | 4 +- .../src/Resources/contao/dca/tl_theme.php | 2 +- .../src/Resources/contao/dca/tl_user.php | 6 +- .../src/Resources/contao/drivers/DC_File.php | 6 +- .../Resources/contao/drivers/DC_Folder.php | 32 ++++---- .../src/Resources/contao/drivers/DC_Table.php | 82 +++++++++---------- .../contao/elements/ContentAccordion.php | 2 +- .../Resources/contao/elements/ContentCode.php | 2 +- .../contao/elements/ContentDownload.php | 2 +- .../contao/elements/ContentDownloads.php | 8 +- .../contao/elements/ContentElement.php | 6 +- .../contao/elements/ContentGallery.php | 2 +- .../contao/elements/ContentHyperlink.php | 4 +- .../contao/elements/ContentImage.php | 2 +- .../contao/elements/ContentMarkdown.php | 2 +- .../contao/elements/ContentMedia.php | 6 +- .../contao/elements/ContentTable.php | 6 +- .../Resources/contao/elements/ContentText.php | 2 +- .../contao/elements/ContentToplink.php | 2 +- .../contao/elements/ContentVimeo.php | 4 +- .../contao/elements/ContentYouTube.php | 4 +- .../src/Resources/contao/forms/Form.php | 12 +-- .../Resources/contao/forms/FormCaptcha.php | 2 +- .../Resources/contao/forms/FormCheckBox.php | 2 +- .../Resources/contao/forms/FormFileUpload.php | 4 +- .../contao/forms/FormRadioButton.php | 2 +- .../Resources/contao/forms/FormSelectMenu.php | 4 +- .../src/Resources/contao/forms/FormSubmit.php | 2 +- .../src/Resources/contao/helper/functions.php | 4 +- .../contao/library/Contao/Combiner.php | 4 +- .../contao/library/Contao/Config.php | 6 +- .../contao/library/Contao/Controller.php | 24 +++--- .../contao/library/Contao/Database.php | 10 +-- .../library/Contao/Database/Installer.php | 6 +- .../library/Contao/Database/Statement.php | 4 +- .../library/Contao/Database/Updater.php | 2 +- .../Resources/contao/library/Contao/Date.php | 12 +-- .../Resources/contao/library/Contao/Dbafs.php | 2 +- .../contao/library/Contao/DcaExtractor.php | 8 +- .../contao/library/Contao/DcaLoader.php | 2 +- .../Resources/contao/library/Contao/Email.php | 16 ++-- .../contao/library/Contao/Encryption.php | 6 +- .../contao/library/Contao/Environment.php | 2 +- .../contao/library/Contao/FeedItem.php | 2 +- .../Resources/contao/library/Contao/Files.php | 2 +- .../Resources/contao/library/Contao/Idna.php | 20 ++--- .../Resources/contao/library/Contao/Image.php | 6 +- .../Resources/contao/library/Contao/Input.php | 12 +-- .../contao/library/Contao/InsertTags.php | 28 +++---- .../contao/library/Contao/Message.php | 4 +- .../Resources/contao/library/Contao/Model.php | 4 +- .../contao/library/Contao/Request.php | 6 +- .../contao/library/Contao/SqlFileParser.php | 6 +- .../contao/library/Contao/StringUtil.php | 10 +-- .../contao/library/Contao/System.php | 12 +-- .../contao/library/Contao/Template.php | 2 +- .../contao/library/Contao/Validator.php | 2 +- .../contao/library/Contao/Widget.php | 20 ++--- .../src/Resources/contao/models/PageModel.php | 12 +-- .../src/Resources/contao/modules/Module.php | 8 +- .../contao/modules/ModuleArticle.php | 16 ++-- .../contao/modules/ModuleCustomnav.php | 6 +- .../contao/modules/ModuleNavigation.php | 4 +- .../contao/modules/ModulePersonalData.php | 6 +- .../contao/modules/ModuleQuicklink.php | 4 +- .../contao/modules/ModuleQuicknav.php | 2 +- .../contao/modules/ModuleRandomImage.php | 2 +- .../contao/modules/ModuleRegistration.php | 10 +-- .../Resources/contao/modules/ModuleSearch.php | 2 +- .../contao/modules/ModuleSitemap.php | 2 +- .../Resources/contao/pages/PageError404.php | 2 +- .../Resources/contao/pages/PageForward.php | 4 +- .../Resources/contao/pages/PageRegular.php | 46 +++++------ .../src/Resources/contao/pages/PageRoot.php | 2 +- .../Resources/contao/widgets/FileSelector.php | 20 ++--- .../src/Resources/contao/widgets/FileTree.php | 10 +-- .../Resources/contao/widgets/ImageSize.php | 2 +- .../contao/widgets/KeyValueWizard.php | 4 +- .../Resources/contao/widgets/MetaWizard.php | 2 +- .../Resources/contao/widgets/ModuleWizard.php | 2 +- .../Resources/contao/widgets/OptionWizard.php | 6 +- .../Resources/contao/widgets/PageSelector.php | 8 +- .../src/Resources/contao/widgets/PageTree.php | 10 +-- .../src/Resources/contao/widgets/Password.php | 6 +- .../src/Resources/contao/widgets/Picker.php | 8 +- .../Resources/contao/widgets/TextStore.php | 2 +- .../src/Resources/contao/dca/tl_faq.php | 2 +- .../Resources/contao/modules/ModuleFaq.php | 2 +- .../contao/modules/ModuleFaqPage.php | 2 +- .../contao/modules/ModuleFaqReader.php | 6 +- .../contao/modules/ModuleListing.php | 8 +- .../src/Resources/contao/classes/News.php | 6 +- .../src/Resources/contao/dca/tl_news.php | 4 +- .../src/Resources/contao/dca/tl_news_feed.php | 2 +- .../Resources/contao/modules/ModuleNews.php | 10 +-- .../contao/modules/ModuleNewsMenu.php | 2 +- .../contao/modules/ModuleNewsReader.php | 2 +- .../Resources/contao/classes/Newsletter.php | 24 +++--- .../Resources/contao/dca/tl_newsletter.php | 2 +- .../contao/modules/ModuleNewsletterReader.php | 2 +- 131 files changed, 469 insertions(+), 469 deletions(-) diff --git a/calendar-bundle/src/Resources/contao/classes/Calendar.php b/calendar-bundle/src/Resources/contao/classes/Calendar.php index cba95ddc2e2..582d41bb3b6 100644 --- a/calendar-bundle/src/Resources/contao/classes/Calendar.php +++ b/calendar-bundle/src/Resources/contao/classes/Calendar.php @@ -353,7 +353,7 @@ public function getSearchablePages($arrPages, $intRoot=0, $blnIsSitemap=false) } // The target page has not been published (see #5520) - if (!$objParent->published || ($objParent->start != '' && $objParent->start > $time) || ($objParent->stop != '' && $objParent->stop <= $time)) + if (!$objParent->published || ($objParent->start && $objParent->start > $time) || ($objParent->stop && $objParent->stop <= $time)) { continue; } @@ -446,7 +446,7 @@ protected function addEvent($objEvent, $intStart, $intEnd, $strUrl, $strBase='') $title .= ' ' . $objEvent->title; // Backwards compatibility (see #8329) - if ($strBase != '' && !preg_match('#^https?://#', $strUrl)) + if ($strBase && !preg_match('#^https?://#', $strUrl)) { $strUrl = $strBase . $strUrl; } diff --git a/calendar-bundle/src/Resources/contao/classes/Events.php b/calendar-bundle/src/Resources/contao/classes/Events.php index cb6a352bea1..b5afea9eed6 100644 --- a/calendar-bundle/src/Resources/contao/classes/Events.php +++ b/calendar-bundle/src/Resources/contao/classes/Events.php @@ -323,7 +323,7 @@ protected function addEvent($objEvents, $intStart, $intEnd, $intBegin, $intLimit } // Clean the RTE output - if ($arrEvent['teaser'] != '') + if ($arrEvent['teaser']) { $arrEvent['hasTeaser'] = true; $arrEvent['teaser'] = StringUtil::toHtml5($arrEvent['teaser']); diff --git a/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php b/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php index 2d443b5f83f..0ea69c2081a 100644 --- a/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php +++ b/calendar-bundle/src/Resources/contao/dca/tl_calendar_events.php @@ -668,7 +668,7 @@ public function generateAlias($varValue, Contao\DataContainer $dc) }; // Generate the alias if there is none - if ($varValue == '') + if (!$varValue) { $varValue = Contao\System::getContainer()->get('contao.slug')->generate($dc->activeRecord->title, Contao\CalendarModel::findByPk($dc->activeRecord->pid)->jumpTo, $aliasExists); } @@ -862,7 +862,7 @@ public function getSourceOptions(Contao\DataContainer $dc) } // Add the option currently set - if ($dc->activeRecord && $dc->activeRecord->source != '') + if ($dc->activeRecord && $dc->activeRecord->source) { $arrOptions[] = $dc->activeRecord->source; $arrOptions = array_unique($arrOptions); diff --git a/calendar-bundle/src/Resources/contao/dca/tl_calendar_feed.php b/calendar-bundle/src/Resources/contao/dca/tl_calendar_feed.php index f001bb4b101..de78d3f537a 100644 --- a/calendar-bundle/src/Resources/contao/dca/tl_calendar_feed.php +++ b/calendar-bundle/src/Resources/contao/dca/tl_calendar_feed.php @@ -533,7 +533,7 @@ public function getAllowedCalendars() public function checkFeedAlias($varValue, Contao\DataContainer $dc) { // No change or empty value - if ($varValue == $dc->value || $varValue == '') + if (!$varValue || $varValue == $dc->value) { return $varValue; } diff --git a/calendar-bundle/src/Resources/contao/modules/ModuleEventReader.php b/calendar-bundle/src/Resources/contao/modules/ModuleEventReader.php index afbb3753189..5bfa8b0ab52 100644 --- a/calendar-bundle/src/Resources/contao/modules/ModuleEventReader.php +++ b/calendar-bundle/src/Resources/contao/modules/ModuleEventReader.php @@ -251,7 +251,7 @@ protected function compile() } // Clean the RTE output - if ($objEvent->teaser != '') + if ($objEvent->teaser) { $objTemplate->hasTeaser = true; $objTemplate->teaser = StringUtil::toHtml5($objEvent->teaser); @@ -294,7 +294,7 @@ protected function compile() $objTemplate->addImage = false; // Add an image - if ($objEvent->addImage && $objEvent->singleSRC != '') + if ($objEvent->addImage && $objEvent->singleSRC) { $objModel = FilesModel::findByUuid($objEvent->singleSRC); @@ -304,7 +304,7 @@ protected function compile() $arrEvent = $objEvent->row(); // Override the default image size - if ($this->imgSize != '') + if ($this->imgSize) { $size = StringUtil::deserialize($this->imgSize); @@ -439,7 +439,7 @@ protected function compile() } /** @var UserModel $objAuthor */ - if ($objCalendar->notify != 'notify_admin' && ($objAuthor = $objEvent->getRelated('author')) instanceof UserModel && $objAuthor->email != '') + if ($objCalendar->notify != 'notify_admin' && ($objAuthor = $objEvent->getRelated('author')) instanceof UserModel && $objAuthor->email) { $arrNotifies[] = $objAuthor->email; } diff --git a/calendar-bundle/src/Resources/contao/modules/ModuleEventlist.php b/calendar-bundle/src/Resources/contao/modules/ModuleEventlist.php index ec791d2409c..6ace532fad1 100644 --- a/calendar-bundle/src/Resources/contao/modules/ModuleEventlist.php +++ b/calendar-bundle/src/Resources/contao/modules/ModuleEventlist.php @@ -251,7 +251,7 @@ protected function compile() $imgSize = false; // Override the default image size - if ($this->imgSize != '') + if ($this->imgSize) { $size = StringUtil::deserialize($this->imgSize); @@ -266,7 +266,7 @@ protected function compile() for ($i=$offset; $i<$limit; $i++) { - if ($arrEvents[$i]['addImage'] && $arrEvents[$i]['singleSRC'] != '') + if ($arrEvents[$i]['addImage'] && $arrEvents[$i]['singleSRC']) { $uuids[] = $arrEvents[$i]['singleSRC']; } @@ -336,7 +336,7 @@ protected function compile() $objTemplate->addImage = false; // Add an image - if ($event['addImage'] && $event['singleSRC'] != '') + if ($event['addImage'] && $event['singleSRC']) { $objModel = FilesModel::findByUuid($event['singleSRC']); @@ -379,7 +379,7 @@ protected function compile() } // No events found - if ($strEvents == '') + if (!$strEvents) { $strEvents = "\n" . '
' . $strEmpty . '
' . "\n"; } diff --git a/comments-bundle/src/Resources/contao/classes/Comments.php b/comments-bundle/src/Resources/contao/classes/Comments.php index f50b42d096f..5201ca1a176 100644 --- a/comments-bundle/src/Resources/contao/classes/Comments.php +++ b/comments-bundle/src/Resources/contao/classes/Comments.php @@ -122,7 +122,7 @@ public function addCommentsToTemplate(FrontendTemplate $objTemplate, \stdClass $ $objPartial->addReply = false; // Reply - if ($objComments->addReply && $objComments->reply != '' && ($objAuthor = $objComments->getRelated('author')) instanceof UserModel) + if ($objComments->addReply && $objComments->reply && ($objAuthor = $objComments->getRelated('author')) instanceof UserModel) { $objPartial->addReply = true; $objPartial->rby = $GLOBALS['TL_LANG']['MSC']['com_reply']; @@ -307,7 +307,7 @@ protected function renderCommentForm(FrontendTemplate $objTemplate, \stdClass $o $strWebsite = $arrWidgets['website']->value; // Add http:// to the website - if (($strWebsite != '') && !preg_match('@^(https?://|ftp://|mailto:|#)@i', $strWebsite)) + if ($strWebsite && !preg_match('@^(https?://|ftp://|mailto:|#)@i', $strWebsite)) { $strWebsite = 'http://' . $strWebsite; } @@ -404,7 +404,7 @@ protected function renderCommentForm(FrontendTemplate $objTemplate, \stdClass $o { $objEmail->sendTo(array_unique($varNotifies)); } - elseif ($varNotifies != '') + elseif ($varNotifies) { $objEmail->sendTo($varNotifies); // see #5443 } diff --git a/comments-bundle/src/Resources/contao/dca/tl_comments.php b/comments-bundle/src/Resources/contao/dca/tl_comments.php index bff3b22b473..d44e935745d 100644 --- a/comments-bundle/src/Resources/contao/dca/tl_comments.php +++ b/comments-bundle/src/Resources/contao/dca/tl_comments.php @@ -554,7 +554,7 @@ public function listComments($arrRow) return '
-
' . $arrRow['name'] . '' . (($arrRow['website'] != '') ? ' (' . $GLOBALS['TL_LANG']['MSC']['com_website'] . ')' : '') . ' – ' . Contao\Date::parse(Contao\Config::get('datimFormat'), $arrRow['date']) . ' – IP ' . Contao\StringUtil::specialchars($arrRow['ip']) . '
' . $title . '
+
' . $arrRow['name'] . '' . ($arrRow['website'] ? ' (' . $GLOBALS['TL_LANG']['MSC']['com_website'] . ')' : '') . ' – ' . Contao\Date::parse(Contao\Config::get('datimFormat'), $arrRow['date']) . ' – IP ' . Contao\StringUtil::specialchars($arrRow['ip']) . '
' . $title . '
' . $arrRow['comment'] . '
diff --git a/core-bundle/src/Resources/contao/classes/Ajax.php b/core-bundle/src/Resources/contao/classes/Ajax.php index 6f20e00f966..7d2bf1b6b15 100644 --- a/core-bundle/src/Resources/contao/classes/Ajax.php +++ b/core-bundle/src/Resources/contao/classes/Ajax.php @@ -57,7 +57,7 @@ class Ajax extends Backend */ public function __construct($strAction) { - if ($strAction == '') + if (!$strAction) { throw new \Exception('Missing Ajax action'); } @@ -257,7 +257,7 @@ public function executePostActions(DataContainer $dc) $objWidget = new $strClass($strClass::getAttributesFromDca($GLOBALS['TL_DCA'][$dc->table]['fields'][$strField], $dc->field, $varValue, $strField, $dc->table, $dc)); // Load a particular node - if (Input::post('folder', true) != '') + if (Input::post('folder', true)) { throw new ResponseException($this->convertToResponse($objWidget->generateAjax(Input::post('folder', true), Input::post('field'), (int) Input::post('level')))); } @@ -351,7 +351,7 @@ public function executePostActions(DataContainer $dc) } // Convert the selected values - if ($varValue != '') + if ($varValue) { $varValue = StringUtil::trimsplit("\t", $varValue); diff --git a/core-bundle/src/Resources/contao/classes/Backend.php b/core-bundle/src/Resources/contao/classes/Backend.php index 441fb388724..46673864f00 100644 --- a/core-bundle/src/Resources/contao/classes/Backend.php +++ b/core-bundle/src/Resources/contao/classes/Backend.php @@ -51,7 +51,7 @@ public static function getTheme() $theme = Config::get('backendTheme'); $projectDir = System::getContainer()->getParameter('kernel.project_dir'); - if ($theme != '' && $theme != 'flexible' && is_dir($projectDir . '/system/themes/' . $theme)) + if ($theme && $theme != 'flexible' && is_dir($projectDir . '/system/themes/' . $theme)) { return $theme; } @@ -92,7 +92,7 @@ public static function getTinyMceLanguage() { $lang = $GLOBALS['TL_LANGUAGE']; - if ($lang == '') + if (!$lang) { return 'en'; } @@ -229,7 +229,7 @@ public static function addToUrl($strRequest, $blnAddRef=true, $arrUnset=array()) // Unset the "no back button" flag $arrUnset[] = 'nb'; - return parent::addToUrl($strRequest . (($strRequest != '') ? '&' : '') . 'rt=' . REQUEST_TOKEN, $blnAddRef, $arrUnset); + return parent::addToUrl($strRequest . ($strRequest ? '&' : '') . 'rt=' . REQUEST_TOKEN, $blnAddRef, $arrUnset); } /** @@ -370,7 +370,7 @@ protected function getBackendModule($module, PickerInterface $picker = null) $dc = null; // Create the data container object - if ($strTable != '') + if ($strTable) { if (!\in_array($strTable, $arrTables)) { @@ -399,7 +399,7 @@ protected function getBackendModule($module, PickerInterface $picker = null) } // Fabricate a new data container object - if ($GLOBALS['TL_DCA'][$strTable]['config']['dataContainer'] == '') + if (!$GLOBALS['TL_DCA'][$strTable]['config']['dataContainer']) { $this->log('Missing data container for table "' . $strTable . '"', __METHOD__, TL_ERROR); trigger_error('Could not create a data container object', E_USER_ERROR); @@ -459,11 +459,11 @@ protected function getBackendModule($module, PickerInterface $picker = null) ->limit(1) ->execute(Input::get('id')); - if ($objRow->title != '') + if ($objRow->title) { $this->Template->headline .= ' › ' . $objRow->title . ''; } - elseif ($objRow->name != '') + elseif ($objRow->name) { $this->Template->headline .= ' › ' . $objRow->name . ''; } @@ -478,7 +478,7 @@ protected function getBackendModule($module, PickerInterface $picker = null) { $act = Input::get('act'); - if ($act == '' || $act == 'paste' || $act == 'select') + if (!$act || $act == 'paste' || $act == 'select') { $act = ($dc instanceof \listable) ? 'showAll' : 'edit'; } @@ -536,15 +536,15 @@ protected function getBackendModule($module, PickerInterface $picker = null) } // Add object title or name - if ($objRow->title != '') + if ($objRow->title) { $trail[] = ' › ' . $objRow->title . ''; } - elseif ($objRow->name != '') + elseif ($objRow->name) { $trail[] = ' › ' . $objRow->name . ''; } - elseif ($objRow->headline != '') + elseif ($objRow->headline) { $trail[] = ' › ' . $objRow->headline . ''; } @@ -673,7 +673,7 @@ public static function findSearchablePages($pid=0, $domain='', $blnIsXmlSitemap= // Recursively walk through all subpages foreach ($objPages as $objPage) { - $isPublished = ($objPage->published && ($objPage->start == '' || $objPage->start <= time()) && ($objPage->stop == '' || $objPage->stop > time())); + $isPublished = ($objPage->published && (!$objPage->start || $objPage->start <= time()) && (!$objPage->stop || $objPage->stop > time())); // Searchable and not protected if ($isPublished && $objPage->type == 'regular' && !$objPage->requireItem && (!$objPage->noSearch || $blnIsXmlSitemap) && (!$blnIsXmlSitemap || $objPage->robots != 'noindex,nofollow') && (!$objPage->protected || Config::get('indexProtected'))) @@ -767,17 +767,17 @@ public static function addFileMetaInformationToRequest($strUuid, $strPtable, $in if (isset($arrMeta[$strLanguage])) { - if (!empty($arrMeta[$strLanguage]['title']) && Input::post('title') == '') + if (!empty($arrMeta[$strLanguage]['title']) && !Input::post('title')) { Input::setPost('title', $arrMeta[$strLanguage]['title']); } - if (!empty($arrMeta[$strLanguage]['alt']) && Input::post('alt') == '') + if (!empty($arrMeta[$strLanguage]['alt']) && !Input::post('alt')) { Input::setPost('alt', $arrMeta[$strLanguage]['alt']); } - if (!empty($arrMeta[$strLanguage]['caption']) && Input::post('caption') == '') + if (!empty($arrMeta[$strLanguage]['caption']) && !Input::post('caption')) { Input::setPost('caption', $arrMeta[$strLanguage]['caption']); } @@ -958,7 +958,7 @@ public static function getSystemMessages() { $strBuffer = System::importStatic($callback[0])->{$callback[1]}(); - if ($strBuffer != '') + if ($strBuffer) { $arrMessages[] = $strBuffer; } @@ -1001,7 +1001,7 @@ public static function addFilesBreadcrumb($strKey='tl_files_node') $strNode = $objSession->get($strKey); - if ($strNode == '') + if (!$strNode) { return; } @@ -1386,7 +1386,7 @@ protected function doCreateFileList($strFolder=null, $level=-1, $strFilter='') else { // Filter images - if ($strFilter != '' && !preg_match('/\.(' . str_replace(',', '|', $strFilter) . ')$/i', $strFile)) + if ($strFilter && !preg_match('/\.(' . str_replace(',', '|', $strFilter) . ')$/i', $strFile)) { continue; } diff --git a/core-bundle/src/Resources/contao/classes/BackendUser.php b/core-bundle/src/Resources/contao/classes/BackendUser.php index 10cfb05a3c2..38179fe859d 100644 --- a/core-bundle/src/Resources/contao/classes/BackendUser.php +++ b/core-bundle/src/Resources/contao/classes/BackendUser.php @@ -171,19 +171,19 @@ public function __get($strKey) return $this->arrData['admin'] ? true : false; case 'groups': - return \is_array($this->arrData['groups']) ? $this->arrData['groups'] : (($this->arrData['groups'] != '') ? array($this->arrData['groups']) : array()); + return \is_array($this->arrData['groups']) ? $this->arrData['groups'] : ($this->arrData['groups'] ? array($this->arrData['groups']) : array()); case 'pagemounts': - return \is_array($this->arrData['pagemounts']) ? $this->arrData['pagemounts'] : (($this->arrData['pagemounts'] != '') ? array($this->arrData['pagemounts']) : false); + return \is_array($this->arrData['pagemounts']) ? $this->arrData['pagemounts'] : ($this->arrData['pagemounts'] ? array($this->arrData['pagemounts']) : false); case 'filemounts': - return \is_array($this->arrData['filemounts']) ? $this->arrData['filemounts'] : (($this->arrData['filemounts'] != '') ? array($this->arrData['filemounts']) : false); + return \is_array($this->arrData['filemounts']) ? $this->arrData['filemounts'] : ($this->arrData['filemounts'] ? array($this->arrData['filemounts']) : false); case 'filemountIds': return $this->arrFilemountIds; case 'fop': - return \is_array($this->arrData['fop']) ? $this->arrData['fop'] : (($this->arrData['fop'] != '') ? array($this->arrData['fop']) : false); + return \is_array($this->arrData['fop']) ? $this->arrData['fop'] : ($this->arrData['fop'] ? array($this->arrData['fop']) : false); case 'alexf': return $this->alexf; @@ -450,7 +450,7 @@ protected function setUserFromDb() // The new page/file picker can return integers instead of arrays, so use empty() instead of is_array() and StringUtil::deserialize(true) here if (!empty($value)) { - $this->$field = array_merge((\is_array($this->$field) ? $this->$field : (($this->$field != '') ? array($this->$field) : array())), $value); + $this->$field = array_merge((\is_array($this->$field) ? $this->$field : ($this->$field ? array($this->$field) : array())), $value); $this->$field = array_unique($this->$field); } } diff --git a/core-bundle/src/Resources/contao/classes/DataContainer.php b/core-bundle/src/Resources/contao/classes/DataContainer.php index 6cc199f474b..05289693474 100644 --- a/core-bundle/src/Resources/contao/classes/DataContainer.php +++ b/core-bundle/src/Resources/contao/classes/DataContainer.php @@ -380,7 +380,7 @@ protected function row($strPalette=null) if ($objWidget->hasErrors()) { // Skip mandatory fields on auto-submit (see #4077) - if (!$objWidget->mandatory || $objWidget->value != '' || Input::post('SUBMIT_TYPE') != 'auto') + if (!$objWidget->mandatory || $objWidget->value || Input::post('SUBMIT_TYPE') != 'auto') { $this->noReload = true; } @@ -517,7 +517,7 @@ protected function row($strPalette=null) } } - if ($wizard != '') + if ($wizard) { $objWidget->wizard = $wizard; @@ -681,7 +681,7 @@ public function help($strClass='') { $return = $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['label'][1]; - if ($return == '' || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] == 'password' || !Config::get('showHelp')) + if (!$return || $GLOBALS['TL_DCA'][$this->strTable]['fields'][$this->strField]['inputType'] == 'password' || !Config::get('showHelp')) { return ''; } @@ -908,12 +908,12 @@ protected function generateGlobalButtons() $attributes = sprintf(' style="background-image:url(\'%s\')"', Controller::addAssetsUrlTo($v['icon'])) . $attributes; } - if ($label == '') + if (!$label) { $label = $k; } - if ($title == '') + if (!$title) { $title = $label; } @@ -1120,7 +1120,7 @@ protected function getPickerInputField($value, $attributes='') */ protected function panel() { - if ($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout'] == '') + if (!$GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['panelLayout']) { return ''; } @@ -1187,14 +1187,14 @@ protected function panel() } // Add the panel if it is not empty - if ($panel != '') + if ($panel) { $panels = $panel . $panels; } } // Add the group if it is not empty - if ($panels != '') + if ($panels) { $arrPanels[] = $panels; } diff --git a/core-bundle/src/Resources/contao/classes/FileUpload.php b/core-bundle/src/Resources/contao/classes/FileUpload.php index e10733712b9..747cd6c2ed5 100644 --- a/core-bundle/src/Resources/contao/classes/FileUpload.php +++ b/core-bundle/src/Resources/contao/classes/FileUpload.php @@ -86,7 +86,7 @@ public function setName($strName) */ public function uploadTo($strTarget) { - if ($strTarget == '' || Validator::isInsecurePath($strTarget)) + if (!$strTarget || Validator::isInsecurePath($strTarget)) { throw new \InvalidArgumentException('Invalid target path ' . $strTarget); } @@ -235,7 +235,7 @@ protected function getFilesFromGlobal() for ($i=0; $i<$intCount; $i++) { - if ($_FILES[$this->strName]['name'][$i] == '') + if (!$_FILES[$this->strName]['name'][$i]) { continue; } diff --git a/core-bundle/src/Resources/contao/classes/Frontend.php b/core-bundle/src/Resources/contao/classes/Frontend.php index e5ad067d3ac..d8a22413887 100644 --- a/core-bundle/src/Resources/contao/classes/Frontend.php +++ b/core-bundle/src/Resources/contao/classes/Frontend.php @@ -69,7 +69,7 @@ public static function getPageIdFromUrl() $strRequest = Environment::get('relativeRequest'); - if ($strRequest == '') + if (!$strRequest) { return null; } @@ -97,7 +97,7 @@ public static function getPageIdFromUrl() Input::setGet('language', $arrMatches[1]); // Trigger the root page if only the language was given - if ($arrMatches[3] == '') + if (!$arrMatches[3]) { return null; } @@ -111,7 +111,7 @@ public static function getPageIdFromUrl() } // Remove the URL suffix if not just a language root (e.g. en/) is requested - if ($strRequest != '' && (!Config::get('addLanguageToUrl') || !preg_match('@^[a-z]{2}(-[A-Z]{2})?/$@', $strRequest))) + if ($strRequest && (!Config::get('addLanguageToUrl') || !preg_match('@^[a-z]{2}(-[A-Z]{2})?/$@', $strRequest))) { $intSuffixLength = \strlen(Config::get('urlSuffix')); @@ -240,7 +240,7 @@ public static function getPageIdFromUrl() } // Return if the alias is empty (see #4702 and #4972) - if ($arrFragments[0] == '' && \count($arrFragments) > 1) + if (!$arrFragments[0] && \count($arrFragments) > 1) { return false; } @@ -249,7 +249,7 @@ public static function getPageIdFromUrl() for ($i=1, $c=\count($arrFragments); $i<$c; $i+=2) { // Return false if the key is empty (see #4702 and #263) - if ($arrFragments[$i] == '') + if (!$arrFragments[$i]) { return false; } @@ -353,7 +353,7 @@ public static function getRootPageFromUrl() } // Redirect to the website root or language root (e.g. en/) - if (Environment::get('relativeRequest') == '') + if (!Environment::get('relativeRequest')) { if (Config::get('addLanguageToUrl') && !Config::get('doNotRedirectEmpty')) { @@ -406,7 +406,7 @@ public static function addToUrl($strRequest, $blnIgnoreParams=false, $arrUnset=a { list($key, $value) = explode('=', $strFragment); - if ($value == '') + if (!$value) { unset($arrGet[$key]); } diff --git a/core-bundle/src/Resources/contao/classes/FrontendTemplate.php b/core-bundle/src/Resources/contao/classes/FrontendTemplate.php index 65db244d093..24fa2430235 100644 --- a/core-bundle/src/Resources/contao/classes/FrontendTemplate.php +++ b/core-bundle/src/Resources/contao/classes/FrontendTemplate.php @@ -322,7 +322,7 @@ public function getCustomSections($strKey=null) { @trigger_error('Using FrontendTemplate::getCustomSections() has been deprecated and will no longer work in Contao 5.0. Use FrontendTemplate::sections() instead.', E_USER_DEPRECATED); - if ($strKey != '' && !isset($this->positions[$strKey])) + if ($strKey && !isset($this->positions[$strKey])) { return ''; } @@ -346,7 +346,7 @@ public function getCustomSections($strKey=null) } } - if ($sections == '') + if (!$sections) { return ''; } diff --git a/core-bundle/src/Resources/contao/classes/FrontendUser.php b/core-bundle/src/Resources/contao/classes/FrontendUser.php index 99806cf0f57..2ddf3e3c70c 100644 --- a/core-bundle/src/Resources/contao/classes/FrontendUser.php +++ b/core-bundle/src/Resources/contao/classes/FrontendUser.php @@ -228,7 +228,7 @@ protected function setUserFromDb() // Make sure that groups is an array if (!\is_array($this->groups)) { - $this->groups = ($this->groups != '') ? array($this->groups) : array(); + $this->groups = $this->groups ? array($this->groups) : array(); } // Skip inactive groups diff --git a/core-bundle/src/Resources/contao/classes/Hybrid.php b/core-bundle/src/Resources/contao/classes/Hybrid.php index fbd3210b972..eedb9dea5db 100644 --- a/core-bundle/src/Resources/contao/classes/Hybrid.php +++ b/core-bundle/src/Resources/contao/classes/Hybrid.php @@ -98,7 +98,7 @@ public function __construct($objElement, $strColumn='main') $this->objParent = $objModel; } - if ($this->strKey == '' || $this->strTable == '') + if (!$this->strKey || !$this->strTable) { return; } @@ -237,12 +237,12 @@ public function generate() $this->Template->inColumn = $this->strColumn; - if ($this->Template->headline == '') + if (!$this->Template->headline) { $this->Template->headline = $this->headline; } - if ($this->Template->hl == '') + if (!$this->Template->hl) { $this->Template->hl = $this->hl; } @@ -263,7 +263,7 @@ protected function isHidden() return false; } - $isInvisible = $this->objParent->invisible || ($this->objParent->start != '' && $this->objParent->start > time()) || ($this->objParent->stop != '' && $this->objParent->stop <= time()); + $isInvisible = $this->objParent->invisible || ($this->objParent->start && $this->objParent->start > time()) || ($this->objParent->stop && $this->objParent->stop <= time()); // The element is visible, so show it if (!$isInvisible) diff --git a/core-bundle/src/Resources/contao/classes/Messages.php b/core-bundle/src/Resources/contao/classes/Messages.php index e99d3a6a608..7a259f1b2ff 100644 --- a/core-bundle/src/Resources/contao/classes/Messages.php +++ b/core-bundle/src/Resources/contao/classes/Messages.php @@ -87,7 +87,7 @@ public function languageFallback() foreach ($arrRoots as $k=>$v) { - if ($v != '') + if ($v) { continue; } diff --git a/core-bundle/src/Resources/contao/classes/PurgeData.php b/core-bundle/src/Resources/contao/classes/PurgeData.php index cbffa701c37..fbd86dca3e4 100644 --- a/core-bundle/src/Resources/contao/classes/PurgeData.php +++ b/core-bundle/src/Resources/contao/classes/PurgeData.php @@ -141,7 +141,7 @@ public function run() $objTemplate->job = $GLOBALS['TL_LANG']['tl_maintenance']['job']; $objTemplate->description = $GLOBALS['TL_LANG']['tl_maintenance']['description']; $objTemplate->submit = StringUtil::specialchars($GLOBALS['TL_LANG']['tl_maintenance']['clearCache']); - $objTemplate->help = (Config::get('showHelp') && ($GLOBALS['TL_LANG']['tl_maintenance']['cacheTables'][1] != '')) ? $GLOBALS['TL_LANG']['tl_maintenance']['cacheTables'][1] : ''; + $objTemplate->help = (Config::get('showHelp') && $GLOBALS['TL_LANG']['tl_maintenance']['cacheTables'][1]) ? $GLOBALS['TL_LANG']['tl_maintenance']['cacheTables'][1] : ''; return $objTemplate->parse(); } diff --git a/core-bundle/src/Resources/contao/classes/Theme.php b/core-bundle/src/Resources/contao/classes/Theme.php index 4b0f00f9c5a..367d0f36e24 100644 --- a/core-bundle/src/Resources/contao/classes/Theme.php +++ b/core-bundle/src/Resources/contao/classes/Theme.php @@ -1087,7 +1087,7 @@ protected function addFolderToArchive(ZipWriter $objArchive, $strFolder, \DOMDoc $strFolder = preg_replace('@^' . preg_quote(Config::get('uploadPath'), '@') . '/@', '', $strFolder); // Add the default upload folder name - if ($strFolder == '') + if (!$strFolder) { $strTarget = 'files'; $strFolder = Config::get('uploadPath'); @@ -1163,7 +1163,7 @@ protected function addTemplatesToArchive(ZipWriter $objArchive, $strFolder) $strFolder = preg_replace('@^templates/@', '', $strFolder); // Re-add the templates folder name - if ($strFolder == '') + if (!$strFolder) { $strFolder = 'templates'; } @@ -1202,7 +1202,7 @@ protected function addTemplatesToArchive(ZipWriter $objArchive, $strFolder) */ protected function customizeUploadPath($strPath) { - if ($strPath == '') + if (!$strPath) { return ''; } @@ -1219,7 +1219,7 @@ protected function customizeUploadPath($strPath) */ protected function standardizeUploadPath($strPath) { - if ($strPath == '') + if (!$strPath) { return ''; } diff --git a/core-bundle/src/Resources/contao/classes/Versions.php b/core-bundle/src/Resources/contao/classes/Versions.php index 8ec0d402c73..287c5cef331 100644 --- a/core-bundle/src/Resources/contao/classes/Versions.php +++ b/core-bundle/src/Resources/contao/classes/Versions.php @@ -577,7 +577,7 @@ public function compare($blnReturnBuffer=false) } // Identical versions - if ($strBuffer == '') + if (!$strBuffer) { $strBuffer = '

' . $GLOBALS['TL_LANG']['MSC']['identicalVersions'] . '

'; } diff --git a/core-bundle/src/Resources/contao/controllers/BackendMain.php b/core-bundle/src/Resources/contao/controllers/BackendMain.php index e1558fc0518..921f845d3c0 100644 --- a/core-bundle/src/Resources/contao/controllers/BackendMain.php +++ b/core-bundle/src/Resources/contao/controllers/BackendMain.php @@ -217,13 +217,13 @@ protected function welcomeScreen() protected function output() { // Default headline - if ($this->Template->headline == '') + if (!$this->Template->headline) { $this->Template->headline = $GLOBALS['TL_LANG']['MSC']['dashboard']; } // Default title - if ($this->Template->title == '') + if (!$this->Template->title) { $this->Template->title = $this->Template->headline; } diff --git a/core-bundle/src/Resources/contao/controllers/BackendPopup.php b/core-bundle/src/Resources/contao/controllers/BackendPopup.php index 4aa6ef54038..47cf29bd1d4 100644 --- a/core-bundle/src/Resources/contao/controllers/BackendPopup.php +++ b/core-bundle/src/Resources/contao/controllers/BackendPopup.php @@ -61,7 +61,7 @@ public function __construct() */ public function run() { - if ($this->strFile == '') + if (!$this->strFile) { die('No file given'); } diff --git a/core-bundle/src/Resources/contao/controllers/FrontendIndex.php b/core-bundle/src/Resources/contao/controllers/FrontendIndex.php index ffa2aa537d2..6810fce35c9 100644 --- a/core-bundle/src/Resources/contao/controllers/FrontendIndex.php +++ b/core-bundle/src/Resources/contao/controllers/FrontendIndex.php @@ -171,7 +171,7 @@ public function renderPage($pageModel) } // If the page has an alias, it can no longer be called via ID (see #7661) - if ($objPage->alias != '') + if ($objPage->alias) { $language = Config::get('addLanguageToUrl') ? '[a-z]{2}(-[A-Z]{2})?/' : ''; $suffix = preg_quote(Config::get('urlSuffix'), '#'); @@ -227,7 +227,7 @@ public function renderPage($pageModel) } // Set the admin e-mail address - if ($objPage->adminEmail != '') + if ($objPage->adminEmail) { list($GLOBALS['TL_ADMIN_NAME'], $GLOBALS['TL_ADMIN_EMAIL']) = StringUtil::splitFriendlyEmail($objPage->adminEmail); } @@ -250,7 +250,7 @@ public function renderPage($pageModel) } // Check whether there are domain name restrictions - if ($objPage->domain != '' && $objPage->domain != Environment::get('host')) + if ($objPage->domain && $objPage->domain != Environment::get('host')) { $this->log('Page ID "' . $objPage->id . '" was requested via "' . Environment::get('host') . '" but can only be accessed via "' . $objPage->domain . '" (' . Environment::get('base') . Environment::get('request') . ')', __METHOD__, TL_ERROR); diff --git a/core-bundle/src/Resources/contao/dca/tl_article.php b/core-bundle/src/Resources/contao/dca/tl_article.php index 90353979311..09e0cdcd702 100644 --- a/core-bundle/src/Resources/contao/dca/tl_article.php +++ b/core-bundle/src/Resources/contao/dca/tl_article.php @@ -540,7 +540,7 @@ public function addIcon($row, $label) { $image = 'articles'; - $unpublished = ($row['start'] != '' && $row['start'] > time()) || ($row['stop'] != '' && $row['stop'] <= time()); + $unpublished = ($row['start'] && $row['start'] > time()) || ($row['stop'] && $row['stop'] <= time()); if ($unpublished || !$row['published']) { @@ -573,7 +573,7 @@ public function generateAlias($varValue, Contao\DataContainer $dc) }; // Generate an alias if there is none - if ($varValue == '') + if (!$varValue) { $varValue = Contao\System::getContainer()->get('contao.slug')->generate($dc->activeRecord->title, $dc->activeRecord->pid, $aliasExists); } diff --git a/core-bundle/src/Resources/contao/dca/tl_content.php b/core-bundle/src/Resources/contao/dca/tl_content.php index b5013de5914..ca48a6895d9 100644 --- a/core-bundle/src/Resources/contao/dca/tl_content.php +++ b/core-bundle/src/Resources/contao/dca/tl_content.php @@ -1470,11 +1470,11 @@ public function getAlias() $text = Contao\StringUtil::substr(strip_tags(preg_replace('/[\n\r\t]+/', ' ', $objAlias->text)), 32); $strText = $GLOBALS['TL_LANG']['CTE'][$objAlias->type][0] . ' ('; - if ($headline != '') + if ($headline) { $strText .= $headline . ', '; } - elseif ($text != '') + elseif ($text) { $strText .= $text . ', '; } diff --git a/core-bundle/src/Resources/contao/dca/tl_files.php b/core-bundle/src/Resources/contao/dca/tl_files.php index 0ac4699f6a6..db356f31df1 100644 --- a/core-bundle/src/Resources/contao/dca/tl_files.php +++ b/core-bundle/src/Resources/contao/dca/tl_files.php @@ -582,7 +582,7 @@ public function checkFilename($varValue, Contao\DataContainer $dc) } // Check the length without the file extension - if ($dc->activeRecord && $varValue != '') + if ($dc->activeRecord && $varValue) { $intMaxlength = $GLOBALS['TL_DCA'][$dc->table]['fields'][$dc->field]['eval']['maxlength']; diff --git a/core-bundle/src/Resources/contao/dca/tl_form.php b/core-bundle/src/Resources/contao/dca/tl_form.php index f873574ee6f..4d9b0c09f90 100644 --- a/core-bundle/src/Resources/contao/dca/tl_form.php +++ b/core-bundle/src/Resources/contao/dca/tl_form.php @@ -473,7 +473,7 @@ public function generateAlias($varValue, Contao\DataContainer $dc) }; // Generate an alias if there is none - if ($varValue == '') + if (!$varValue) { $varValue = Contao\System::getContainer()->get('contao.slug')->generate($dc->activeRecord->title, Contao\Input::post('jumpTo') ?: $dc->activeRecord->jumpTo, $aliasExists); } diff --git a/core-bundle/src/Resources/contao/dca/tl_member.php b/core-bundle/src/Resources/contao/dca/tl_member.php index 6410b4e8097..79ec0a22d02 100644 --- a/core-bundle/src/Resources/contao/dca/tl_member.php +++ b/core-bundle/src/Resources/contao/dca/tl_member.php @@ -491,7 +491,7 @@ public function switchUser($row, $href, $label, $title, $icon) return ''; } - if (!$row['login'] || $row['username'] == '' || (!$this->User->isAdmin && count(array_intersect(Contao\StringUtil::deserialize($row['groups'], true), $this->User->amg)) < 1)) + if (!$row['login'] || !$row['username'] || (!$this->User->isAdmin && count(array_intersect(Contao\StringUtil::deserialize($row['groups'], true), $this->User->amg)) < 1)) { return Contao\Image::getHtml(preg_replace('/\.svg$/i', '_.svg', $icon)) . ' '; } diff --git a/core-bundle/src/Resources/contao/dca/tl_page.php b/core-bundle/src/Resources/contao/dca/tl_page.php index bad591915bc..22a5502fb51 100644 --- a/core-bundle/src/Resources/contao/dca/tl_page.php +++ b/core-bundle/src/Resources/contao/dca/tl_page.php @@ -805,7 +805,7 @@ public function checkPermission() { $permission = 0; $cid = CURRENT_ID ?: Contao\Input::get('id'); - $ids = ($cid != '') ? array($cid) : array(); + $ids = $cid ? array($cid) : array(); // Set permission switch (Contao\Input::get('act')) @@ -997,7 +997,7 @@ public function getSerpUrl(Contao\PageModel $model) */ public function showFallbackWarning() { - if (Contao\Input::get('act') != '') + if (Contao\Input::get('act')) { return; } @@ -1132,7 +1132,7 @@ public function generateAlias($varValue, Contao\DataContainer $dc) }; // Generate an alias if there is none - if ($varValue == '') + if (!$varValue) { $varValue = Contao\System::getContainer()->get('contao.slug')->generate ( @@ -1145,7 +1145,7 @@ static function ($alias) use ($objPage, $aliasExists) ); // Generate folder URL aliases (see #4933) - if (Contao\Config::get('folderUrl') && $objPage->folderUrl != '') + if (Contao\Config::get('folderUrl') && $objPage->folderUrl) { $varValue = $objPage->folderUrl . $varValue; } @@ -1177,7 +1177,7 @@ public function generateArticle(Contao\DataContainer $dc) } // No title or not a regular page - if ($dc->activeRecord->title == '' || !in_array($dc->activeRecord->type, array('regular', 'error_401', 'error_403', 'error_404'))) + if (!$dc->activeRecord->title || !in_array($dc->activeRecord->type, array('regular', 'error_401', 'error_403', 'error_404'))) { return; } @@ -1253,7 +1253,7 @@ public function purgeSearchIndex(Contao\DataContainer $dc) public function checkFeedAlias($varValue, Contao\DataContainer $dc) { // No change or empty value - if ($varValue == $dc->value || $varValue == '') + if (!$varValue || $varValue == $dc->value) { return $varValue; } @@ -1316,7 +1316,7 @@ public function checkDns($varValue) */ public function checkFallback($varValue, Contao\DataContainer $dc) { - if ($varValue == '') + if (!$varValue) { return ''; } @@ -1341,7 +1341,7 @@ public function checkFallback($varValue, Contao\DataContainer $dc) */ public function checkStaticUrl($varValue) { - if ($varValue != '') + if ($varValue) { $varValue = preg_replace('@https?://@', '', $varValue); } diff --git a/core-bundle/src/Resources/contao/dca/tl_style_sheet.php b/core-bundle/src/Resources/contao/dca/tl_style_sheet.php index ed13c107041..24b508b0d71 100644 --- a/core-bundle/src/Resources/contao/dca/tl_style_sheet.php +++ b/core-bundle/src/Resources/contao/dca/tl_style_sheet.php @@ -295,12 +295,12 @@ public function listStyleSheet($row) $cc = ''; $media = Contao\StringUtil::deserialize($row['media']); - if ($row['cc'] != '') + if ($row['cc']) { $cc = ' <!--[' . $row['cc'] . ']>'; } - if ($row['mediaQuery'] != '') + if ($row['mediaQuery']) { return '
' . $row['name'] . ' @media ' . $row['mediaQuery'] . $cc . '' . "
\n"; } @@ -334,7 +334,7 @@ public function romanizeName($varValue) */ public function sanitizeCc($varValue) { - if ($varValue != '') + if ($varValue) { $varValue = str_replace(array('||/g,"")}),i.javascript&&this.options.evalScripts&&Browser.exec(i.javascript)),this.onSuccess(i.content,i)}}}),Request.Mixed=Request.Contao,Tips.Contao=new Class({Extends:Tips,options:{id:"tip",onShow:function(){this.tip.setStyle("display","block")},onHide:function(){this.tip.setStyle("display","none")},title:"title",text:"",showDelay:1e3,hideDelay:100,className:"tip-wrap",offset:{x:16,y:16},windowPadding:{x:0,y:0},fixed:!0,waiAria:!0},position:function(t){this.tip||document.id(this);var e=window.getSize(),i=window.getScroll(),n={x:this.tip.offsetWidth,y:this.tip.offsetHeight},s={x:"left",y:"top"},o={y:!1,x2:!1,y2:!1,x:!1},r={};for(var a in s)r[s[a]]=t.page[a]+this.options.offset[a],r[s[a]]<0&&(o[a]=!0),r[s[a]]+n[a]-i[a]>e[a]-this.options.windowPadding[a]&&("x"==a&&(r[s[a]]=t.page[a]-this.options.offset[a]-n[a]),o[a+"2"]=!0);var h=this.tip.getElement("div.tip-top");o.x2?(r.left+=24,h.setStyles({left:"auto",right:"9px"})):(r.left-=9,h.setStyles({left:"9px",right:"auto"})),this.fireEvent("bound",o),this.tip.setStyles(r)},hide:function(t){this.tip||document.id(this),this.fireEvent("hide",[this.tip,t])}}),Class.refactor(Drag,{attach:function(){return this.handles.addEvent("touchstart",this.bound.start),this.previous.apply(this,arguments)},detach:function(){return this.handles.removeEvent("touchstart",this.bound.start),this.previous.apply(this,arguments)},start:function(){document.addEvents({touchmove:this.bound.check,touchend:this.bound.cancel}),this.previous.apply(this,arguments)},check:function(t){this.options.preventDefault&&t.preventDefault(),Math.round(Math.sqrt(Math.pow(t.page.x-this.mouse.start.x,2)+Math.pow(t.page.y-this.mouse.start.y,2)))>this.options.snap&&(this.cancel(),this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop}),document.addEvents({touchmove:this.bound.drag,touchend:this.bound.stop}),this.fireEvent("start",[this.element,t]).fireEvent("snap",this.element))},cancel:function(){return document.removeEvents({touchmove:this.bound.check,touchend:this.bound.cancel}),this.previous.apply(this,arguments)},stop:function(){return document.removeEvents({touchmove:this.bound.drag,touchend:this.bound.stop}),this.previous.apply(this,arguments)}}),Class.refactor(Sortables,{initialize:function(t,e){return e.dragOptions=Object.merge(e.dragOptions||{},{preventDefault:e.dragOptions&&e.dragOptions.preventDefault||Browser.Features.Touch}),void 0===e.dragOptions.unDraggableTags&&(e.dragOptions.unDraggableTags=this.options.unDraggableTags.filter(function(t){return"button"!=t})),this.previous.apply(this,arguments)},addItems:function(){return Array.flatten(arguments).each(function(e){this.elements.push(e);var t=e.retrieve("sortables:start",function(t){this.start.call(this,t,e)}.bind(this));(this.options.handle&&e.getElement(this.options.handle)||e).addEvents({mousedown:t,touchstart:t})},this),this},removeItems:function(){return $$(Array.flatten(arguments).map(function(t){this.elements.erase(t);var e=t.retrieve("sortables:start");return(this.options.handle&&t.getElement(this.options.handle)||t).removeEvents({mousedown:e,touchend:e}),t},this))},getClone:function(t,e){if(!this.options.clone)return new Element(e.tagName).inject(document.body);if("function"==typeOf(this.options.clone))return this.options.clone.call(this,t,e,this.list);var i=this.previous.apply(this,arguments);return i.addEvent("touchstart",function(t){e.fireEvent("touchstart",t)}),i}}),Class.refactor(Request.Queue,{onComplete:function(){this.fireEvent("complete",arguments)},onCancel:function(){this.options.autoAdvance&&!this.error&&this.resume(),this.fireEvent("cancel",arguments)},onSuccess:function(){this.options.autoAdvance&&!this.error&&this.resume(),this.fireEvent("success",arguments),this.queue.length||this.isRunning()||this.fireEvent("end")},onFailure:function(){this.error=!0,!this.options.stopOnFailure&&this.options.autoAdvance&&this.resume(),this.fireEvent("failure",arguments),this.queue.length||this.isRunning()||this.fireEvent("end")},onException:function(){this.error=!0,!this.options.stopOnFailure&&this.options.autoAdvance&&this.resume(),this.fireEvent("exception",arguments)}}),Contao.SerpPreview=new Class({options:{id:0,trail:null,titleField:null,titleFallbackField:null,aliasField:null,descriptionField:null,descriptionFallbackField:null},shorten:function(t,e){return t.length<=e?t:t.substr(0,t.lastIndexOf(" ",e))+" …"},html2string:function(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent},getTinymce:function(){if(window.tinyMCE&&this.options.descriptionFallbackField)return window.tinyMCE.get(this.options.descriptionFallbackField)},initialize:function(){this.options=Object.merge.apply(null,[{},this.options].append(arguments));var t=$("serp_title_"+this.options.id),e=$("serp_url_"+this.options.id),i=$("serp_description_"+this.options.id),n=$(this.options.titleField),s=$(this.options.titleFallbackField),o=$(this.options.aliasField),r=$(this.options.descriptionField),a=$(this.options.descriptionFallbackField),h=-1===this.options.trail.indexOf("›");n&&n.addEvent("input",function(){n.value?t.set("text",this.shorten(n.value,64)):s&&s.value?t.set("text",this.shorten(this.html2string(s.value),64)):t.set("text","")}.bind(this)),s&&s.addEvent("input",function(){n&&n.value||t.set("text",this.shorten(this.html2string(s.value),64))}.bind(this)),o&&o.addEvent("input",function(){"index"==o.value&&h?e.set("text",this.options.trail):e.set("text",this.options.trail+" › "+(o.value||this.options.id).replace(/\//g," › "))}.bind(this)),r&&r.addEvent("input",function(){if(r.value)i.set("text",this.shorten(r.value,160));else{var t=this.getTinymce();t?i.set("text",this.shorten(this.html2string(t.getContent()),160)):a&&a.value?i.set("text",this.shorten(this.html2string(a.value),160)):i.set("text","")}}.bind(this)),a&&a.addEvent("input",function(){r&&r.value||i.set("text",this.shorten(this.html2string(a.value),160))}.bind(this)),setTimeout(function(){var t=this.getTinymce();t&&t.on("keyup",function(){r&&r.value||i.set("text",this.shorten(this.html2string(window.tinyMCE.activeEditor.getContent()),160))}.bind(this))}.bind(this),4)}}); \ No newline at end of file +Request.Contao=new Class({Extends:Request.JSON,options:{followRedirects:!0,url:window.location.href},initialize:function(t){t&&!t.url&&t.field&&t.field.form&&t.field.form.action&&(this.options.url=t.field.form.action),this.parent(t)},success:function(e){var i,t=this.getHeader("X-Ajax-Location");if(t&&this.options.followRedirects)location.replace(t);else{try{i=this.response.json=JSON.decode(e,this.options.secure)}catch(t){i={content:e}}null===i?i={content:""}:"object"!=typeof i&&(i={content:e}),""!=i.content&&(i.content=i.content.stripScripts(function(t){i.javascript=t.replace(/||/g,"")}),i.javascript&&this.options.evalScripts&&Browser.exec(i.javascript)),this.onSuccess(i.content,i)}}}),Request.Mixed=Request.Contao,Tips.Contao=new Class({Extends:Tips,options:{id:"tip",onShow:function(){this.tip.setStyle("display","block")},onHide:function(){this.tip.setStyle("display","none")},title:"title",text:"",showDelay:1e3,hideDelay:100,className:"tip-wrap",offset:{x:16,y:16},windowPadding:{x:0,y:0},fixed:!0,waiAria:!0},position:function(t){this.tip||document.id(this);var e=window.getSize(),i=window.getScroll(),s={x:this.tip.offsetWidth,y:this.tip.offsetHeight},n={x:"left",y:"top"},o={y:!1,x2:!1,y2:!1,x:!1},r={};for(var a in n)r[n[a]]=t.page[a]+this.options.offset[a],r[n[a]]<0&&(o[a]=!0),r[n[a]]+s[a]-i[a]>e[a]-this.options.windowPadding[a]&&("x"==a&&(r[n[a]]=t.page[a]-this.options.offset[a]-s[a]),o[a+"2"]=!0);var h=this.tip.getElement("div.tip-top");o.x2?(r.left+=24,h.setStyles({left:"auto",right:"9px"})):(r.left-=9,h.setStyles({left:"9px",right:"auto"})),this.fireEvent("bound",o),this.tip.setStyles(r)},hide:function(t){this.tip||document.id(this),this.fireEvent("hide",[this.tip,t])}}),Class.refactor(Drag,{attach:function(){return this.handles.addEvent("touchstart",this.bound.start),this.previous.apply(this,arguments)},detach:function(){return this.handles.removeEvent("touchstart",this.bound.start),this.previous.apply(this,arguments)},start:function(){document.addEvents({touchmove:this.bound.check,touchend:this.bound.cancel}),this.previous.apply(this,arguments)},check:function(t){this.options.preventDefault&&t.preventDefault(),Math.round(Math.sqrt(Math.pow(t.page.x-this.mouse.start.x,2)+Math.pow(t.page.y-this.mouse.start.y,2)))>this.options.snap&&(this.cancel(),this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop}),document.addEvents({touchmove:this.bound.drag,touchend:this.bound.stop}),this.fireEvent("start",[this.element,t]).fireEvent("snap",this.element))},cancel:function(){return document.removeEvents({touchmove:this.bound.check,touchend:this.bound.cancel}),this.previous.apply(this,arguments)},stop:function(){return document.removeEvents({touchmove:this.bound.drag,touchend:this.bound.stop}),this.previous.apply(this,arguments)}}),Class.refactor(Sortables,{initialize:function(t,e){return e.dragOptions=Object.merge(e.dragOptions||{},{preventDefault:e.dragOptions&&e.dragOptions.preventDefault||Browser.Features.Touch}),void 0===e.dragOptions.unDraggableTags&&(e.dragOptions.unDraggableTags=this.options.unDraggableTags.filter(function(t){return"button"!=t})),this.previous.apply(this,arguments)},addItems:function(){return Array.flatten(arguments).each(function(e){this.elements.push(e);var t=e.retrieve("sortables:start",function(t){this.start.call(this,t,e)}.bind(this));(this.options.handle&&e.getElement(this.options.handle)||e).addEvents({mousedown:t,touchstart:t})},this),this},removeItems:function(){return $$(Array.flatten(arguments).map(function(t){this.elements.erase(t);var e=t.retrieve("sortables:start");return(this.options.handle&&t.getElement(this.options.handle)||t).removeEvents({mousedown:e,touchend:e}),t},this))},getClone:function(t,e){if(!this.options.clone)return new Element(e.tagName).inject(document.body);if("function"==typeOf(this.options.clone))return this.options.clone.call(this,t,e,this.list);var i=this.previous.apply(this,arguments);return i.addEvent("touchstart",function(t){e.fireEvent("touchstart",t)}),i}}),Class.refactor(Request.Queue,{onComplete:function(){this.fireEvent("complete",arguments)},onCancel:function(){this.options.autoAdvance&&!this.error&&this.resume(),this.fireEvent("cancel",arguments)},onSuccess:function(){this.options.autoAdvance&&!this.error&&this.resume(),this.fireEvent("success",arguments),this.queue.length||this.isRunning()||this.fireEvent("end")},onFailure:function(){this.error=!0,!this.options.stopOnFailure&&this.options.autoAdvance&&this.resume(),this.fireEvent("failure",arguments),this.queue.length||this.isRunning()||this.fireEvent("end")},onException:function(){this.error=!0,!this.options.stopOnFailure&&this.options.autoAdvance&&this.resume(),this.fireEvent("exception",arguments)}}),Contao.SerpPreview=new Class({options:{id:0,trail:null,titleField:null,titleFallbackField:null,aliasField:null,descriptionField:null,descriptionFallbackField:null,titleTag:null},shorten:function(t,e){return t.length<=e?t:t.substr(0,t.lastIndexOf(" ",e))+" …"},html2string:function(t){return(new DOMParser).parseFromString(t,"text/html").body.textContent},getTinymce:function(){if(window.tinyMCE&&this.options.descriptionFallbackField)return window.tinyMCE.get(this.options.descriptionFallbackField)},initialize:function(){this.options=Object.merge.apply(null,[{},this.options].append(arguments));var t=$("serp_title_"+this.options.id),e=$("serp_url_"+this.options.id),i=$("serp_description_"+this.options.id),s=$(this.options.titleField),n=$(this.options.titleFallbackField),o=$(this.options.aliasField),r=$(this.options.descriptionField),a=$(this.options.descriptionFallbackField),h=-1===this.options.trail.indexOf("›"),l=this.options.titleTag||"%s";s&&s.addEvent("input",function(){s.value?t.set("text",this.shorten(l.replace(/\%s/,s.value),64)):n&&n.value?t.set("text",this.shorten(this.html2string(l.replace(/\%s/,n.value)),64)):t.set("text","")}.bind(this)),n&&n.addEvent("input",function(){s&&s.value||t.set("text",this.shorten(this.html2string(l.replace(/\%s/,n.value)),64))}.bind(this)),o&&o.addEvent("input",function(){"index"==o.value&&h?e.set("text",this.options.trail):e.set("text",this.options.trail+" › "+(o.value||this.options.id).replace(/\//g," › "))}.bind(this)),r&&r.addEvent("input",function(){if(r.value)i.set("text",this.shorten(r.value,160));else{var t=this.getTinymce();t?i.set("text",this.shorten(this.html2string(t.getContent()),160)):a&&a.value?i.set("text",this.shorten(this.html2string(a.value),160)):i.set("text","")}}.bind(this)),a&&a.addEvent("input",function(){r&&r.value||i.set("text",this.shorten(this.html2string(a.value),160))}.bind(this)),setTimeout(function(){var t=this.getTinymce();t&&t.on("keyup",function(){r&&r.value||i.set("text",this.shorten(this.html2string(window.tinyMCE.activeEditor.getContent()),160))}.bind(this))}.bind(this),4)}}); \ No newline at end of file diff --git a/news-bundle/src/Resources/contao/dca/tl_news.php b/news-bundle/src/Resources/contao/dca/tl_news.php index 8964a28bb52..b620f079ac3 100644 --- a/news-bundle/src/Resources/contao/dca/tl_news.php +++ b/news-bundle/src/Resources/contao/dca/tl_news.php @@ -239,7 +239,7 @@ 'label' => &$GLOBALS['TL_LANG']['MSC']['serpPreview'], 'exclude' => true, 'inputType' => 'serpPreview', - 'eval' => array('url_callback'=>array('tl_news', 'getSerpUrl'), 'titleFields'=>array('pageTitle', 'headline'), 'descriptionFields'=>array('description', 'teaser')), + 'eval' => array('url_callback'=>array('tl_news', 'getSerpUrl'), 'title_tag_callback'=>array('tl_news', 'getTitleTag'), 'titleFields'=>array('pageTitle', 'headline'), 'descriptionFields'=>array('description', 'teaser')), 'sql' => null ), 'subheadline' => array @@ -691,6 +691,43 @@ public function getSerpUrl(Contao\NewsModel $model) return Contao\News::generateNewsUrl($model, false, true); } + /** + * Return the title tag from the associated page layout + * + * @param Contao\NewsModel $model + * + * @return string + */ + public function getTitleTag(Contao\NewsModel $model) + { + /** @var Contao\NewsArchiveModel $archive */ + if (!$archive = $model->getRelated('pid')) + { + return ''; + } + + /** @var Contao\PageModel $page */ + if (!$page = $archive->getRelated('jumpTo')) + { + return ''; + } + + $page->loadDetails(); + + /** @var Contao\LayoutModel $layout */ + if (!$layout = $page->getRelated('layout')) + { + return ''; + } + + global $objPage; + + // Set the global page object so we can replace the insert tags + $objPage = $page; + + return self::replaceInsertTags(str_replace('{{page::pageTitle}}', '%s', $layout->titleTag ?: '{{page::pageTitle}} - {{page::rootPageTitle}}')); + } + /** * List a news article * From c219abba6768a6110d402dad4761f67fc9b7acc0 Mon Sep 17 00:00:00 2001 From: Fritz Michael Gschwantner Date: Fri, 2 Oct 2020 16:13:51 +0200 Subject: [PATCH 41/57] Ignore logout URL if no user is present (see #2388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description ----------- If you access the URL of a logout page while not being logged in, the following error will occur: ``` InvalidArgumentException: Unable to generate a logout url for an anonymous token. at vendor/symfony/security-http/Logout/LogoutUrlGenerator.php:144 at Symfony\Component\Security\Http\Logout\LogoutUrlGenerator->getListener(null) (vendor/symfony/security-http/Logout/LogoutUrlGenerator.php:95) at Symfony\Component\Security\Http\Logout\LogoutUrlGenerator->generateLogoutUrl(null, 0) (vendor/symfony/security-http/Logout/LogoutUrlGenerator.php:76) at Symfony\Component\Security\Http\Logout\LogoutUrlGenerator->getLogoutUrl() (vendor/contao/contao/core-bundle/src/Resources/contao/pages/PageLogout.php:57) at Contao\PageLogout->getResponse(object(PageModel), true) (vendor/contao/contao/core-bundle/src/Resources/contao/controllers/FrontendIndex.php:339) at Contao\FrontendIndex->renderPage(object(PageModel)) (vendor/symfony/http-kernel/HttpKernel.php:158) at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1) (vendor/symfony/http-kernel/HttpKernel.php:80) at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true) (vendor/symfony/http-kernel/Kernel.php:201) at Symfony\Component\HttpKernel\Kernel->handle(object(Request)) (web/index.php:31) at require('web/index.php') (web/app.php:4) ``` This can happen if the front end user happens to click twice on the logout link for example, causing two requests and the second click happens before the response of the first request arrives - thus the user will already be logged out for the second request. This PR checks whether a user is actually logged in and if not simply redirects to the redirect target directly, without redirecting to the logout URL first. One might say: "but Fritz, you obviously should protect the logout page 🙃 ". Well, I don't necessarily agree with that. In my case, the logout page isn't actually available anywhere in a regular navigation module, but rather is only used in either a custom navigation or a direct link, _within protected pages_. Thus there was no actual need to protect the logout page itself. Also, if you have defined a `401` page in your site structure (which you should) the 401 page would simply be shown in that case (or its redirect will be executed) - which typically means a login page will be shown. I would not expect the log**out** URL to show me a log**in**. I would expect the logout URL to always redirect to the defined redirect page, no matter what. Commits ------- d30d59bd ignore logout URL if no user is present 87cec53f add issue number cb860633 remove comment 8141286d Update core-bundle/src/Resources/contao/pages/PageLogout.php Co-authored-by: Leo Feyer 33a5724f use token check instead e8f35cd1 code style 507e5e32 Move the comment to the correct position --- .../src/Resources/contao/pages/PageLogout.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core-bundle/src/Resources/contao/pages/PageLogout.php b/core-bundle/src/Resources/contao/pages/PageLogout.php index a8508edca42..78ba8b0c47c 100644 --- a/core-bundle/src/Resources/contao/pages/PageLogout.php +++ b/core-bundle/src/Resources/contao/pages/PageLogout.php @@ -14,6 +14,7 @@ use League\Uri\Http; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; /** * Provide methods to handle a logout page. @@ -31,7 +32,6 @@ class PageLogout extends Frontend */ public function getResponse($objPage) { - $strLogoutUrl = System::getContainer()->get('security.logout_url_generator')->getLogoutUrl(); $strRedirect = Environment::get('base'); // Redirect to last page visited @@ -47,6 +47,16 @@ public function getResponse($objPage) $strRedirect = $objTarget->getAbsoluteUrl(); } + $container = System::getContainer(); + $token = $container->get('security.helper')->getToken(); + + // Redirect immediately if there is no logged in user (see #2388) + if ($token === null || $token instanceof AnonymousToken) + { + return new RedirectResponse($strRedirect); + } + + $strLogoutUrl = $container->get('security.logout_url_generator')->getLogoutUrl(); $uri = Http::createFromString($strLogoutUrl); // Add the redirect= parameter to the logout URL From 1c220e418bda7eaa0f2baf8e15ca55b7054d6049 Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Tue, 6 Oct 2020 09:19:28 +0200 Subject: [PATCH 42/57] Update contao/easy-coding-standard to version 3 (see #2400) Description ----------- - Commits ------- 72fba4f3 Update contao/easy-coding-standard to version 3 ed9cfea9 Also adjust the cy.yml file --- .github/workflows/ci.yml | 6 +++--- composer.json | 10 +++++----- core-bundle/tests/Contao/ImageTest.php | 8 ++++---- .../EventListener/InsecureInstallationListenerTest.php | 4 ++-- .../tests/EventListener/UserSessionListenerTest.php | 4 ++-- core-bundle/tests/Framework/ContaoFrameworkTest.php | 4 ++-- core-bundle/tests/Image/ImageSizesTest.php | 2 +- core-bundle/tests/Security/User/UserCheckerTest.php | 2 +- monorepo.yml | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebc185e0a79..fddc21f8368 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,9 +60,9 @@ jobs: - name: Check the coding style run: | - vendor/bin/ecs check *-bundle/bin *-bundle/src *-bundle/tests --config vendor/contao/easy-coding-standard/config/default.yaml --no-progress-bar --ansi - vendor/bin/ecs check *-bundle/src/Resources/contao --config vendor/contao/easy-coding-standard/config/legacy.yaml --no-progress-bar --ansi - vendor/bin/ecs check *-bundle/src/Resources/contao/templates --config vendor/contao/easy-coding-standard/config/template.yaml --no-progress-bar --ansi + vendor/bin/ecs check *-bundle/bin *-bundle/src *-bundle/tests --config vendor/contao/easy-coding-standard/config/default.php --no-progress-bar --ansi + vendor/bin/ecs check *-bundle/src/Resources/contao --config vendor/contao/easy-coding-standard/config/legacy.php --no-progress-bar --ansi + vendor/bin/ecs check *-bundle/src/Resources/contao/templates --config vendor/contao/easy-coding-standard/config/template.php --no-progress-bar --ansi - name: Analyze the code run: | diff --git a/composer.json b/composer.json index 4326e330b4f..904b2f5dd00 100644 --- a/composer.json +++ b/composer.json @@ -147,14 +147,14 @@ "require-dev": { "ext-fileinfo": "*", "composer/composer": "^1.0", - "contao/easy-coding-standard": "^2.0", + "contao/easy-coding-standard": "^3.0", "contao/monorepo-tools": "dev-master", "contao/test-case": "^4.2", "doctrine/event-manager": "^1.0", "monolog/monolog": "^1.24", - "phpunit/phpunit": "^8.4", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-symfony": "^0.12", + "phpunit/phpunit": "^8.4", "psalm/plugin-phpunit": "^0.10", "psalm/plugin-symfony": "^1.0", "psr/event-dispatcher": "^1.0", @@ -262,9 +262,9 @@ "@monorepo-tools" ], "cs-fixer": [ - "vendor/bin/ecs check *-bundle/bin *-bundle/src *-bundle/tests --config vendor/contao/easy-coding-standard/config/default.yaml --fix --ansi", - "vendor/bin/ecs check *-bundle/src/Resources/contao --config vendor/contao/easy-coding-standard/config/legacy.yaml --fix --ansi", - "vendor/bin/ecs check *-bundle/src/Resources/contao/templates --config vendor/contao/easy-coding-standard/config/template.yaml --fix --ansi" + "vendor/bin/ecs check *-bundle/bin *-bundle/src *-bundle/tests --config vendor/contao/easy-coding-standard/config/default.php --fix --ansi", + "vendor/bin/ecs check *-bundle/src/Resources/contao --config vendor/contao/easy-coding-standard/config/legacy.php --fix --ansi", + "vendor/bin/ecs check *-bundle/src/Resources/contao/templates --config vendor/contao/easy-coding-standard/config/template.php --fix --ansi" ], "functional-tests": [ "vendor/bin/phpunit --testsuite=functional --colors=always" diff --git a/core-bundle/tests/Contao/ImageTest.php b/core-bundle/tests/Contao/ImageTest.php index 52c7e588fd5..e8f89bb8f2f 100644 --- a/core-bundle/tests/Contao/ImageTest.php +++ b/core-bundle/tests/Contao/ImageTest.php @@ -836,7 +836,7 @@ public function testSupportsReadingAndWritingValues(): void $imageObj->setTargetHeight(20); $this->assertSame($imageObj->getTargetHeight(), 20); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ $imageObj->setTargetHeight(50.125); $this->assertSame($imageObj->getTargetHeight(), 50); @@ -844,7 +844,7 @@ public function testSupportsReadingAndWritingValues(): void $imageObj->setTargetWidth(20); $this->assertSame($imageObj->getTargetWidth(), 20); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ $imageObj->setTargetWidth(50.125); $this->assertSame($imageObj->getTargetWidth(), 50); @@ -1007,10 +1007,10 @@ public function testDoesNotFactorImagesInTheLegacyMethodIfTheArgumentIsInvalid() { $this->assertNull(Image::get('', 100, 100)); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ $this->assertNull(Image::get(0, 100, 100)); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ $this->assertNull(Image::get(null, 100, 100)); } diff --git a/core-bundle/tests/EventListener/InsecureInstallationListenerTest.php b/core-bundle/tests/EventListener/InsecureInstallationListenerTest.php index 917ecde1316..a69f68fde52 100644 --- a/core-bundle/tests/EventListener/InsecureInstallationListenerTest.php +++ b/core-bundle/tests/EventListener/InsecureInstallationListenerTest.php @@ -40,7 +40,7 @@ public function testDoesNotThrowAnExceptionIfTheDocumentRootIsSecure(): void $listener = new InsecureInstallationListener(); $listener($this->getResponseEvent($request)); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function testDoesNotThrowAnExceptionOnLocalhost(): void @@ -51,7 +51,7 @@ public function testDoesNotThrowAnExceptionOnLocalhost(): void $listener = new InsecureInstallationListener(); $listener($this->getResponseEvent($request)); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } private function getRequest(): Request diff --git a/core-bundle/tests/EventListener/UserSessionListenerTest.php b/core-bundle/tests/EventListener/UserSessionListenerTest.php index 0a3af2213f1..058759bf9f1 100644 --- a/core-bundle/tests/EventListener/UserSessionListenerTest.php +++ b/core-bundle/tests/EventListener/UserSessionListenerTest.php @@ -264,7 +264,7 @@ public function testDoesNotReplaceTheSessionIfTheUserIsNotAContaoUser(): void $listener($this->getRequestEvent($request)); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function testDoesNotStoreTheSessionIfTheUserIsNotAContaoUser(): void @@ -284,7 +284,7 @@ public function testDoesNotStoreTheSessionIfTheUserIsNotAContaoUser(): void $listener->write($this->getResponseEvent($request)); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function testFailsToReplaceTheSessionIfThereIsNoSession(): void diff --git a/core-bundle/tests/Framework/ContaoFrameworkTest.php b/core-bundle/tests/Framework/ContaoFrameworkTest.php index b85aea79634..ed4fa393483 100644 --- a/core-bundle/tests/Framework/ContaoFrameworkTest.php +++ b/core-bundle/tests/Framework/ContaoFrameworkTest.php @@ -338,7 +338,7 @@ public function testDoesNotInitializeTheFrameworkTwice(): void $framework->initialize(); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function testOverridesTheErrorLevel(): void @@ -440,7 +440,7 @@ public function testAllowsTheInstallationToBeIncompleteInTheInstallTool(string $ $framework->initialize(); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function getInstallRoutes(): \Generator diff --git a/core-bundle/tests/Image/ImageSizesTest.php b/core-bundle/tests/Image/ImageSizesTest.php index 5fef6d2ff73..5035b88212a 100644 --- a/core-bundle/tests/Image/ImageSizesTest.php +++ b/core-bundle/tests/Image/ImageSizesTest.php @@ -50,7 +50,7 @@ protected function setUp(): void ], 'exact' => [ 'crop', - 'left_top', 'center_top', 'right_top', + 'left_top', 'center_top', 'right_top', 'left_center', 'center_center', 'right_center', 'left_bottom', 'center_bottom', 'right_bottom', ], diff --git a/core-bundle/tests/Security/User/UserCheckerTest.php b/core-bundle/tests/Security/User/UserCheckerTest.php index fc19d2f0741..4ff3418b7a6 100644 --- a/core-bundle/tests/Security/User/UserCheckerTest.php +++ b/core-bundle/tests/Security/User/UserCheckerTest.php @@ -41,7 +41,7 @@ public function testChecksAContaoUser(): void $userChecker->checkPreAuth($user); $userChecker->checkPostAuth($user); - $this->addToAssertionCount(1); // does not throw an exception + $this->addToAssertionCount(1); // does not throw an exception } public function testDoesNothingIfTheUserIsNotAContaoUser(): void diff --git a/monorepo.yml b/monorepo.yml index 6aa39e75b79..70d4ecddaa7 100644 --- a/monorepo.yml +++ b/monorepo.yml @@ -9,7 +9,7 @@ composer: nikic/php-parser: 4.7.0 zendframework/zend-code: <3.3.1 require-dev: - contao/easy-coding-standard: ^2.0 + contao/easy-coding-standard: ^3.0 contao/monorepo-tools: dev-master phpstan/phpstan-phpunit: ^0.12 phpstan/phpstan-symfony: ^0.12 From 0e10f0b40d99f645c8afef4392ed8a6b2e4d8b3c Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 6 Oct 2020 11:53:26 +0200 Subject: [PATCH 43/57] Reset the KEY_BLOCK_SIZE when migrating mysql engine and row format (see #2363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description ----------- I have a Contao 4.6 setup on a server that did not support InnoDB. Therefore, I configured the DBAL like so which worked for about 2 years now. ```yaml doctrine: dbal: connections: default: charset: utf8 default_table_options: charset: utf8 collate: utf8_unicode_ci engine: MyISAM ``` I could finally convince the client to move to a good hoster and also do an update to Contao 4.9. First step was to move the existing installation to the new hoster, on Contao 4.6 and with this DBAL configuration. Then I updated to Contao 4.9 and did run `contao:migrate`, which fails at migrating the `tl_files` table: >ALTER TABLE tl_files ENGINE = InnoDB ROW_FORMAT = DYNAMIC......FAILED >[ERROR] An exception occurred while executing 'ALTER TABLE tl_files ENGINE = InnoDB ROW_FORMAT = DYNAMIC': SQLSTATE[HY000]: General error: 1031 Table storage engine for '#sql-10b9_2e20d0a' doesn't have this option After a suggestion from @ausi I tried to set `ROW_FORMAT=DEFAULT` before changing the engine. Changing the row format worked, but I got a new error after that (this time in german due to phpMyAdmin). > error 1478 - Speicher-Engine 'InnoDB' der Tabelle unterstützt die Option 'KEY_BLOCK_SIZE' nicht I did some digging on the internet and found the following to MySQL issues: - https://bugs.mysql.com/bug.php?id=88843 - https://bugs.mysql.com/bug.php?id=56628 Apparently, my database table has a `KEY_BLOCK_SIZE` defined, but that's not supported by an DYNAMIC (uncompressed) row format. According to the second bug report, setting `KEY_BLOCK_SIZE=0` is the correct way to remove that setting since MySQL 5.5.9 Commits ------- 897ffc98 Reset the KEY_BLOCK_SIZE when migrating mysql engine and row format ed8da435 Fix unit tests f5942e96 Fix unit tests 928743da Check table create options before resetting KEY_BLOCK_SIZE 0c3c3a7d Update unit tests to handle from schema 7d8198a4 Do not generate test coverage for installation-bundle 2cd5ec1a CS 6ee54425 Remove the comma Co-authored-by: Martin Auswöger --- .../src/Database/Installer.php | 9 + .../tests/Database/InstallerTest.php | 285 ++++++++++++------ 2 files changed, 209 insertions(+), 85 deletions(-) diff --git a/installation-bundle/src/Database/Installer.php b/installation-bundle/src/Database/Installer.php index 89492957409..939e61b215b 100644 --- a/installation-bundle/src/Database/Installer.php +++ b/installation-bundle/src/Database/Installer.php @@ -255,6 +255,10 @@ private function checkEngineAndCollation(array &$sql, array &$order, Schema $fro if (strtolower($tableOptions->Engine) !== strtolower($engine)) { if ($innodb && $dynamic) { $command = 'ALTER TABLE '.$tableName.' ENGINE = '.$engine.' ROW_FORMAT = DYNAMIC'; + + if (false !== stripos($tableOptions->Create_options, 'key_block_size=')) { + $command .= ' KEY_BLOCK_SIZE = 0'; + } } else { $command = 'ALTER TABLE '.$tableName.' ENGINE = '.$engine; } @@ -264,6 +268,11 @@ private function checkEngineAndCollation(array &$sql, array &$order, Schema $fro } elseif ($innodb && $dynamic) { if (false === stripos($tableOptions->Create_options, 'row_format=dynamic')) { $command = 'ALTER TABLE '.$tableName.' ENGINE = '.$engine.' ROW_FORMAT = DYNAMIC'; + + if (false !== stripos($tableOptions->Create_options, 'key_block_size=')) { + $command .= ' KEY_BLOCK_SIZE = 0'; + } + $alterTables[md5($command)] = $command; } } diff --git a/installation-bundle/tests/Database/InstallerTest.php b/installation-bundle/tests/Database/InstallerTest.php index 160321141d6..915a272aba7 100644 --- a/installation-bundle/tests/Database/InstallerTest.php +++ b/installation-bundle/tests/Database/InstallerTest.php @@ -30,6 +30,9 @@ public function testReturnsTheAlterTableCommands(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ->addColumn('foo', 'string') ; @@ -46,26 +49,96 @@ public function testReturnsTheAlterTableCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_TABLE', $commands); - $this->assertArrayHasKey('d21451588bc7442c256f8a0be02c3430', $commands['ALTER_TABLE']); - $this->assertArrayHasKey('fb9f8dee53c39b7be92194908d98731e', $commands['ALTER_TABLE']); - $this->assertSame( - 'ALTER TABLE tl_foo ENGINE = InnoDB ROW_FORMAT = DYNAMIC', - $commands['ALTER_TABLE']['d21451588bc7442c256f8a0be02c3430'] + $this->assertHasStatement( + $commands['ALTER_TABLE'], + 'ALTER TABLE tl_foo ENGINE = InnoDB ROW_FORMAT = DYNAMIC' ); - - $this->assertSame( - 'ALTER TABLE tl_foo CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci', - $commands['ALTER_TABLE']['fb9f8dee53c39b7be92194908d98731e'] + $this->assertHasStatement( + $commands['ALTER_TABLE'], + 'ALTER TABLE tl_foo CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' ); } + public function testChangesTheDatabaseEngine(): void + { + $fromSchema = new Schema(); + $fromSchema + ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') + ; + + $fromSchema->getTable('tl_foo')->addColumn('foo', 'string'); + $fromSchema->getTable('tl_foo')->addIndex(['foo'], 'foo_idx'); + + $toSchema = new Schema(); + $toSchema + ->createTable('tl_foo') + ->addOption('engine', 'InnoDB') + ; + + $toSchema + ->getTable('tl_foo') + ->addColumn('foo', 'string') + ; + + $toSchema + ->getTable('tl_foo') + ->addIndex(['foo'], 'foo_idx') + ; + + $installer = $this->getInstaller($fromSchema, $toSchema, ['tl_foo']); + $commands = $installer->getCommands(); + + $this->assertHasStatement($commands['ALTER_TABLE'], 'ALTER TABLE tl_foo ENGINE = InnoDB ROW_FORMAT = DYNAMIC'); + } + + public function testResetsTheKeyBlockSizeWhenChangingTheDatabaseEngine(): void + { + $fromSchema = new Schema(); + $fromSchema + ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('create_options', ['KEY_BLOCK_SIZE=16']) + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') + ; + + $fromSchema->getTable('tl_foo')->addColumn('foo', 'string'); + $fromSchema->getTable('tl_foo')->addIndex(['foo'], 'foo_idx'); + + $toSchema = new Schema(); + $toSchema + ->createTable('tl_foo') + ->addOption('engine', 'InnoDB') + ; + + $toSchema + ->getTable('tl_foo') + ->addColumn('foo', 'string') + ; + + $toSchema + ->getTable('tl_foo') + ->addIndex(['foo'], 'foo_idx') + ; + + $installer = $this->getInstaller($fromSchema, $toSchema, ['tl_foo']); + $commands = $installer->getCommands(); + + $this->assertHasStatement($commands['ALTER_TABLE'], 'ALTER TABLE tl_foo ENGINE = InnoDB ROW_FORMAT = DYNAMIC KEY_BLOCK_SIZE = 0'); + } + public function testDeletesTheIndexesWhenChangingTheDatabaseEngine(): void { $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ; $fromSchema @@ -97,10 +170,7 @@ public function testDeletesTheIndexesWhenChangingTheDatabaseEngine(): void $installer = $this->getInstaller($fromSchema, $toSchema, ['tl_foo']); $commands = $installer->getCommands(); - $this->assertSame( - 'DROP INDEX foo_idx ON tl_foo', - $commands['ALTER_TABLE']['db24ce0a48761ea6f77d644a422a3fe0'] - ); + $this->assertHasStatement($commands['ALTER_TABLE'], 'DROP INDEX foo_idx ON tl_foo'); } public function testDeletesTheIndexesWhenChangingTheCollation(): void @@ -108,6 +178,8 @@ public function testDeletesTheIndexesWhenChangingTheCollation(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') ->addOption('collate', 'utf8_unicode_ci') ; @@ -140,10 +212,7 @@ public function testDeletesTheIndexesWhenChangingTheCollation(): void $installer = $this->getInstaller($fromSchema, $toSchema, ['tl_foo']); $commands = $installer->getCommands(); - $this->assertSame( - 'DROP INDEX foo_idx ON tl_foo', - $commands['ALTER_TABLE']['db24ce0a48761ea6f77d644a422a3fe0'] - ); + $this->assertHasStatement($commands['ALTER_TABLE'], 'DROP INDEX foo_idx ON tl_foo'); } public function testChangesTheRowFormatIfInnodbIsUsed(): void @@ -151,6 +220,10 @@ public function testChangesTheRowFormatIfInnodbIsUsed(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_bar') + ->addOption('engine', 'InnoDB') + ->addOption('charset', 'utf8mb4') + ->addOption('collate', 'utf8mb4_unicode_ci') + ->addOption('Create_options', 'row_format=COMPACT') ->addColumn('foo', 'string') ; @@ -168,11 +241,43 @@ public function testChangesTheRowFormatIfInnodbIsUsed(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_TABLE', $commands); - $this->assertArrayHasKey('754c11ae50c43c54456fcd31da3baccb', $commands['ALTER_TABLE']); - $this->assertSame( - 'ALTER TABLE tl_bar ENGINE = InnoDB ROW_FORMAT = DYNAMIC', - $commands['ALTER_TABLE']['754c11ae50c43c54456fcd31da3baccb'] + $this->assertHasStatement( + $commands['ALTER_TABLE'], + 'ALTER TABLE tl_bar ENGINE = InnoDB ROW_FORMAT = DYNAMIC' + ); + } + + public function testResetsTheKeyBlockSizeIfInnodbIsUsed(): void + { + $fromSchema = new Schema(); + $fromSchema + ->createTable('tl_bar') + ->addOption('engine', 'InnoDB') + ->addOption('charset', 'utf8mb4') + ->addOption('collate', 'utf8mb4_unicode_ci') + ->addOption('create_options', ['row_format=COMPACT', 'KEY_BLOCK_SIZE=16']) + ->addColumn('foo', 'string') + ; + + $toSchema = new Schema(); + $toSchema + ->createTable('tl_bar') + ->addOption('engine', 'InnoDB') + ->addOption('row_format', 'DYNAMIC') + ->addOption('charset', 'utf8mb4') + ->addOption('collate', 'utf8mb4_unicode_ci') + ->addColumn('foo', 'string') + ; + + $installer = $this->getInstaller($fromSchema, $toSchema, ['tl_foo']); + $commands = $installer->getCommands(); + + $this->assertArrayHasKey('ALTER_TABLE', $commands); + + $this->assertHasStatement( + $commands['ALTER_TABLE'], + 'ALTER TABLE tl_bar ENGINE = InnoDB ROW_FORMAT = DYNAMIC KEY_BLOCK_SIZE = 0' ); } @@ -181,6 +286,9 @@ public function testDoesNotChangeTheRowFormatIfDynamicRowsAreNotSupported(): voi $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ->addColumn('foo', 'string') ; @@ -200,10 +308,7 @@ public function testDoesNotChangeTheRowFormatIfDynamicRowsAreNotSupported(): voi $this->assertArrayHasKey('ALTER_TABLE', $commands); $this->assertArrayHasKey('537747ae8a3a53e6277dfccf354bc7da', $commands['ALTER_TABLE']); - $this->assertSame( - 'ALTER TABLE tl_foo ENGINE = InnoDB', - $commands['ALTER_TABLE']['537747ae8a3a53e6277dfccf354bc7da'] - ); + $this->assertHasStatement($commands['ALTER_TABLE'], 'ALTER TABLE tl_foo ENGINE = InnoDB'); } public function testDoesNotChangeTheRowFormatIfTableOptionsAreNotAvailable(): void @@ -234,6 +339,9 @@ public function testReturnsTheDropColumnCommands(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ->addColumn('foo', 'string') ; @@ -252,7 +360,7 @@ public function testReturnsTheDropColumnCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_DROP', $commands); - $this->assertSame('ALTER TABLE tl_foo DROP bar', reset($commands['ALTER_DROP'])); + $this->assertHasStatement($commands['ALTER_DROP'], 'ALTER TABLE tl_foo DROP bar'); } public function testReturnsTheAddColumnCommands(): void @@ -260,6 +368,9 @@ public function testReturnsTheAddColumnCommands(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ->addColumn('foo', 'string') ; @@ -278,16 +389,21 @@ public function testReturnsTheAddColumnCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_ADD', $commands); - - $commands = array_values($commands['ALTER_ADD']); - - $this->assertSame('ALTER TABLE tl_foo ADD bar VARCHAR(255) NOT NULL', $commands[0]); + $this->assertHasStatement( + $commands['ALTER_ADD'], + 'ALTER TABLE tl_foo ADD bar VARCHAR(255) NOT NULL' + ); } public function testHandlesDecimalsInTheAddColumnCommands(): void { $fromSchema = new Schema(); - $fromSchema->createTable('tl_foo'); + $fromSchema + ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') + ; $toSchema = new Schema(); $toSchema @@ -299,16 +415,21 @@ public function testHandlesDecimalsInTheAddColumnCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_ADD', $commands); - - $commands = array_values($commands['ALTER_ADD']); - - $this->assertSame('ALTER TABLE tl_foo ADD foo NUMERIC(9,2) NOT NULL', $commands[0]); + $this->assertHasStatement( + $commands['ALTER_ADD'], + 'ALTER TABLE tl_foo ADD foo NUMERIC(9,2) NOT NULL' + ); } public function testHandlesDefaultsInTheAddColumnCommands(): void { $fromSchema = new Schema(); - $fromSchema->createTable('tl_foo'); + $fromSchema + ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') + ; $toSchema = new Schema(); $toSchema @@ -320,16 +441,21 @@ public function testHandlesDefaultsInTheAddColumnCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_ADD', $commands); - - $commands = array_values($commands['ALTER_ADD']); - - $this->assertSame("ALTER TABLE tl_foo ADD foo VARCHAR(255) DEFAULT ',' NOT NULL", $commands[0]); + $this->assertHasStatement( + $commands['ALTER_ADD'], + "ALTER TABLE tl_foo ADD foo VARCHAR(255) DEFAULT ',' NOT NULL" + ); } public function testHandlesMixedColumnsInTheAddColumnCommands(): void { $fromSchema = new Schema(); - $fromSchema->createTable('tl_foo'); + $fromSchema + ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') + ; $toSchema = new Schema(); $toSchema @@ -356,14 +482,10 @@ public function testHandlesMixedColumnsInTheAddColumnCommands(): void $commands = $installer->getCommands(); $this->assertArrayHasKey('ALTER_ADD', $commands); - - $commands = array_values($commands['ALTER_ADD']); - - $this->assertCount(4, $commands); - $this->assertContains('ALTER TABLE tl_foo ADD foo1 VARCHAR(255) NOT NULL', $commands); - $this->assertContains('ALTER TABLE tl_foo ADD foo2 INT NOT NULL', $commands); - $this->assertContains('ALTER TABLE tl_foo ADD foo3 NUMERIC(9,2) NOT NULL', $commands); - $this->assertContains("ALTER TABLE tl_foo ADD foo4 VARCHAR(255) DEFAULT ',' NOT NULL", $commands); + $this->assertHasStatement($commands['ALTER_ADD'], 'ALTER TABLE tl_foo ADD foo1 VARCHAR(255) NOT NULL'); + $this->assertHasStatement($commands['ALTER_ADD'], 'ALTER TABLE tl_foo ADD foo2 INT NOT NULL'); + $this->assertHasStatement($commands['ALTER_ADD'], 'ALTER TABLE tl_foo ADD foo3 NUMERIC(9,2) NOT NULL'); + $this->assertHasStatement($commands['ALTER_ADD'], "ALTER TABLE tl_foo ADD foo4 VARCHAR(255) DEFAULT ',' NOT NULL"); } public function testReturnsNoCommandsIfTheSchemasAreIdentical(): void @@ -371,6 +493,9 @@ public function testReturnsNoCommandsIfTheSchemasAreIdentical(): void $fromSchema = new Schema(); $fromSchema ->createTable('tl_foo') + ->addOption('engine', 'MyISAM') + ->addOption('charset', 'utf8') + ->addOption('collate', 'utf8_unicode_ci') ->addColumn('foo', 'string') ; @@ -389,6 +514,13 @@ public function testReturnsNoCommandsIfTheSchemasAreIdentical(): void $this->assertEmpty($commands); } + private function assertHasStatement(array $commands, string $expected): void + { + $key = md5($expected); + $this->assertArrayHasKey($key, $commands, 'Expected key '.$key.' for statement "'.$expected.'"'); + $this->assertSame($expected, $commands[$key]); + } + /** * Mocks an installer. * @@ -450,48 +582,31 @@ function (string $query) use ($filePerTable): ?MockObject { $connection ->method('executeQuery') ->willReturnCallback( - function (string $query, array $parameters): ?MockObject { + function (string $query, array $parameters) use ($fromSchema): ?MockObject { if ('SHOW TABLE STATUS WHERE Name = ? AND Engine IS NOT NULL AND Create_options IS NOT NULL AND Collation IS NOT NULL' !== $query) { return null; } - switch ($parameters[0]) { - case 'tl_foo': - $statement = $this->createMock(Statement::class); - $statement - ->method('fetch') - ->willReturn((object) [ - 'Engine' => 'MyISAM', - 'Collation' => 'utf8_unicode_ci', - ]) - ; - - return $statement; - - case 'tl_bar': - $statement = $this->createMock(Statement::class); - $statement - ->method('fetch') - ->willReturn((object) [ - 'Engine' => 'InnoDB', - 'Create_options' => 'row_format=COMPACT', - 'Collation' => 'utf8mb4_unicode_ci', - ]) - ; - - return $statement; - - case 'tl_foo_view': - $statement = $this->createMock(Statement::class); - $statement - ->method('fetch') - ->willReturn(false) - ; - - return $statement; + $table = $fromSchema->getTable($parameters[0]); + $statement = $this->createMock(Statement::class); + + if ($table->hasOption('engine')) { + $statement + ->method('fetch') + ->willReturn((object) [ + 'Engine' => $table->getOption('engine'), + 'Create_options' => implode(', ', $table->getOption('create_options')), + 'Collation' => $table->hasOption('collate') ? $table->getOption('collate') : '', + ]) + ; + } else { + $statement + ->method('fetch') + ->willReturn(false) + ; } - return null; + return $statement; } ) ; From f7c15c07e57e6d107e599f2d8f57f2fce9108bcb Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 6 Oct 2020 11:54:06 +0200 Subject: [PATCH 44/57] Remove existing tags from annotations that could be arrays (see #2372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description ----------- Fixes https://github.com/contao/contao/issues/2371 Let's see what https://github.com/symfony/symfony/issues/38339 bringts up … Commits ------- 7a35efc2 Remove existing tags from annotations that could be arrays de60bb6a Page type is optional, it's automatically generated in the compiler pass --- core-bundle/src/ContaoCoreBundle.php | 2 +- .../DependencyInjection/Compiler/RegisterFragmentsPass.php | 1 - .../src/DependencyInjection/Compiler/RegisterPagesPass.php | 2 -- core-bundle/src/ServiceAnnotation/Page.php | 4 ---- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core-bundle/src/ContaoCoreBundle.php b/core-bundle/src/ContaoCoreBundle.php index 7d982df5d1c..dbf517ded81 100644 --- a/core-bundle/src/ContaoCoreBundle.php +++ b/core-bundle/src/ContaoCoreBundle.php @@ -85,7 +85,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new AddResourcesPathsPass()); $container->addCompilerPass(new TaggedMigrationsPass()); $container->addCompilerPass(new PickerProviderPass()); - $container->addCompilerPass(new RegisterPagesPass()); + $container->addCompilerPass(new RegisterPagesPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1); $container->addCompilerPass(new RegisterFragmentsPass(FrontendModuleReference::TAG_NAME)); $container->addCompilerPass(new RegisterFragmentsPass(ContentElementReference::TAG_NAME)); $container->addCompilerPass(new FragmentRendererPass('contao.fragment.handler')); diff --git a/core-bundle/src/DependencyInjection/Compiler/RegisterFragmentsPass.php b/core-bundle/src/DependencyInjection/Compiler/RegisterFragmentsPass.php index ff95b558144..58a2ba1b749 100644 --- a/core-bundle/src/DependencyInjection/Compiler/RegisterFragmentsPass.php +++ b/core-bundle/src/DependencyInjection/Compiler/RegisterFragmentsPass.php @@ -100,7 +100,6 @@ protected function registerFragments(ContainerBuilder $container, string $tag): $command->addMethodCall('add', [$identifier, $config, $attributes]); } - $childDefinition->addTag($tag, $attributes); $container->setDefinition($serviceId, $childDefinition); } } diff --git a/core-bundle/src/DependencyInjection/Compiler/RegisterPagesPass.php b/core-bundle/src/DependencyInjection/Compiler/RegisterPagesPass.php index a6bca6187bd..15a49181780 100644 --- a/core-bundle/src/DependencyInjection/Compiler/RegisterPagesPass.php +++ b/core-bundle/src/DependencyInjection/Compiler/RegisterPagesPass.php @@ -72,8 +72,6 @@ protected function registerPages(ContainerBuilder $container): void $config = $this->getRouteConfig($reference, $definition, $attributes); $registry->addMethodCall('add', [$type, $config, $routeEnhancer, $contentComposition]); - - $definition->addTag(self::TAG_NAME, $attributes); } } } diff --git a/core-bundle/src/ServiceAnnotation/Page.php b/core-bundle/src/ServiceAnnotation/Page.php index bfb9abf2f48..190c7d59555 100644 --- a/core-bundle/src/ServiceAnnotation/Page.php +++ b/core-bundle/src/ServiceAnnotation/Page.php @@ -72,10 +72,6 @@ public function __construct(array $data) unset($data['value']); } - if (!isset($data['type'])) { - throw new \LogicException('@Page annotation requires a type property.'); - } - if (isset($data['locale'])) { $data['defaults']['_locale'] = $data['locale']; unset($data['locale']); From 4ff607acb85ebd222f3215c7385bf2797933b597 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 6 Oct 2020 12:02:40 +0200 Subject: [PATCH 45/57] Purge the search tables directly (see #2265) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #2162 It does not make sense to delete from the search indexer(s), because we query the `tl_search` table directly. This can only work with the default indexer anyway. Commits ------- 2f4bb814 Purge the search tables directly 97e747ce CS 4033df84 Merge branch '4.10' into bugfix/search-indexer --- .../DataContainer/PageUrlListener.php | 17 +-- core-bundle/src/Resources/config/listener.yml | 1 - .../DataContainer/PageUrlListenerTest.php | 113 ++++++------------ 3 files changed, 43 insertions(+), 88 deletions(-) diff --git a/core-bundle/src/EventListener/DataContainer/PageUrlListener.php b/core-bundle/src/EventListener/DataContainer/PageUrlListener.php index 14c35f9a1e2..b369674481d 100644 --- a/core-bundle/src/EventListener/DataContainer/PageUrlListener.php +++ b/core-bundle/src/EventListener/DataContainer/PageUrlListener.php @@ -14,16 +14,14 @@ use Contao\CoreBundle\Exception\DuplicateAliasException; use Contao\CoreBundle\Framework\ContaoFramework; -use Contao\CoreBundle\Search\Document; -use Contao\CoreBundle\Search\Indexer\IndexerInterface; use Contao\CoreBundle\ServiceAnnotation\Callback; use Contao\CoreBundle\Slug\Slug; use Contao\DataContainer; use Contao\Input; use Contao\PageModel; +use Contao\Search; use Doctrine\DBAL\Connection; use Doctrine\DBAL\FetchMode; -use Nyholm\Psr7\Uri; use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -49,11 +47,6 @@ class PageUrlListener implements ResetInterface */ private $connection; - /** - * @var IndexerInterface - */ - private $searchIndexer; - /** * @var array|null */ @@ -64,13 +57,12 @@ class PageUrlListener implements ResetInterface */ private $suffixes; - public function __construct(ContaoFramework $framework, Slug $slug, TranslatorInterface $translator, Connection $connection, IndexerInterface $searchIndexer) + public function __construct(ContaoFramework $framework, Slug $slug, TranslatorInterface $translator, Connection $connection) { $this->framework = $framework; $this->slug = $slug; $this->translator = $translator; $this->connection = $connection; - $this->searchIndexer = $searchIndexer; } /** @@ -222,8 +214,11 @@ public function purgeSearchIndex(int $pageId): void ->fetchAll(FetchMode::COLUMN) ; + /** @var Search $search */ + $search = $this->framework->getAdapter(Search::class); + foreach ($urls as $url) { - $this->searchIndexer->delete(new Document(new Uri($url), 200)); + $search->removeEntry($url); } } diff --git a/core-bundle/src/Resources/config/listener.yml b/core-bundle/src/Resources/config/listener.yml index fed6c50364a..af141186ae4 100644 --- a/core-bundle/src/Resources/config/listener.yml +++ b/core-bundle/src/Resources/config/listener.yml @@ -34,7 +34,6 @@ services: - '@contao.slug' - '@translator' - '@database_connection' - - '@contao.search.indexer' public: true Contao\CoreBundle\EventListener\FilterPageTypeListener: diff --git a/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php b/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php index e3975469d6a..5dbe2c4b5e0 100644 --- a/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php +++ b/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php @@ -14,13 +14,12 @@ use Contao\CoreBundle\EventListener\DataContainer\PageUrlListener; use Contao\CoreBundle\Framework\ContaoFramework; -use Contao\CoreBundle\Search\Document; -use Contao\CoreBundle\Search\Indexer\IndexerInterface; use Contao\CoreBundle\Slug\Slug; use Contao\CoreBundle\Tests\TestCase; use Contao\DataContainer; use Contao\Input; use Contao\PageModel; +use Contao\Search; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Statement; use Doctrine\DBAL\FetchMode; @@ -68,8 +67,7 @@ public function testGeneratesAlias(array $activeRecord, string $expectedAlias): $framework, $slug, $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); $this->assertSame($expectedAlias, $listener->generateAlias('', $dc)); @@ -159,8 +157,7 @@ function (callable $callback) use ($generated, $expectExists) { $framework, $slug, $this->createMock(TranslatorInterface::class), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); $listener->generateAlias('', $dc); @@ -203,8 +200,7 @@ public function testChecksForDuplicatesWhenValidatingAlias(array $activeRecord, $framework, $slug, $translator, - $connection, - $this->createMock(IndexerInterface::class) + $connection ); $listener->generateAlias($value, $dc); @@ -733,20 +729,11 @@ public function testPurgesTheSearchIndexOnAliasChange(): void ->willReturn($statement) ; - $searchIndexer = $this->createMock(IndexerInterface::class); - $searchIndexer + $search = $this->mockAdapter(['removeEntry']); + $search ->expects($this->once()) - ->method('delete') - ->with($this->callback( - function ($document) { - $this->assertInstanceOf(Document::class, $document); - - /** @var Document $document */ - $this->assertSame('uri', (string) $document->getUri()); - - return true; - } - )) + ->method('removeEntry') + ->with('uri') ; /** @var MockObject&DataContainer $dc */ @@ -761,11 +748,10 @@ function ($document) { ); $listener = new PageUrlListener( - $this->createMock(ContaoFramework::class), + $this->mockContaoFramework([Search::class => $search]), $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $searchIndexer + $connection ); $listener->purgeSearchIndexOnAliasChange('bar', $dc); @@ -779,8 +765,8 @@ public function testDoesNotPurgeTheSearchIndexWithUnchangedAlias(): void ->method($this->anything()) ; - $searchIndexer = $this->createMock(IndexerInterface::class); - $searchIndexer + $search = $this->mockAdapter(['removeEntry']); + $search ->expects($this->never()) ->method($this->anything()) ; @@ -797,11 +783,10 @@ public function testDoesNotPurgeTheSearchIndexWithUnchangedAlias(): void ); $listener = new PageUrlListener( - $this->createMock(ContaoFramework::class), + $this->mockContaoFramework([Search::class => $search]), $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); $listener->purgeSearchIndexOnAliasChange('foo', $dc); @@ -824,20 +809,11 @@ public function testPurgesTheSearchIndexOnDelete(): void ->willReturn($statement) ; - $searchIndexer = $this->createMock(IndexerInterface::class); - $searchIndexer + $search = $this->mockAdapter(['removeEntry']); + $search ->expects($this->once()) - ->method('delete') - ->with($this->callback( - function ($document) { - $this->assertInstanceOf(Document::class, $document); - - /** @var Document $document */ - $this->assertSame('uri', (string) $document->getUri()); - - return true; - } - )) + ->method('removeEntry') + ->with('uri') ; /** @var MockObject&DataContainer $dc */ @@ -849,11 +825,10 @@ function ($document) { ); $listener = new PageUrlListener( - $this->createMock(ContaoFramework::class), + $this->mockContaoFramework([Search::class => $search]), $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $searchIndexer + $connection ); $listener->purgeSearchIndexOnDelete($dc); @@ -867,8 +842,8 @@ public function testDoesNotPurgeTheSearchIndexWithoutId(): void ->method($this->anything()) ; - $searchIndexer = $this->createMock(IndexerInterface::class); - $searchIndexer + $search = $this->mockAdapter(['removeEntry']); + $search ->expects($this->never()) ->method($this->anything()) ; @@ -882,11 +857,10 @@ public function testDoesNotPurgeTheSearchIndexWithoutId(): void ); $listener = new PageUrlListener( - $this->createMock(ContaoFramework::class), + $this->mockContaoFramework([Search::class => $search]), $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $searchIndexer + $connection ); $listener->purgeSearchIndexOnDelete($dc); @@ -966,8 +940,7 @@ public function testResetsThePrefixesAndSuffixes(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var DataContainer&MockObject $dc1 */ @@ -1023,8 +996,7 @@ public function testReturnsValueWhenValidatingUrlPrefix(): void $framework, $this->createMock(Slug::class), $this->mockTranslator(), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1065,8 +1037,7 @@ public function testThrowsExceptionOnDuplicateUrlPrefixInDomain(): void $this->mockContaoFramework(), $this->createMock(Slug::class), $translator, - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1154,8 +1125,7 @@ public function testThrowsExceptionIfUrlPrefixLeadsToDuplicatePages(): void $framework, $this->createMock(Slug::class), $translator, - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1235,8 +1205,7 @@ public function testIgnoresPagesWithoutAliasWhenValidatingUrlPrefix(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1265,8 +1234,7 @@ public function testDoesNotValidateTheUrlPrefixIfPageTypeIsNotRoot(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ @@ -1298,8 +1266,7 @@ public function testDoesNotValidateTheUrlPrefixIfTheValueHasNotChanged(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ @@ -1333,8 +1300,7 @@ public function testDoesNotValidateTheUrlPrefixIfTheRootPageIsNotFound(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ @@ -1389,8 +1355,7 @@ public function testReturnsValueWhenValidatingUrlSuffix(): void $framework, $this->createMock(Slug::class), $this->mockTranslator(), - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1471,8 +1436,7 @@ public function testThrowsExceptionOnDuplicateUrlSuffix(): void $framework, $this->createMock(Slug::class), $translator, - $connection, - $this->createMock(IndexerInterface::class) + $connection ); /** @var MockObject&DataContainer $dc */ @@ -1501,8 +1465,7 @@ public function testDoesNotValidateTheUrlSuffixIfPageTypeIsNotRoot(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ @@ -1534,8 +1497,7 @@ public function testDoesNotValidateTheUrlSuffixIfTheValueHasNotChanged(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ @@ -1569,8 +1531,7 @@ public function testDoesNotValidateTheUrlSuffixIfTheRootPageIsNotFound(): void $framework, $this->createMock(Slug::class), $this->createMock(TranslatorInterface::class), - $this->mockConnectionWithStatement(), - $this->createMock(IndexerInterface::class) + $this->mockConnectionWithStatement() ); /** @var MockObject&DataContainer $dc */ From 9a6d51d2feff076785ac9ffd80f70d96074475ac Mon Sep 17 00:00:00 2001 From: "M. Vondano" Date: Tue, 6 Oct 2020 12:04:27 +0200 Subject: [PATCH 46/57] Fix asset url handling in image studio (see #2337) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #2330 | Docs PR or issue | - This PR addresses two issues that occur when using asset urls: 1. the `contao.assets.files_context` was used instead of `contao.assets.asset_context` 2. the `File#imageSize` calculation for the legacy templates wasn't always getting a relative path. /cc @ausi Commits ------- 31a75109 use assets context instead of file context for images d13891db use path instead of url to retrieve file info d46fdb66 test getting image src as path c108a274 use more realistic arguments for the tests 4b805be3 test stability / unrelated to changes (see #2316) 9e7afa6d Revert "use assets context instead of file context for images" This reverts commit 31a75109 319736a6 Drop unnecessary parantheses Co-authored-by: Leo Feyer 908d3881 Merge branch '4.10' into bugfix/image-studio-asset-url --- core-bundle/src/Image/Studio/Figure.php | 4 +- core-bundle/src/Image/Studio/ImageResult.php | 13 +++- .../Studio/FigureBuilderIntegrationTest.php | 1 + core-bundle/tests/Image/Studio/FigureTest.php | 11 +-- .../tests/Image/Studio/ImageResultTest.php | 72 ++++++++++++++----- 5 files changed, 76 insertions(+), 25 deletions(-) diff --git a/core-bundle/src/Image/Studio/Figure.php b/core-bundle/src/Image/Studio/Figure.php index 03005ac0432..2c54052c7a6 100644 --- a/core-bundle/src/Image/Studio/Figure.php +++ b/core-bundle/src/Image/Studio/Figure.php @@ -257,7 +257,7 @@ public function getLegacyTemplateData($margin = null, string $floating = null, b $image = $this->getImage(); $originalSize = $image->getOriginalDimensions()->getSize(); - $fileInfoImageSize = (new File(rawurldecode($image->getImageSrc())))->imageSize; + $fileInfoImageSize = (array) (new File($image->getImageSrc(true)))->imageSize; $linkAttributes = $this->getLinkAttributes(); $metadata = $this->hasMetadata() ? $this->getMetadata() : new Metadata([]); @@ -273,7 +273,7 @@ public function getLegacyTemplateData($margin = null, string $floating = null, b 'width' => $originalSize->getWidth(), 'height' => $originalSize->getHeight(), 'arrSize' => $fileInfoImageSize, - 'imgSize' => sprintf(' width="%d" height="%d"', $fileInfoImageSize[0], $fileInfoImageSize[1]), + 'imgSize' => !empty($fileInfoImageSize) ? sprintf(' width="%d" height="%d"', $fileInfoImageSize[0], $fileInfoImageSize[1]) : '', 'singleSRC' => $image->getFilePath(), 'src' => $image->getImageSrc(), 'fullsize' => ('_blank' === ($linkAttributes['target'] ?? null)) || $this->hasLightbox(), diff --git a/core-bundle/src/Image/Studio/ImageResult.php b/core-bundle/src/Image/Studio/ImageResult.php index 23aef0dd36e..876456d4a7b 100644 --- a/core-bundle/src/Image/Studio/ImageResult.php +++ b/core-bundle/src/Image/Studio/ImageResult.php @@ -14,6 +14,7 @@ use Contao\CoreBundle\Image\ImageFactoryInterface; use Contao\CoreBundle\Image\PictureFactoryInterface; +use Contao\Image\Image; use Contao\Image\ImageDimensions; use Contao\Image\ImageInterface; use Contao\Image\PictureConfiguration; @@ -100,10 +101,18 @@ public function getImg(): array } /** - * Returns the "src" attribute of the image. + * Returns the "src" attribute of the image. This will return an URL by + * default. Set $asPath to true to get a relative file path instead. */ - public function getImageSrc(): string + public function getImageSrc(bool $asPath = false): string { + if ($asPath) { + /** @var Image $image */ + $image = $this->getPicture()->getImg()['src']; + + return Path::makeRelative($image->getPath(), $this->projectDir); + } + return $this->getImg()['src'] ?? ''; } diff --git a/core-bundle/tests/Image/Studio/FigureBuilderIntegrationTest.php b/core-bundle/tests/Image/Studio/FigureBuilderIntegrationTest.php index e2731e2cebf..85c73fe55ed 100644 --- a/core-bundle/tests/Image/Studio/FigureBuilderIntegrationTest.php +++ b/core-bundle/tests/Image/Studio/FigureBuilderIntegrationTest.php @@ -1432,6 +1432,7 @@ private function setUpTestCase(\Closure $testCase): array { // Evaluate preconditions and setup container $container = $this->getContainerWithContaoConfiguration(self::$testRoot); + $container->set('request_stack', $this->createMock(RequestStack::class)); System::setContainer($container); [$preConditions, $arguments] = $testCase(); diff --git a/core-bundle/tests/Image/Studio/FigureTest.php b/core-bundle/tests/Image/Studio/FigureTest.php index b4cfd8ca285..aac85f67115 100644 --- a/core-bundle/tests/Image/Studio/FigureTest.php +++ b/core-bundle/tests/Image/Studio/FigureTest.php @@ -339,15 +339,13 @@ public function testGetLegacyTemplateData(array $preconditions, array $buildAttr public function provideLegacyTemplateDataScenarios(): \Generator { - $imageSrc = 'files/public/foo.jpg'; - yield 'basic image data' => [ [null, null, null, null], [false, null, null], - function (array $data) use ($imageSrc): void { + function (array $data): void { $this->assertSame(['img foo'], $data['picture']['img']); $this->assertSame(['sources foo'], $data['picture']['sources']); - $this->assertSame($imageSrc, $data['src']); + $this->assertSame('https://assets.url/files/public/foo.jpg', $data['src']); $this->assertSame('path/to/resource.jpg', $data['singleSRC']); $this->assertSame(100, $data['width']); $this->assertSame(50, $data['height']); @@ -623,7 +621,10 @@ private function getImageMock() $image ->method('getImageSrc') - ->willReturn($imageSrc) + ->willReturnMap([ + [false, "https://assets.url/$imageSrc"], + [true, $imageSrc], + ]) ; return $image; diff --git a/core-bundle/tests/Image/Studio/ImageResultTest.php b/core-bundle/tests/Image/Studio/ImageResultTest.php index b3f0f0bc5c2..18c77fce5e5 100644 --- a/core-bundle/tests/Image/Studio/ImageResultTest.php +++ b/core-bundle/tests/Image/Studio/ImageResultTest.php @@ -17,35 +17,38 @@ use Contao\CoreBundle\Image\PictureFactoryInterface; use Contao\CoreBundle\Image\Studio\ImageResult; use Contao\CoreBundle\Tests\TestCase; +use Contao\Image\Image; use Contao\Image\ImageDimensions; use Contao\Image\ImageInterface; use Contao\Image\PictureInterface; +use Imagine\Image\ImagineInterface; use PHPUnit\Framework\MockObject\MockObject; use Psr\Container\ContainerInterface; +use Symfony\Component\Filesystem\Filesystem; class ImageResultTest extends TestCase { public function testGetPicture(): void { - $filePathOrImage = 'foo/bar/foobar.png'; + $filePathOrImage = '/project/dir/foo/bar/foobar.png'; $sizeConfiguration = [100, 200, 'crop']; /** @var PictureInterface&MockObject $picture */ $picture = $this->createMock(PictureInterface::class); $pictureFactory = $this->getPictureFactoryMock($filePathOrImage, $sizeConfiguration, $picture); $locator = $this->getLocatorMock($pictureFactory); - $imageResult = new ImageResult($locator, 'any/project/dir', $filePathOrImage, $sizeConfiguration); + $imageResult = new ImageResult($locator, '/project/dir', $filePathOrImage, $sizeConfiguration); $this->assertSame($picture, $imageResult->getPicture()); } public function testGetSourcesAndImg(): void { - $filePathOrImage = 'foo/bar/foobar.png'; + $filePathOrImage = '/project/dir/foo/bar/foobar.png'; $sizeConfiguration = [100, 200, 'crop']; - $projectDir = 'project/dir'; - $staticUrl = 'static/url'; + $projectDir = '/project/dir'; + $staticUrl = 'https://static.url'; $sources = ['sources result']; $img = ['img result']; @@ -76,13 +79,13 @@ public function testGetSourcesAndImg(): void public function testGetImageSrc(): void { - $filePath = 'foo/bar/foobar.png'; + $filePathOrImage = '/project/dir/foo/bar/foobar.png'; $sizeConfiguration = [100, 200, 'crop']; - $projectDir = 'project/dir'; - $staticUrl = 'static/url'; + $projectDir = '/project/dir'; + $staticUrl = 'https://static.url'; - $img = ['src' => 'foo', 'other' => 'bar']; + $img = ['src' => 'https://static.url/foo/bar/foobar.png', 'other' => 'bar']; /** @var PictureInterface&MockObject $picture */ $picture = $this->createMock(PictureInterface::class); @@ -93,16 +96,54 @@ public function testGetImageSrc(): void ->willReturn($img) ; - $pictureFactory = $this->getPictureFactoryMock($filePath, $sizeConfiguration, $picture); + $pictureFactory = $this->getPictureFactoryMock($filePathOrImage, $sizeConfiguration, $picture); $locator = $this->getLocatorMock($pictureFactory, $staticUrl); - $imageResult = new ImageResult($locator, $projectDir, $filePath, $sizeConfiguration); + $imageResult = new ImageResult($locator, $projectDir, $filePathOrImage, $sizeConfiguration); + + $this->assertSame('https://static.url/foo/bar/foobar.png', $imageResult->getImageSrc()); + } + + public function testGetImageSrcAsPath(): void + { + $filePathOrImage = '/project/dir/foo/bar/foobar.png'; + $sizeConfiguration = [100, 200, 'crop']; + + $projectDir = '/project/dir'; + $staticUrl = 'https://static.url'; + + $filesystem = $this->createMock(Filesystem::class); + $filesystem + ->method('exists') + ->willReturn(true) + ; + + $img = [ + 'src' => new Image( + $filePathOrImage, + $this->createMock(ImagineInterface::class), + $filesystem + ), + ]; + + /** @var PictureInterface&MockObject $picture */ + $picture = $this->createMock(PictureInterface::class); + $picture + ->expects($this->once()) + ->method('getImg') + ->with() + ->willReturn($img) + ; + + $pictureFactory = $this->getPictureFactoryMock($filePathOrImage, $sizeConfiguration, $picture); + $locator = $this->getLocatorMock($pictureFactory, $staticUrl); + $imageResult = new ImageResult($locator, $projectDir, $filePathOrImage, $sizeConfiguration); - $this->assertSame('foo', $imageResult->getImageSrc()); + $this->assertSame('foo/bar/foobar.png', $imageResult->getImageSrc(true)); } public function testGetOriginalDimensionsFromPathResource(): void { - $filePath = 'foo/bar/foobar.png'; + $filePathOrImage = '/project/dir/foo/bar/foobar.png'; $dimensions = $this->createMock(ImageDimensions::class); /** @var ImageInterface&MockObject $image */ @@ -118,7 +159,7 @@ public function testGetOriginalDimensionsFromPathResource(): void $imageFactory ->expects($this->once()) ->method('create') - ->with($filePath) + ->with($filePathOrImage) ->willReturn($image) ; @@ -131,7 +172,7 @@ public function testGetOriginalDimensionsFromPathResource(): void ->willReturn($imageFactory) ; - $imageResult = new ImageResult($locator, 'any/project/dir', $filePath); + $imageResult = new ImageResult($locator, '/project/dir', $filePathOrImage); $this->assertSame($dimensions, $imageResult->getOriginalDimensions()); @@ -217,7 +258,6 @@ private function getLocatorMock(?PictureFactoryInterface $pictureFactory = null, if (null !== $staticUrl) { $context = $this->createMock(ContaoContext::class); $context - ->expects($this->atLeastOnce()) ->method('getStaticUrl') ->willReturn($staticUrl) ; From cba304c894638798abc5d5c8bd621c1759ae6589 Mon Sep 17 00:00:00 2001 From: "M. Vondano" Date: Tue, 6 Oct 2020 12:06:14 +0200 Subject: [PATCH 47/57] Add image_container class to default figure template (see #2379) Description ----------- | Q | A | -----------------| --- | Fixed issues | - | Docs PR or issue | - I figured we should probably add the `image_container` class to the core's default `figure.html.twig` template as well so that people can use it as a drop-in-replacement for the `image.html5` template. If someone should not want this class to be present in their installation, only a minor template adjustment is needed. Commits ------- cda83aaa add 'image_container' class to default figure.html.twig template to be inline with the image.html5 template --- core-bundle/src/Resources/views/Image/Studio/figure.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-bundle/src/Resources/views/Image/Studio/figure.html.twig b/core-bundle/src/Resources/views/Image/Studio/figure.html.twig index 0c9354b6df1..824934edbaa 100644 --- a/core-bundle/src/Resources/views/Image/Studio/figure.html.twig +++ b/core-bundle/src/Resources/views/Image/Studio/figure.html.twig @@ -1,3 +1,3 @@ {% import "@ContaoCore/Image/Studio/_macros.html.twig" as studio %} -{{- studio.figure(figure) -}} +{{- studio.figure(figure, { attr: { class: 'image_container' }}) -}} From 2afe13cdc425cd4da774df205d6df4758f5e47ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ausw=C3=B6ger?= Date: Tue, 6 Oct 2020 13:43:30 +0200 Subject: [PATCH 48/57] Remove last username from session after use (see #2399) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #1627 See https://github.com/contao/contao/issues/1627#issuecomment-669228176 Commits ------- 70781cf7 Remove last username from session after use --- .../src/EventListener/ClearSessionDataListener.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core-bundle/src/EventListener/ClearSessionDataListener.php b/core-bundle/src/EventListener/ClearSessionDataListener.php index 3c910648efd..5b09eff41ce 100644 --- a/core-bundle/src/EventListener/ClearSessionDataListener.php +++ b/core-bundle/src/EventListener/ClearSessionDataListener.php @@ -13,7 +13,9 @@ namespace Contao\CoreBundle\EventListener; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\Security\Core\Security; /** * @internal @@ -39,6 +41,10 @@ public function __invoke(ResponseEvent $event): void return; } + if ($event->getResponse()->isSuccessful()) { + $this->clearLoginData($request->getSession()); + } + $this->clearLegacyAttributeBags('FE_DATA'); $this->clearLegacyAttributeBags('BE_DATA'); $this->clearLegacyFormData(); @@ -68,4 +74,10 @@ private function clearLegacyFormData(): void unset($_SESSION['FORM_DATA'], $_SESSION['FILES']); } + + private function clearLoginData(SessionInterface $session): void + { + $session->remove(Security::AUTHENTICATION_ERROR); + $session->remove(Security::LAST_USERNAME); + } } From 67ecdac63362360c63b7cbd28c37e360a2b129d7 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Tue, 6 Oct 2020 16:41:46 +0200 Subject: [PATCH 49/57] Check allowed page types (see #2397) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #1521 With this PR Contao only allows acceptable page type options in the respective place: 1. The root node can only contain `root` (already present) 2. `root` is not allowed with a parent page 3. `error_401`, `error_403` and `error_404` are only allowed as direct descendents of type `root` 4. `error_401`, `error_403` and `error_404` can only exist once per root page This PR is against Contao 4.10, because it added the feature to limit the page type options. I don't think we should invest into fixing this in Contao 4.9, also because it might introduce a slightly different behavior for people. I'm not 100% sure if the error pages can only exist once per root page? What about permissions or unpublished pages? Also, this is not 100%, it does not prevent copying the page type etc., but I don't think it should. Commits ------- dd3e5b28 Check allowed page types 0a6ceea8 CS c67e2500 Merge branch '4.10' into bugfix/page-options --- .../EventListener/FilterPageTypeListener.php | 42 ++++- core-bundle/src/Resources/config/listener.yml | 2 + .../FilterPageTypeListenerTest.php | 143 ++++++++++++++++++ 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 core-bundle/tests/EventListener/FilterPageTypeListenerTest.php diff --git a/core-bundle/src/EventListener/FilterPageTypeListener.php b/core-bundle/src/EventListener/FilterPageTypeListener.php index 98908f0857b..c06c322b6fb 100644 --- a/core-bundle/src/EventListener/FilterPageTypeListener.php +++ b/core-bundle/src/EventListener/FilterPageTypeListener.php @@ -13,12 +13,24 @@ namespace Contao\CoreBundle\EventListener; use Contao\CoreBundle\Event\FilterPageTypeEvent; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\FetchMode; /** * @internal */ class FilterPageTypeListener { + /** + * @var Connection + */ + private $connection; + + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + public function __invoke(FilterPageTypeEvent $event): void { $dc = $event->getDataContainer(); @@ -27,11 +39,33 @@ public function __invoke(FilterPageTypeEvent $event): void return; } - // Root pages are allowed on the first level only (see #6360) - if ($dc->activeRecord->pid > 0) { - $event->removeOption('root'); - } else { + // The first level can only have root pages (see #6360) + if (!$dc->activeRecord->pid) { $event->setOptions(['root']); + + return; + } + + $event->removeOption('root'); + + $parentType = $this->connection->fetchColumn('SELECT type FROM tl_page WHERE id=?', [$dc->activeRecord->pid]); + + // Error pages can only be placed directly inside root pages + if ('root' !== $parentType) { + $event->removeOption('error_401'); + $event->removeOption('error_403'); + $event->removeOption('error_404'); + + return; + } + + $siblingTypes = $this->connection + ->executeQuery('SELECT DISTINCT(type) FROM tl_page WHERE pid=?', [$dc->activeRecord->pid]) + ->fetchAll(FetchMode::COLUMN) + ; + + foreach (array_intersect(['error_401', 'error_403', 'error_404'], $siblingTypes) as $type) { + $event->removeOption($type); } } } diff --git a/core-bundle/src/Resources/config/listener.yml b/core-bundle/src/Resources/config/listener.yml index af141186ae4..3e113cda236 100644 --- a/core-bundle/src/Resources/config/listener.yml +++ b/core-bundle/src/Resources/config/listener.yml @@ -37,6 +37,8 @@ services: public: true Contao\CoreBundle\EventListener\FilterPageTypeListener: + arguments: + - '@database_connection' tags: - { name: kernel.event_listener } diff --git a/core-bundle/tests/EventListener/FilterPageTypeListenerTest.php b/core-bundle/tests/EventListener/FilterPageTypeListenerTest.php new file mode 100644 index 00000000000..0f68cd85382 --- /dev/null +++ b/core-bundle/tests/EventListener/FilterPageTypeListenerTest.php @@ -0,0 +1,143 @@ +createMock(Connection::class); + $connection + ->expects($this->never()) + ->method($this->anything()) + ; + + $event = new FilterPageTypeEvent(['foo', 'bar'], $this->mockDataContainer(null)); + + $listener = new FilterPageTypeListener($connection); + $listener($event); + + $this->assertSame(['foo', 'bar'], $event->getOptions()); + } + + public function testOnlyAllowsRootTypeWithoutPid(): void + { + $connection = $this->createMock(Connection::class); + $connection + ->expects($this->never()) + ->method($this->anything()) + ; + + $event = new FilterPageTypeEvent(['foo', 'bar', 'root'], $this->mockDataContainer(0)); + + $listener = new FilterPageTypeListener($connection); + $listener($event); + + $this->assertSame(['root'], $event->getOptions()); + } + + public function testRemovesRootTypeIfHasParentPage(): void + { + $connection = $this->createMock(Connection::class); + $connection + ->expects($this->once()) + ->method('fetchColumn') + ->willReturn('foo') + ; + + $event = new FilterPageTypeEvent(['foo', 'root'], $this->mockDataContainer(17)); + + $listener = new FilterPageTypeListener($connection); + $listener($event); + + $this->assertSame(['foo'], $event->getOptions()); + } + + public function testRemovesErrorTypesIfParentIsNotRoot(): void + { + $connection = $this->createMock(Connection::class); + $connection + ->expects($this->once()) + ->method('fetchColumn') + ->with('SELECT type FROM tl_page WHERE id=?', [17]) + ->willReturn('foo') + ; + + $event = new FilterPageTypeEvent( + ['foo', 'root', 'error_401', 'error_403', 'error_404'], + $this->mockDataContainer(17) + ); + + $listener = new FilterPageTypeListener($connection); + $listener($event); + + $this->assertSame(['foo'], $event->getOptions()); + } + + public function testRemovesErrorTypesAlreadyPresentInTheRootPage(): void + { + $statement = $this->createMock(Statement::class); + $statement + ->expects($this->once()) + ->method('fetchAll') + ->with(FetchMode::COLUMN) + ->willReturn(['foo', 'error_401', 'error_403']) + ; + + $connection = $this->createMock(Connection::class); + $connection + ->expects($this->once()) + ->method('fetchColumn') + ->with('SELECT type FROM tl_page WHERE id=?', [1]) + ->willReturn('root') + ; + + $connection + ->expects($this->once()) + ->method('executeQuery') + ->with('SELECT DISTINCT(type) FROM tl_page WHERE pid=?', [1]) + ->willReturn($statement) + ; + + $event = new FilterPageTypeEvent( + ['foo', 'root', 'error_401', 'error_403', 'error_404'], + $this->mockDataContainer(1) + ); + + $listener = new FilterPageTypeListener($connection); + $listener($event); + + $this->assertSame(['foo', 'error_404'], $event->getOptions()); + } + + /** + * @return DataContainer&MockObject + */ + private function mockDataContainer(?int $pid): DataContainer + { + /** @var DataContainer&MockObject */ + return $this->mockClassWithProperties( + DataContainer::class, + ['activeRecord' => null === $pid ? null : (object) ['pid' => $pid]] + ); + } +} From 8044e70a5ea6bc54cc67869f0bb2cb865ae86d2c Mon Sep 17 00:00:00 2001 From: "M. Vondano" Date: Tue, 6 Oct 2020 17:26:41 +0200 Subject: [PATCH 50/57] Replace phpunit/token-stream with nikic/php-parser (see #2284) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #2282 | Docs PR or issue | - Here is a POC to replace `phpunit/token-stream` with `nikic/php-parser`. It won't be able to keep the whitespaces in the output, though (see test output, [Reference from docs](https://github.com/nikic/PHP-Parser/blob/master/doc/0_Introduction.markdown#what-output-does-it-produce)). Not sure if this matters as internally we only use this to dump/combine code, not to format it. Commits ------- 170c1201 replace phpunit/php-token-stream requirement with nikic/php-parser 98225f64 rewrite PHPFileLoader to use PhpParser 677c0472 drop unused condition unlike before an empty global namespace definition is already stripped at that point with the $namespace variable being an empty string e91d5ca1 - rewrite legacy check to work with any format - unify visitors - also drop inline html (files are getting included) d13ba427 explicitly require legacy check 17e99151 improve whitespaces when wrapping namespaces cae22aeb adjust expected results in tests This only affects: - whitespaces - linebreaks - comments - prefixed \ 82b4d75c CS --- composer.json | 2 +- core-bundle/composer.json | 2 +- .../src/Config/Loader/PhpFileLoader.php | 147 +++++++++++------- .../tests/Cache/ContaoCacheWarmerTest.php | 4 +- .../tests/Config/Loader/PhpFileLoaderTest.php | 81 ++-------- 5 files changed, 100 insertions(+), 136 deletions(-) diff --git a/composer.json b/composer.json index 49b7327e8ac..4cf3165b59c 100644 --- a/composer.json +++ b/composer.json @@ -65,12 +65,12 @@ "michelf/php-markdown": "^1.4", "nelmio/cors-bundle": "^1.5.3 || ^2.0.1", "nelmio/security-bundle": "^2.2", + "nikic/php-parser": "^4.9", "nyholm/psr7": "^1.2", "ocramius/proxy-manager": "^2.1", "paragonie/constant_time_encoding": "^2.2", "patchwork/utf8": "^1.2", "phpspec/php-diff": "^1.0", - "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0 || ^4.0", "psr/log": "^1.0", "ramsey/uuid": "^3.8", "scheb/two-factor-bundle": "^4.11", diff --git a/core-bundle/composer.json b/core-bundle/composer.json index e7b49ff6317..d5089d4a6e3 100644 --- a/core-bundle/composer.json +++ b/core-bundle/composer.json @@ -62,11 +62,11 @@ "matthiasmullie/minify": "^1.3", "michelf/php-markdown": "^1.4", "nelmio/cors-bundle": "^1.5.3 || ^2.0.1", + "nikic/php-parser": "^4.9", "nyholm/psr7": "^1.2", "paragonie/constant_time_encoding": "^2.2", "patchwork/utf8": "^1.2", "phpspec/php-diff": "^1.0", - "phpunit/php-token-stream": "^1.4 || ^2.0 || ^3.0 || ^4.0", "psr/log": "^1.0", "ramsey/uuid": "^3.8", "scheb/two-factor-bundle": "^4.11", diff --git a/core-bundle/src/Config/Loader/PhpFileLoader.php b/core-bundle/src/Config/Loader/PhpFileLoader.php index 2f5cc3fe203..8899dc4a99c 100644 --- a/core-bundle/src/Config/Loader/PhpFileLoader.php +++ b/core-bundle/src/Config/Loader/PhpFileLoader.php @@ -12,6 +12,23 @@ namespace Contao\CoreBundle\Config\Loader; +use PhpParser\Node; +use PhpParser\Node\Expr\BooleanNot; +use PhpParser\Node\Expr\Exit_; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar\String_; +use PhpParser\Node\Stmt\Declare_; +use PhpParser\Node\Stmt\Expression; +use PhpParser\Node\Stmt\If_; +use PhpParser\Node\Stmt\InlineHTML; +use PhpParser\Node\Stmt\Namespace_; +use PhpParser\Node\Stmt\Use_; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\NodeVisitorAbstract; +use PhpParser\ParserFactory; +use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Symfony\Component\Config\Loader\Loader; use Webmozart\PathUtil\Path; @@ -24,10 +41,8 @@ public function load($resource, $type = null): string { [$code, $namespace] = $this->parseFile((string) $resource); - $code = $this->stripLegacyCheck($code); - - if (false !== $namespace && 'namespaced' === $type) { - $code = sprintf("\nnamespace %s {%s}\n", $namespace, $code); + if ('namespaced' === $type) { + $code = sprintf("\nnamespace %s{%s}\n", ltrim($namespace.' '), $code); } return $code; @@ -45,71 +60,83 @@ public function supports($resource, $type = null): bool */ private function parseFile(string $file): array { - $code = ''; - $namespace = ''; - $buffer = false; - $stream = new \PHP_Token_Stream($file); - - foreach ($stream as $token) { - switch (true) { - case $token instanceof \PHP_Token_OPEN_TAG: - case $token instanceof \PHP_Token_CLOSE_TAG: - // remove - break; - - case false !== $buffer: - $buffer .= $token; - - if (';' === (string) $token) { - $code .= $this->handleDeclare($buffer); - $buffer = false; + $ast = (new ParserFactory()) + ->create(ParserFactory::PREFER_PHP7) + ->parse(trim(file_get_contents($file))) + ; + + $namespaceResolver = new NameResolver(); + + $nodeStripper = new class() extends NodeVisitorAbstract { + public function leaveNode(Node $node) + { + // Drop namespace and use declarations + if ($node instanceof Namespace_) { + return $node->stmts; + } + + if ($node instanceof Use_) { + return NodeTraverser::REMOVE_NODE; + } + + // Drop 'strict_types' definition + if ($node instanceof Declare_) { + foreach ($node->declares as $key => $declare) { + if ('strict_types' === $declare->key->name) { + unset($node->declares[$key]); + } } - break; - - case $token instanceof \PHP_Token_NAMESPACE: - if ('{' === $token->getName()) { - $namespace = false; - $code .= $token; - } else { - $namespace = $token->getName(); - $stream->seek($token->getEndTokenId()); + + if (empty($node->declares)) { + return NodeTraverser::REMOVE_NODE; } - break; + } + + // Drop any inline HTML + if ($node instanceof InlineHTML) { + return NodeTraverser::REMOVE_NODE; + } - case $token instanceof \PHP_Token_DECLARE: - $buffer = (string) $token; - break; + // Drop legacy access check + if ($this->matchLegacyCheck($node)) { + return NodeTraverser::REMOVE_NODE; + } - default: - $code .= $token; + return null; } - } - return [$code, $namespace]; - } + private function matchLegacyCheck(Node $node): bool + { + return $node instanceof If_ + // match "if(!defined('TL_ROOT'))" + && ($condition = $node->cond) instanceof BooleanNot + && $condition->expr instanceof FuncCall + && $condition->expr->name instanceof Name + && 'defined' === $condition->expr->name->toLowerString() + && null !== ($argument = $condition->expr->args[0] ?? null) + && $argument->value instanceof String_ + && 'TL_ROOT' === $argument->value->value + + // match "die('You ...')" + && ($statement = $node->stmts[0] ?? null) instanceof Expression + && $statement->expr instanceof Exit_ + && ($text = $statement->expr->expr) instanceof String_ + && \in_array($text->value, ['You cannot access this file directly!', 'You can not access this file directly!'], true); + } + }; - private function handleDeclare(string $code): string - { - $code = preg_replace('/(,\s*)?strict_types\s*=\s*1(\s*,)?/', '', $code); + $traverser = new NodeTraverser(); + $traverser->addVisitor($namespaceResolver); + $traverser->addVisitor($nodeStripper); - if (preg_match('/declare\(\s*\)/', $code)) { - return ''; - } + $ast = $traverser->traverse($ast); - return str_replace(' ', '', $code); - } + // Emit code and namespace information + $prettyPrinter = new PrettyPrinter(); + $code = sprintf("\n%s\n", $prettyPrinter->prettyPrint($ast)); + $namespaceNode = $namespaceResolver->getNameContext()->getNamespace(); + $namespace = null !== $namespaceNode ? $namespaceNode->toString() : ''; - private function stripLegacyCheck(string $code): string - { - $code = str_replace( - [ - "if (!defined('TL_ROOT')) die('You cannot access this file directly!');", - "if (!defined('TL_ROOT')) die('You can not access this file directly!');", - ], - '', - $code - ); - - return "\n".trim($code)."\n"; + return [$code, $namespace]; } } diff --git a/core-bundle/tests/Cache/ContaoCacheWarmerTest.php b/core-bundle/tests/Cache/ContaoCacheWarmerTest.php index a05d4aa8976..83f9424d23f 100644 --- a/core-bundle/tests/Cache/ContaoCacheWarmerTest.php +++ b/core-bundle/tests/Cache/ContaoCacheWarmerTest.php @@ -67,7 +67,7 @@ public function testCreatesTheCacheFolder(): void $this->assertFileExists($this->getFixturesDir().'/var/cache/contao/sql/tl_test.php'); $this->assertStringContainsString( - "\$GLOBALS['TL_TEST'] = true;", + "\$GLOBALS['TL_TEST'] = \\true;", file_get_contents($this->getFixturesDir().'/var/cache/contao/config/config.php') ); @@ -77,7 +77,7 @@ public function testCreatesTheCacheFolder(): void ); $this->assertStringContainsString( - "\$GLOBALS['TL_DCA']['tl_test'] = [\n", + "\$GLOBALS['TL_DCA']['tl_test'] = [", file_get_contents($this->getFixturesDir().'/var/cache/contao/dca/tl_test.php') ); diff --git a/core-bundle/tests/Config/Loader/PhpFileLoaderTest.php b/core-bundle/tests/Config/Loader/PhpFileLoaderTest.php index 00ccf07fb35..b57916ae731 100644 --- a/core-bundle/tests/Config/Loader/PhpFileLoaderTest.php +++ b/core-bundle/tests/Config/Loader/PhpFileLoaderTest.php @@ -48,7 +48,7 @@ public function testLoadsPhpFiles(): void { $expects = <<<'EOF' -$GLOBALS['TL_TEST'] = true; +$GLOBALS['TL_TEST'] = \true; EOF; @@ -59,21 +59,7 @@ public function testLoadsPhpFiles(): void $content = <<<'EOF' -$GLOBALS['TL_DCA']['tl_test'] = [ - 'config' => [ - 'dataContainer' => 'Table', - 'sql' => [ - 'keys' => [ - 'id' => 'primary', - ], - ], - ], - 'fields' => [ - 'id' => [ - 'sql' => "int(10) unsigned NOT NULL auto_increment" - ], - ], -]; +$GLOBALS['TL_DCA']['tl_test'] = ['config' => ['dataContainer' => 'Table', 'sql' => ['keys' => ['id' => 'primary']]], 'fields' => ['id' => ['sql' => "int(10) unsigned NOT NULL auto_increment"]]]; EOF; @@ -104,7 +90,7 @@ public function testAddsCustomNamespaces(): void $expects = <<<'EOF' namespace { - $GLOBALS['TL_DCA']['tl_test']['config']['dataContainer'] = 'Table'; +$GLOBALS['TL_DCA']['tl_test']['config']['dataContainer'] = 'Table'; } EOF; @@ -119,8 +105,8 @@ public function testAddsCustomNamespaces(): void $expects = <<<'EOF' -namespace { -$GLOBALS['TL_TEST'] = true; +namespace { +$GLOBALS['TL_TEST'] = \true; } EOF; @@ -141,21 +127,7 @@ public function testStripsDeclareStrictTypes(string $file): void { $content = <<<'EOF' -$GLOBALS['TL_DCA']['tl_test'] = [ - 'config' => [ - 'dataContainer' => 'Table', - 'sql' => [ - 'keys' => [ - 'id' => 'primary', - ], - ], - ], - 'fields' => [ - 'id' => [ - 'sql' => "int(10) unsigned NOT NULL auto_increment" - ], - ], -]; +$GLOBALS['TL_DCA']['tl_test'] = ['config' => ['dataContainer' => 'Table', 'sql' => ['keys' => ['id' => 'primary']]], 'fields' => ['id' => ['sql' => "int(10) unsigned NOT NULL auto_increment"]]]; EOF; @@ -174,27 +146,7 @@ public function testIgnoresDeclareStatementsInComments(): void { $content = <<<'EOF' -/** - * I am a declare(strict_types=1) comment - */ - - - -$GLOBALS['TL_DCA']['tl_test'] = [ - 'config' => [ - 'dataContainer' => 'Table', - 'sql' => [ - 'keys' => [ - 'id' => 'primary', - ], - ], - ], - 'fields' => [ - 'id' => [ - 'sql' => "int(10) unsigned NOT NULL auto_increment" - ], - ], -]; +$GLOBALS['TL_DCA']['tl_test'] = ['config' => ['dataContainer' => 'Table', 'sql' => ['keys' => ['id' => 'primary']]], 'fields' => ['id' => ['sql' => "int(10) unsigned NOT NULL auto_increment"]]]; EOF; @@ -219,23 +171,8 @@ public function testPreservesOtherDeclareDefinitions(string $file): void { $content = <<<'EOF' -declare(ticks=1); - -$GLOBALS['TL_DCA']['tl_test'] = [ - 'config' => [ - 'dataContainer' => 'Table', - 'sql' => [ - 'keys' => [ - 'id' => 'primary', - ], - ], - ], - 'fields' => [ - 'id' => [ - 'sql' => "int(10) unsigned NOT NULL auto_increment" - ], - ], -]; +declare (ticks=1); +$GLOBALS['TL_DCA']['tl_test'] = ['config' => ['dataContainer' => 'Table', 'sql' => ['keys' => ['id' => 'primary']]], 'fields' => ['id' => ['sql' => "int(10) unsigned NOT NULL auto_increment"]]]; EOF; From 9ce0168f83735be9addb253a6db2f183dc260f3e Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Wed, 7 Oct 2020 08:03:40 +0200 Subject: [PATCH 51/57] Resolve private services in the ContaoCoreExtensionTest class (see #2403) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #949 | Docs PR or issue | - Commits ------- 7e152462 Resolve private services in the ContaoCoreExtensionTest class --- core-bundle/src/Resources/config/services.yml | 2 ++ .../DependencyInjection/ContaoCoreExtensionTest.php | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core-bundle/src/Resources/config/services.yml b/core-bundle/src/Resources/config/services.yml index 516018ba3d3..cbc18ddf6fa 100644 --- a/core-bundle/src/Resources/config/services.yml +++ b/core-bundle/src/Resources/config/services.yml @@ -49,6 +49,7 @@ services: class: Contao\CoreBundle\Cache\ContaoCacheClearer arguments: - '@filesystem' + public: true contao.cache.warm_internal: class: Contao\CoreBundle\Cache\ContaoCacheWarmer @@ -60,6 +61,7 @@ services: - '@database_connection' - '@contao.framework' - '%contao.locales%' + public: true Contao\CoreBundle\Controller\BackendController: ~ diff --git a/core-bundle/tests/DependencyInjection/ContaoCoreExtensionTest.php b/core-bundle/tests/DependencyInjection/ContaoCoreExtensionTest.php index b2da7e7c5ea..dcb9e209282 100644 --- a/core-bundle/tests/DependencyInjection/ContaoCoreExtensionTest.php +++ b/core-bundle/tests/DependencyInjection/ContaoCoreExtensionTest.php @@ -149,6 +149,7 @@ use Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher; use Symfony\Cmf\Component\Routing\ProviderBasedGenerator; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Compiler\ResolvePrivatesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -191,6 +192,10 @@ protected function setUp(): void $extension = new ContaoCoreExtension(); $extension->load($params, $this->container); + + // Resolve private services (see #949) + $pass = new ResolvePrivatesPass(); + $pass->process($this->container); } public function testReturnsTheCorrectAlias(): void @@ -1547,7 +1552,7 @@ public function testRegistersTheLegacyCronService(): void $definition = $this->container->getDefinition(LegacyCron::class); - $this->assertTrue($definition->isPublic()); + $this->assertTrue($definition->isPrivate()); $this->assertEquals( [ @@ -1779,7 +1784,7 @@ public function testRegistersTheImageImagineService(): void { $this->assertTrue($this->container->has('contao.image.imagine')); - $definition = $this->container->findDefinition('contao.image.imagine'); + $definition = $this->container->getAlias('contao.image.imagine'); $this->assertTrue($definition->isPublic()); } From fa9c348ad753d5f56f28469d37502d0b03de0a9b Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Wed, 7 Oct 2020 08:24:55 +0200 Subject: [PATCH 52/57] Update the changelog (see #2406) Description ----------- - Commits ------- ed369cdb Update the changelog --- CHANGELOG.md | 5 +++++ core-bundle/src/Resources/contao/config/constants.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82450551b4..5bade8889f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +## 4.4.54 (2020-10-07) + + * Correctly detect unknown options (see #2360). + * Support legacy console scripts in the initialize.php (see #2344). + ## 4.4.53 (2020-09-25) * Fix picker for providers with insert tags (see #2353). diff --git a/core-bundle/src/Resources/contao/config/constants.php b/core-bundle/src/Resources/contao/config/constants.php index 4d79fdb123f..6543adada79 100644 --- a/core-bundle/src/Resources/contao/config/constants.php +++ b/core-bundle/src/Resources/contao/config/constants.php @@ -10,7 +10,7 @@ // Core version define('VERSION', '4.4'); -define('BUILD', '53'); +define('BUILD', '54'); define('LONG_TERM_SUPPORT', true); // Link constants From 80a446583eb6c2b4540dc78466eae182e2644ca1 Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Wed, 7 Oct 2020 08:59:57 +0200 Subject: [PATCH 53/57] Update the changelog and the language files (see #2408) Description ----------- - Commits ------- 177d1948 Update the changelog and the language files --- CHANGELOG.md | 38 ++++++++++++++++++- .../src/Resources/contao/config/constants.php | 2 +- .../Resources/contao/languages/cs/default.xlf | 3 ++ .../Resources/contao/languages/de/default.xlf | 4 ++ .../Resources/contao/languages/es/default.xlf | 3 ++ .../Resources/contao/languages/fa/default.xlf | 3 ++ .../Resources/contao/languages/it/default.xlf | 3 ++ .../Resources/contao/languages/ja/default.xlf | 4 ++ .../Resources/contao/languages/nl/default.xlf | 3 ++ .../Resources/contao/languages/pl/default.xlf | 3 ++ .../Resources/contao/languages/pt/default.xlf | 3 ++ .../Resources/contao/languages/ru/default.xlf | 4 ++ .../Resources/contao/languages/sl/default.xlf | 3 ++ .../Resources/contao/languages/sr/default.xlf | 3 ++ .../Resources/contao/languages/zh/default.xlf | 3 ++ 15 files changed, 79 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 843d3cd9a04..bedf4abe707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,31 @@ This project adheres to [Semantic Versioning]. +## [4.9.8] (2020-10-07) + +**Fixed issues:** + +- [#2403] Resolve private services in the ContaoCoreExtensionTest class ([leofeyer]) +- [#2399] Remove the last username from the session after use ([ausi]) +- [#2363] Reset the KEY_BLOCK_SIZE when migrating the MySQL engine and row format ([aschempp]) +- [#2388] Ignore the logout URL if no user is present ([fritzmg]) +- [#2376] Add a title tag callback to the SERP preview ([leofeyer]) +- [#2361] Prevent using page aliases that could be page IDs ([leofeyer]) +- [#2380] Fix the popup button padding ([leofeyer]) +- [#2373] Use $this->imageHref in the image.html5 template ([leofeyer]) +- [#2339] Optimize the check for inlined services ([aschempp]) +- [#2366] Harden non-normalized file extension comparisons in the LegacyResizer ([m-vo]) +- [#2369] Correctly check for numeric page IDs ([aschempp]) +- [#2351] Override the size variable for the ce_player template ([fritzmg]) +- [#2362] Correctly handle IDNA hostnames in the root page ([leofeyer]) +- [#2345] Support legacy console scripts in the initialize.php ([aschempp]) + ## [4.9.7] (2020-09-25) **Fixed issues:** -- [#2343] Only use $dc->id in the protectFolder() method ([leofeyer]) - [#2342] Fix entering 0 in the back end ([leofeyer]) +- [#2343] Only use $dc->id in the protectFolder() method ([leofeyer]) ## [4.9.6] (2020-09-24) @@ -422,6 +441,7 @@ This project adheres to [Semantic Versioning]. - [#991] Replace mb_strlen() with Utf8::strlen() ([leofeyer]) [Semantic Versioning]: https://semver.org/spec/v2.0.0.html +[4.9.8]: https://github.com/contao/contao/releases/tag/4.9.8 [4.9.7]: https://github.com/contao/contao/releases/tag/4.9.7 [4.9.6]: https://github.com/contao/contao/releases/tag/4.9.6 [4.9.5]: https://github.com/contao/contao/releases/tag/4.9.5 @@ -451,8 +471,22 @@ This project adheres to [Semantic Versioning]. [Tastaturberuf]: https://github.com/Tastaturberuf [Toflar]: https://github.com/Toflar [xchs]: https://github.com/xchs -[#2343]: https://github.com/contao/contao/pull/2343 +[#2403]: https://github.com/contao/contao/pull/2403 +[#2399]: https://github.com/contao/contao/pull/2399 +[#2363]: https://github.com/contao/contao/pull/2363 +[#2388]: https://github.com/contao/contao/pull/2388 +[#2376]: https://github.com/contao/contao/pull/2376 +[#2361]: https://github.com/contao/contao/pull/2361 +[#2380]: https://github.com/contao/contao/pull/2380 +[#2373]: https://github.com/contao/contao/pull/2373 +[#2339]: https://github.com/contao/contao/pull/2339 +[#2366]: https://github.com/contao/contao/pull/2366 +[#2369]: https://github.com/contao/contao/pull/2369 +[#2351]: https://github.com/contao/contao/pull/2351 +[#2362]: https://github.com/contao/contao/pull/2362 +[#2345]: https://github.com/contao/contao/pull/2345 [#2342]: https://github.com/contao/contao/pull/2342 +[#2343]: https://github.com/contao/contao/pull/2343 [#2148]: https://github.com/contao/contao/pull/2148 [#2313]: https://github.com/contao/contao/pull/2313 [#2320]: https://github.com/contao/contao/pull/2320 diff --git a/core-bundle/src/Resources/contao/config/constants.php b/core-bundle/src/Resources/contao/config/constants.php index e9ca11fe414..ae87ca4e171 100644 --- a/core-bundle/src/Resources/contao/config/constants.php +++ b/core-bundle/src/Resources/contao/config/constants.php @@ -10,7 +10,7 @@ // Core version define('VERSION', '4.9'); -define('BUILD', '7'); +define('BUILD', '8'); define('LONG_TERM_SUPPORT', true); // Link constants diff --git a/core-bundle/src/Resources/contao/languages/cs/default.xlf b/core-bundle/src/Resources/contao/languages/cs/default.xlf index 02cc78fae53..b1a7e00745d 100644 --- a/core-bundle/src/Resources/contao/languages/cs/default.xlf +++ b/core-bundle/src/Resources/contao/languages/cs/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Zástupce stránky "%s" již existuje! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Adresář "%s" nemůže být importován! diff --git a/core-bundle/src/Resources/contao/languages/de/default.xlf b/core-bundle/src/Resources/contao/languages/de/default.xlf index eb9fbfaaba7..e2020a8daca 100644 --- a/core-bundle/src/Resources/contao/languages/de/default.xlf +++ b/core-bundle/src/Resources/contao/languages/de/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! Der Alias "%s" existiert bereits! + + Numeric aliases are not supported! + Numerische Aliase werden nicht unterstützt! + The folder "%s" cannot be imported! Der Ordner "%s" kann nicht importiert werden! diff --git a/core-bundle/src/Resources/contao/languages/es/default.xlf b/core-bundle/src/Resources/contao/languages/es/default.xlf index 275b26de9a5..2baf17f3aa1 100644 --- a/core-bundle/src/Resources/contao/languages/es/default.xlf +++ b/core-bundle/src/Resources/contao/languages/es/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! ¡El alias "%s" ya existe! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! !No se puede importar la carpeta "%s"! diff --git a/core-bundle/src/Resources/contao/languages/fa/default.xlf b/core-bundle/src/Resources/contao/languages/fa/default.xlf index 6365eba185e..ad243912665 100644 --- a/core-bundle/src/Resources/contao/languages/fa/default.xlf +++ b/core-bundle/src/Resources/contao/languages/fa/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! نام مستعار %s وجود دارد! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! پوشه "%s" قابل وارد کردن نمی‌باشد! diff --git a/core-bundle/src/Resources/contao/languages/it/default.xlf b/core-bundle/src/Resources/contao/languages/it/default.xlf index dd9d69b975f..32d47059e94 100644 --- a/core-bundle/src/Resources/contao/languages/it/default.xlf +++ b/core-bundle/src/Resources/contao/languages/it/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! L'alias di pagina "%s" già esistente! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! La cartella "%s" non è stata importata! diff --git a/core-bundle/src/Resources/contao/languages/ja/default.xlf b/core-bundle/src/Resources/contao/languages/ja/default.xlf index 3989196ff06..ac6dd10f5ed 100644 --- a/core-bundle/src/Resources/contao/languages/ja/default.xlf +++ b/core-bundle/src/Resources/contao/languages/ja/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! "%s"というエイリアスは既に存在します。 + + Numeric aliases are not supported! + 数字だけのエイリアスはサポートしていません! + The folder "%s" cannot be imported! "%s"はフォルダーなのでインポートできません。 diff --git a/core-bundle/src/Resources/contao/languages/nl/default.xlf b/core-bundle/src/Resources/contao/languages/nl/default.xlf index df95c042a51..15b63a534da 100644 --- a/core-bundle/src/Resources/contao/languages/nl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/nl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Pagina alias "%s" bestaat reeds! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Map "%s" kan niet worden geïmporteerd! diff --git a/core-bundle/src/Resources/contao/languages/pl/default.xlf b/core-bundle/src/Resources/contao/languages/pl/default.xlf index e96c85c158a..6a67e5a57d1 100644 --- a/core-bundle/src/Resources/contao/languages/pl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/pl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Alias strony "%s" już istnieje! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Katalog "%s" nie może być zaimportowany! diff --git a/core-bundle/src/Resources/contao/languages/pt/default.xlf b/core-bundle/src/Resources/contao/languages/pt/default.xlf index f045fe66614..da6a0989ebe 100644 --- a/core-bundle/src/Resources/contao/languages/pt/default.xlf +++ b/core-bundle/src/Resources/contao/languages/pt/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Apelido de página %s já existente! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Pasta %s não pode ser importada! diff --git a/core-bundle/src/Resources/contao/languages/ru/default.xlf b/core-bundle/src/Resources/contao/languages/ru/default.xlf index c567089ecd3..72d4846f391 100644 --- a/core-bundle/src/Resources/contao/languages/ru/default.xlf +++ b/core-bundle/src/Resources/contao/languages/ru/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! Алиас страницы "%s" уже существует! + + Numeric aliases are not supported! + Цифровые алиасы не поддерживаются! + The folder "%s" cannot be imported! Каталог "%s" не может быть импортирован! diff --git a/core-bundle/src/Resources/contao/languages/sl/default.xlf b/core-bundle/src/Resources/contao/languages/sl/default.xlf index f959da434e8..582505cb498 100644 --- a/core-bundle/src/Resources/contao/languages/sl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/sl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Vzdevek "%s" že obstaja! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Mape "%s" ni mogoče uvoziti! diff --git a/core-bundle/src/Resources/contao/languages/sr/default.xlf b/core-bundle/src/Resources/contao/languages/sr/default.xlf index a893cc74929..a3f11307d1c 100644 --- a/core-bundle/src/Resources/contao/languages/sr/default.xlf +++ b/core-bundle/src/Resources/contao/languages/sr/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Алиас "%s" већ постоји! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! Фолдер "%s" не може бити увезен! diff --git a/core-bundle/src/Resources/contao/languages/zh/default.xlf b/core-bundle/src/Resources/contao/languages/zh/default.xlf index def971d2e01..9406d38b328 100644 --- a/core-bundle/src/Resources/contao/languages/zh/default.xlf +++ b/core-bundle/src/Resources/contao/languages/zh/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! 页面别名“%s”已经存在! + + Numeric aliases are not supported! + The folder "%s" cannot be imported! 无法导入文件夹“%s”! From 98f3913e9600f656429e14cf96297a01305ac531 Mon Sep 17 00:00:00 2001 From: "M. Vondano" Date: Wed, 7 Oct 2020 09:08:26 +0200 Subject: [PATCH 54/57] Fix SimpleTokenParser BC (see #2341) Description ----------- | Q | A | -----------------| --- | Fixed issues | Fixes #2315, #2387 | Docs PR or issue | - To regain BC there now is an additional step in which the variables used in the expression are identified. With this we can find out if any variable isn't provided in the array of data and log + exit gracefully (= return `false`) instead of throwing an exception. This also allows logging all unknown tokens not only the first occurrence. I also added a `bool` cast to the evaluated result to support shorthand/non-boolean expressions like `{if foo}`, `{if 1}`, `{if a + b}`. Commits ------- 321aebfa setup some BC tests 1ee74ae2 correctly handle unmatched tokens 5e45ff7f add and adjust tests 193723fc drop BC test dummies ec3d58dc preset missing vars with 'null' instead of always returning false d7995b68 also treat missing var as 'null' in legacy code path --- core-bundle/src/Util/SimpleTokenParser.php | 101 ++++++++++-- .../tests/Util/SimpleTokenParserTest.php | 147 +++++++++++++++++- 2 files changed, 234 insertions(+), 14 deletions(-) diff --git a/core-bundle/src/Util/SimpleTokenParser.php b/core-bundle/src/Util/SimpleTokenParser.php index 0295c2cf38c..8cf906f318e 100644 --- a/core-bundle/src/Util/SimpleTokenParser.php +++ b/core-bundle/src/Util/SimpleTokenParser.php @@ -16,6 +16,9 @@ use Psr\Log\LoggerAwareTrait; use Psr\Log\LogLevel; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\Lexer; +use Symfony\Component\ExpressionLanguage\SyntaxError; +use Symfony\Component\ExpressionLanguage\Token; class SimpleTokenParser implements LoggerAwareInterface { @@ -137,15 +140,31 @@ private function canUseExpressionLanguage(array $data): bool private function evaluateExpression(string $expression, array $data, bool $canUseExpressionLanguage): bool { - if ($canUseExpressionLanguage) { - try { - return $this->expressionLanguage->evaluate($expression, $data); - } catch (\Exception $e) { - throw new \InvalidArgumentException($e->getMessage(), 0, $e); - } + if (!$canUseExpressionLanguage) { + return $this->evaluateExpressionLegacy($expression, $data); + } + + $unmatchedVariables = array_diff($this->getVariables($expression), array_keys($data)); + + if (!empty($unmatchedVariables)) { + $this->logUnmatchedVariables(...$unmatchedVariables); + + // Define variables that weren't provided with the value 'null' + $data = array_merge( + array_combine($unmatchedVariables, array_fill(0, \count($unmatchedVariables), null)), + $data + ); } - // Legacy code + try { + return (bool) $this->expressionLanguage->evaluate($expression, $data); + } catch (SyntaxError $e) { + throw new \InvalidArgumentException($e->getMessage(), 0, $e); + } + } + + private function evaluateExpressionLegacy(string $expression, array $data): bool + { if (!preg_match('/^([^=!<>\s]+) *([=!<>]+)(.+)$/s', $expression, $matches)) { return false; } @@ -153,15 +172,13 @@ private function evaluateExpression(string $expression, array $data, bool $canUs [, $token, $operator, $value] = $matches; if (!\array_key_exists($token, $data)) { - if (null !== $this->logger) { - $this->logger->log(LogLevel::INFO, sprintf('Tried to evaluate unknown simple token "%s".', $token)); - } + $this->logUnmatchedVariables($token); - return false; + $tokenValue = null; + } else { + $tokenValue = $data[$token]; } - $tokenValue = $data[$token]; - // Normalize types $value = trim($value, ' '); @@ -217,4 +234,62 @@ private function evaluateExpression(string $expression, array $data, bool $canUs throw new \InvalidArgumentException(sprintf('Unknown simple token comparison operator "%s".', $operator)); } } + + private function getVariables(string $expression): array + { + /** @var array $tokens */ + $tokens = []; + + try { + $tokenStream = (new Lexer())->tokenize($expression); + + while (!$tokenStream->isEOF()) { + $tokens[] = $tokenStream->current; + $tokenStream->next(); + } + } catch (SyntaxError $e) { + // We cannot identify the variables if tokenizing fails + return []; + } + + $variables = []; + + for ($i = 0; $i < \count($tokens); ++$i) { + if (!$tokens[$i]->test(Token::NAME_TYPE)) { + continue; + } + + $value = $tokens[$i]->value; + + // Skip constant nodes (see Symfony/Component/ExpressionLanguage/Parser#parsePrimaryExpression() + if (\in_array($value, ['true', 'TRUE', 'false', 'FALSE', 'null'], true)) { + continue; + } + + // Skip functions + if (isset($tokens[$i + 1]) && '(' === $tokens[$i + 1]->value) { + ++$i; + + continue; + } + + if (!\in_array($value, $variables, true)) { + $variables[] = $value; + } + } + + return $variables; + } + + private function logUnmatchedVariables(string ...$tokenNames): void + { + if (null === $this->logger) { + return; + } + + $this->logger->log( + LogLevel::INFO, + sprintf('Tried to evaluate unknown simple token(s): "%s".', implode('", "', $tokenNames)) + ); + } } diff --git a/core-bundle/tests/Util/SimpleTokenParserTest.php b/core-bundle/tests/Util/SimpleTokenParserTest.php index 64ffb0bcb2f..c762284c28a 100644 --- a/core-bundle/tests/Util/SimpleTokenParserTest.php +++ b/core-bundle/tests/Util/SimpleTokenParserTest.php @@ -16,6 +16,8 @@ use Contao\CoreBundle\Util\SimpleTokenExpressionLanguage; use Contao\CoreBundle\Util\SimpleTokenParser; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\ExpressionLanguage\ExpressionFunction; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -290,6 +292,138 @@ public function parseSimpleTokensProvider(): \Generator ]; } + /** + * @dataProvider parsesSimpleTokensWithShorthandIfProvider + */ + public function testParsesSimpleTokensWithShorthandIf($value, bool $match): void + { + $this->assertSame( + $match ? 'match' : 'no-match', + $this->getParser()->parse('{if value}match{else}no-match{endif}', ['value' => $value]) + ); + } + + public function parsesSimpleTokensWithShorthandIfProvider(): \Generator + { + yield 'Test true matches' => [true, true]; + + yield 'Test 1 matches' => [1, true]; + + yield 'Test "1" matches' => ['1', true]; + + yield 'Test "anything" matches' => ['anything', true]; + + yield 'Test non-empty array matches' => [['foo'], true]; + + yield 'Test false does not match' => [false, false]; + + yield 'Test 0 does not match' => [0, false]; + + yield 'Test "0" does not match' => ['0', false]; + + yield 'Test null does not match' => [null, false]; + + yield 'Test empty string does not match' => ['', false]; + + yield 'Test empty array does not match' => [[], false]; + } + + /** + * @dataProvider handlesUnknownTokensProvider + */ + public function testHandlesUnknownTokens(string $condition, string $logMessage, bool $match): void + { + $parser = $this->getParser(); + + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::INFO, $logMessage) + ; + + $parser->setLogger($logger); + + $this->assertSame( + $match ? 'match' : 'no-match', + $parser->parse("{if $condition}match{else}no-match{endif}", ['foobar' => 1]) + ); + } + + public function handlesUnknownTokensProvider(): \Generator + { + yield 'Test single unknown token (left side of comparison)' => [ + 'foo == 1', + 'Tried to evaluate unknown simple token(s): "foo".', + false, + ]; + + yield 'Test single unknown token (right side of comparison)' => [ + '1 == foo', + 'Tried to evaluate unknown simple token(s): "foo".', + false, + ]; + + yield 'Test inverted comparison with unknown token matches' => [ + 'foo != "bar"', + 'Tried to evaluate unknown simple token(s): "foo".', + true, + ]; + + yield 'Test unknown token is equal to be null' => [ + 'foo === null', + 'Tried to evaluate unknown simple token(s): "foo".', + true, + ]; + + yield 'Test single unknown token (array test)' => [ + 'foo in [1, 2, 3]', + 'Tried to evaluate unknown simple token(s): "foo".', + false, + ]; + + yield 'Test single unknown token (regex test)' => [ + 'foo matches "/whatever/"', 'Tried to evaluate unknown simple token(s): "foo".', + false, + ]; + + yield 'Test PHP constants are treated as unknown variables' => [ + '__FILE__=="foo"', + 'Tried to evaluate unknown simple token(s): "__FILE__".', + false, + ]; + + yield 'Test multiple unknown tokens' => [ + 'foo === 1 and bar == null', + 'Tried to evaluate unknown simple token(s): "foo", "bar".', + false, + ]; + + yield 'Test multiple unknown tokens are considered equal' => [ + 'foo === bar', + 'Tried to evaluate unknown simple token(s): "foo", "bar".', + true, + ]; + + yield 'Test unknown token is only reported once' => [ + '1 == foo or foo in [2, 3]', + 'Tried to evaluate unknown simple token(s): "foo".', + false, + ]; + + yield 'Test true/false/null are recognized as constants (not reported)' => [ + 'foo == true || foo == false || foo == null', + 'Tried to evaluate unknown simple token(s): "foo".', + true, + ]; + + yield 'Test known tokens are not reported' => [ + 'foo == 0 or foobar == 1 or bar == 2', + 'Tried to evaluate unknown simple token(s): "foo", "bar".', + true, + ]; + } + /** * @group legacy * @dataProvider parseSimpleTokensLegacyProvider @@ -345,6 +479,18 @@ public function parseSimpleTokensLegacyProvider(): \Generator 'This is my ', ]; + yield 'Test unknown token is treated as null (match)' => [ + 'This is my {if foo===null}match{endif}', + ['val&#ue' => 1], + 'This is my match', + ]; + + yield 'Test unknown token is treated as null (no match)' => [ + 'This is my {if foo!="bar"}match{endif}', + ['val&#ue' => 1], + 'This is my match', + ]; + yield 'Test indexed token replacement' => [ 'This is my ##0##,##1##', ['test@foobar.com', 'foo@test.com'], @@ -515,7 +661,6 @@ public function testFailsIfTheComparisonOperatorIsInvalid(string $string): void public function parseSimpleTokensInvalidComparison(): \Generator { - yield 'PHP constants are not allowed' => ['{if foo==__FILE__}{endif}']; yield 'Not closed string (")' => ['{if foo=="bar}{endif}']; yield 'Not closed string (\')' => ['{if foo==\'bar}{endif}']; yield 'Additional chars after string ("/)' => ['{if foo=="bar"/}{endif}']; From 443098334b2ba868c17383e0a314022174a52da1 Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Wed, 7 Oct 2020 10:17:00 +0200 Subject: [PATCH 55/57] Port the numeric alias changes to the PageUrlListener class (see #2410) Description ----------- Originally added to Contao 4.9 in #2361. The changes could not be merged upstream, hence this PR. Commits ------- 491e4df2 Port the numeric alias changes to the PageUrlListener class b399a429 Also fix the AbstractPageRouteProvider --- .../DataContainer/PageUrlListener.php | 4 ++ .../src/Routing/AbstractPageRouteProvider.php | 4 +- .../DataContainer/PageUrlListenerTest.php | 45 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core-bundle/src/EventListener/DataContainer/PageUrlListener.php b/core-bundle/src/EventListener/DataContainer/PageUrlListener.php index b369674481d..f9550691f14 100644 --- a/core-bundle/src/EventListener/DataContainer/PageUrlListener.php +++ b/core-bundle/src/EventListener/DataContainer/PageUrlListener.php @@ -77,6 +77,10 @@ public function generateAlias(string $value, DataContainer $dc): string $pageModel = $pageAdapter->findWithDetails($dc->id); if ('' !== $value) { + if (preg_match('/^[1-9]\d*$/', $value)) { + throw new \RuntimeException(sprintf($this->translator->trans('ERR.aliasNumeric', [], 'contao_default'))); + } + try { $this->aliasExists($value, (int) $pageModel->id, $pageModel, true); } catch (DuplicateAliasException $exception) { diff --git a/core-bundle/src/Routing/AbstractPageRouteProvider.php b/core-bundle/src/Routing/AbstractPageRouteProvider.php index 6c2296ea77e..3c45d2198fd 100644 --- a/core-bundle/src/Routing/AbstractPageRouteProvider.php +++ b/core-bundle/src/Routing/AbstractPageRouteProvider.php @@ -53,7 +53,7 @@ protected function findCandidatePages(Request $request): array $aliases = []; foreach ($candidates as $candidate) { - if (is_numeric($candidate)) { + if (preg_match('/^[1-9]\d*$/', $candidate)) { $ids[] = (int) $candidate; } else { $aliases[] = $candidate; @@ -96,7 +96,7 @@ protected function getPageIdsFromNames(array $names): array [, $id] = explode('.', $name); - if (!is_numeric($id)) { + if (!preg_match('/^[1-9]\d*$/', $id)) { continue; } diff --git a/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php b/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php index 5dbe2c4b5e0..984dfb7ceb2 100644 --- a/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php +++ b/core-bundle/tests/EventListener/DataContainer/PageUrlListenerTest.php @@ -712,6 +712,51 @@ public function duplicateAliasProvider(): \Generator ]; } + public function testPreventsNumericAliases(): void + { + /** @var MockObject&PageModel $page */ + $page = $this->mockClassWithProperties(PageModel::class, ['id' => 17]); + + $pageAdapter = $this->mockAdapter(['findWithDetails']); + $pageAdapter + ->expects($this->once()) + ->method('findWithDetails') + ->with($page->id) + ->willReturn($page) + ; + + $framework = $this->mockContaoFramework([PageModel::class => $pageAdapter]); + + $slug = $this->createMock(Slug::class); + $slug + ->expects($this->never()) + ->method('generate') + ; + + $translator = $this->createMock(TranslatorInterface::class); + $translator + ->expects($this->once()) + ->method('trans') + ->with('ERR.aliasNumeric') + ->willReturn('Numeric aliases are not supported!') + ; + + /** @var MockObject&DataContainer $dc */ + $dc = $this->mockClassWithProperties(DataContainer::class, ['id' => $page->id]); + + $listener = new PageUrlListener( + $framework, + $slug, + $translator, + $this->mockConnectionWithStatement() + ); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Numeric aliases are not supported!'); + + $listener->generateAlias('123', $dc); + } + public function testPurgesTheSearchIndexOnAliasChange(): void { $statement = $this->createMock(Statement::class); From 74e783619c05dcd2af9933e94e4b5d7a2a2289f0 Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Wed, 7 Oct 2020 10:49:42 +0200 Subject: [PATCH 56/57] Update the changelog and the language files (see #2411) Description ----------- - Commits ------- beb591a9 Update the changelog and the language files --- CHANGELOG.md | 28 +++++++++++++++++++ .../src/Resources/contao/config/constants.php | 2 +- .../Resources/contao/languages/cs/default.xlf | 3 ++ .../Resources/contao/languages/de/default.xlf | 4 +++ .../Resources/contao/languages/es/default.xlf | 3 ++ .../Resources/contao/languages/fa/default.xlf | 3 ++ .../Resources/contao/languages/it/default.xlf | 3 ++ .../Resources/contao/languages/ja/default.xlf | 4 +++ .../Resources/contao/languages/nl/default.xlf | 3 ++ .../Resources/contao/languages/pl/default.xlf | 3 ++ .../Resources/contao/languages/pt/default.xlf | 3 ++ .../Resources/contao/languages/ru/default.xlf | 4 +++ .../Resources/contao/languages/sl/default.xlf | 3 ++ .../Resources/contao/languages/sr/default.xlf | 3 ++ .../Resources/contao/languages/zh/default.xlf | 3 ++ 15 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9520f245d..eed14c88e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ This project adheres to [Semantic Versioning]. +## [4.10.3] (2020-10-07) + +**New features:** + +- [#2379] Add the image_container class to the default figure template ([m-vo]) + +**Fixed issues:** + +- [#2410] Port the numeric alias changes to the PageUrlListener class ([leofeyer]) +- [#2341] Make the SimpleTokenParser backwards compatible ([m-vo]) +- [#2397] Check allowed page types ([aschempp]) +- [#2337] Fix the asset URL handling in the image studio ([m-vo]) +- [#2265] Purge the search tables directly ([aschempp]) +- [#2372] Remove existing tags from annotations that could be arrays ([aschempp]) +- [#2385] Check submitInput() again after validation ([leofeyer]) +- [#2365] Fix non-normalized file extension comparisons when building lightboxes ([m-vo]) + ## [4.10.2] (2020-09-25) ## [4.10.1] (2020-09-24) @@ -156,6 +173,8 @@ This project adheres to [Semantic Versioning]. - [#1458] Always set host and language when generating the navigation menu ([aschempp]) [Semantic Versioning]: https://semver.org/spec/v2.0.0.html +[4.10.3]: https://github.com/contao/contao/releases/tag/4.10.3 +[4.10.2]: https://github.com/contao/contao/releases/tag/4.10.2 [4.10.1]: https://github.com/contao/contao/releases/tag/4.10.1 [4.10.0]: https://github.com/contao/contao/releases/tag/4.10.0 [4.10.0-RC4]: https://github.com/contao/contao/releases/tag/4.10.0-RC4 @@ -173,6 +192,15 @@ This project adheres to [Semantic Versioning]. [m-vo]: https://github.com/m-vo [richardhj]: https://github.com/richardhj [Toflar]: https://github.com/Toflar +[#2379]: https://github.com/contao/contao/pull/2379 +[#2410]: https://github.com/contao/contao/pull/2410 +[#2341]: https://github.com/contao/contao/pull/2341 +[#2397]: https://github.com/contao/contao/pull/2397 +[#2337]: https://github.com/contao/contao/pull/2337 +[#2265]: https://github.com/contao/contao/pull/2265 +[#2372]: https://github.com/contao/contao/pull/2372 +[#2385]: https://github.com/contao/contao/pull/2385 +[#2365]: https://github.com/contao/contao/pull/2365 [#2328]: https://github.com/contao/contao/pull/2328 [#2323]: https://github.com/contao/contao/pull/2323 [#2311]: https://github.com/contao/contao/pull/2311 diff --git a/core-bundle/src/Resources/contao/config/constants.php b/core-bundle/src/Resources/contao/config/constants.php index bd2d86ac510..a0830f2f224 100644 --- a/core-bundle/src/Resources/contao/config/constants.php +++ b/core-bundle/src/Resources/contao/config/constants.php @@ -10,7 +10,7 @@ // Core version define('VERSION', '4.10'); -define('BUILD', '2'); +define('BUILD', '3'); define('LONG_TERM_SUPPORT', false); // Link constants diff --git a/core-bundle/src/Resources/contao/languages/cs/default.xlf b/core-bundle/src/Resources/contao/languages/cs/default.xlf index 11f143d381c..5df8f0cea20 100644 --- a/core-bundle/src/Resources/contao/languages/cs/default.xlf +++ b/core-bundle/src/Resources/contao/languages/cs/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Zástupce stránky "%s" již existuje! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! diff --git a/core-bundle/src/Resources/contao/languages/de/default.xlf b/core-bundle/src/Resources/contao/languages/de/default.xlf index 973d4112e5f..35041534440 100644 --- a/core-bundle/src/Resources/contao/languages/de/default.xlf +++ b/core-bundle/src/Resources/contao/languages/de/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! Der Alias "%s" existiert bereits! + + Numeric aliases are not supported! + Numerische Aliase werden nicht unterstützt! + The URL prefix "%s" is already used by another root page with the same domain name! Das URL-Präfix "%s" wird bereits von einer anderen Root-Seite mit demselben Domainnamen verwendet! diff --git a/core-bundle/src/Resources/contao/languages/es/default.xlf b/core-bundle/src/Resources/contao/languages/es/default.xlf index a35d9c5a7a3..1402f8a34ae 100644 --- a/core-bundle/src/Resources/contao/languages/es/default.xlf +++ b/core-bundle/src/Resources/contao/languages/es/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! ¡El alias "%s" ya existe! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! ¡El prefijo de URL "%s" ya lo utiliza otra página raíz con el mismo nombre de dominio! diff --git a/core-bundle/src/Resources/contao/languages/fa/default.xlf b/core-bundle/src/Resources/contao/languages/fa/default.xlf index 3305276b321..121817a672c 100644 --- a/core-bundle/src/Resources/contao/languages/fa/default.xlf +++ b/core-bundle/src/Resources/contao/languages/fa/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! نام مستعار %s وجود دارد! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! پیشوند URL "%s" قبلا توسط یک برگه ریشه دیگر با همین نام دامنه استفاده شده است! diff --git a/core-bundle/src/Resources/contao/languages/it/default.xlf b/core-bundle/src/Resources/contao/languages/it/default.xlf index 413811ce2e9..0b7b6e662d5 100644 --- a/core-bundle/src/Resources/contao/languages/it/default.xlf +++ b/core-bundle/src/Resources/contao/languages/it/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! L'alias di pagina "%s" già esistente! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! Il prefisso URL "%s" è già utilizzato da un'altra pagina principale con lo stesso nome di dominio! diff --git a/core-bundle/src/Resources/contao/languages/ja/default.xlf b/core-bundle/src/Resources/contao/languages/ja/default.xlf index e0032e225f0..3b68af790fd 100644 --- a/core-bundle/src/Resources/contao/languages/ja/default.xlf +++ b/core-bundle/src/Resources/contao/languages/ja/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! "%s"というエイリアスは既に存在します。 + + Numeric aliases are not supported! + 数字だけのエイリアスはサポートしていません! + The URL prefix "%s" is already used by another root page with the same domain name! "%s"というURLプリフィックスは既に同じドメイン名の別のルートページで使用されています! diff --git a/core-bundle/src/Resources/contao/languages/nl/default.xlf b/core-bundle/src/Resources/contao/languages/nl/default.xlf index 0b6d9b6291d..fe45d06b76d 100644 --- a/core-bundle/src/Resources/contao/languages/nl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/nl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Pagina alias "%s" bestaat reeds! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! diff --git a/core-bundle/src/Resources/contao/languages/pl/default.xlf b/core-bundle/src/Resources/contao/languages/pl/default.xlf index 6c7b007d0ad..a3e74896107 100644 --- a/core-bundle/src/Resources/contao/languages/pl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/pl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Alias strony "%s" już istnieje! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! Prefiks URL "%s" jest już używany przez inny serwis z tą samą domeną! diff --git a/core-bundle/src/Resources/contao/languages/pt/default.xlf b/core-bundle/src/Resources/contao/languages/pt/default.xlf index a3d53f75dab..120058b2e45 100644 --- a/core-bundle/src/Resources/contao/languages/pt/default.xlf +++ b/core-bundle/src/Resources/contao/languages/pt/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Apelido de página %s já existente! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! diff --git a/core-bundle/src/Resources/contao/languages/ru/default.xlf b/core-bundle/src/Resources/contao/languages/ru/default.xlf index 3c9bd7bc213..11adba1b2f4 100644 --- a/core-bundle/src/Resources/contao/languages/ru/default.xlf +++ b/core-bundle/src/Resources/contao/languages/ru/default.xlf @@ -185,6 +185,10 @@ The alias "%s" already exists! Алиас страницы "%s" уже существует! + + Numeric aliases are not supported! + Цифровые алиасы не поддерживаются! + The URL prefix "%s" is already used by another root page with the same domain name! Префикс URL "%s" уже используется другой корневой страницей с тем же именем домена! diff --git a/core-bundle/src/Resources/contao/languages/sl/default.xlf b/core-bundle/src/Resources/contao/languages/sl/default.xlf index dd09f9cff2c..ed2582ebb9c 100644 --- a/core-bundle/src/Resources/contao/languages/sl/default.xlf +++ b/core-bundle/src/Resources/contao/languages/sl/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Vzdevek "%s" že obstaja! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! diff --git a/core-bundle/src/Resources/contao/languages/sr/default.xlf b/core-bundle/src/Resources/contao/languages/sr/default.xlf index 5fb4d22f20f..9ac74d1bf66 100644 --- a/core-bundle/src/Resources/contao/languages/sr/default.xlf +++ b/core-bundle/src/Resources/contao/languages/sr/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! Алиас "%s" већ постоји! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! diff --git a/core-bundle/src/Resources/contao/languages/zh/default.xlf b/core-bundle/src/Resources/contao/languages/zh/default.xlf index b807c2c9e06..0339be5c625 100644 --- a/core-bundle/src/Resources/contao/languages/zh/default.xlf +++ b/core-bundle/src/Resources/contao/languages/zh/default.xlf @@ -185,6 +185,9 @@ The alias "%s" already exists! 页面别名“%s”已经存在! + + Numeric aliases are not supported! + The URL prefix "%s" is already used by another root page with the same domain name! From 96b9d4dea12d2edff6ccf161711f236caa22f146 Mon Sep 17 00:00:00 2001 From: Leo Feyer Date: Fri, 9 Oct 2020 16:33:03 +0200 Subject: [PATCH 57/57] Remove the "add language" menu from the meta wizard (see #2404) Description ----------- Now that available languages are added to the meta wizard by default, we no longer need the "add language" menu (see #2329). Commits ------- 39880bfe Remove the "add language" menu from the meta wizard 718b4aba Adjust the contao.locales info text 1f43f131 Re-add the delete button --- .../src/DependencyInjection/Configuration.php | 2 +- .../Resources/contao/languages/en/default.xlf | 6 -- .../contao/languages/en/tl_files.xlf | 2 +- .../Resources/contao/themes/flexible/main.css | 24 +++---- .../contao/themes/flexible/main.min.css | 2 +- .../Resources/contao/widgets/MetaWizard.php | 13 ---- core-bundle/src/Resources/public/core.js | 65 +------------------ core-bundle/src/Resources/public/core.min.js | 2 +- 8 files changed, 13 insertions(+), 103 deletions(-) diff --git a/core-bundle/src/DependencyInjection/Configuration.php b/core-bundle/src/DependencyInjection/Configuration.php index d78d06eaa2b..eaf9f49efff 100644 --- a/core-bundle/src/DependencyInjection/Configuration.php +++ b/core-bundle/src/DependencyInjection/Configuration.php @@ -71,7 +71,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('Allows to set TL_CONFIG variables, overriding settings stored in localconfig.php. Changes in the Contao back end will not have any effect.') ->end() ->arrayNode('locales') - ->info('Allows to configure which languages can be used within Contao. Defaults to all languages for which a translation exists.') + ->info('Allows to configure which languages can be used in the Contao back end. Defaults to all languages for which a translation exists.') ->prototype('scalar')->end() ->defaultValue($this->getLocales()) ->end() diff --git a/core-bundle/src/Resources/contao/languages/en/default.xlf b/core-bundle/src/Resources/contao/languages/en/default.xlf index 9e6d641c214..3eb601ff2b3 100644 --- a/core-bundle/src/Resources/contao/languages/en/default.xlf +++ b/core-bundle/src/Resources/contao/languages/en/default.xlf @@ -959,12 +959,6 @@ Caption - - Delete the language - - - Add language - Title diff --git a/core-bundle/src/Resources/contao/languages/en/tl_files.xlf b/core-bundle/src/Resources/contao/languages/en/tl_files.xlf index 9ff32d1ca7b..03b5240d97e 100644 --- a/core-bundle/src/Resources/contao/languages/en/tl_files.xlf +++ b/core-bundle/src/Resources/contao/languages/en/tl_files.xlf @@ -60,7 +60,7 @@ Metadata - Here you can enter the file metadata. + Here you can enter the file metadata. To add a language, create a matching root page in the site structure. File upload diff --git a/core-bundle/src/Resources/contao/themes/flexible/main.css b/core-bundle/src/Resources/contao/themes/flexible/main.css index e2be178b52f..fc056e95c0b 100644 --- a/core-bundle/src/Resources/contao/themes/flexible/main.css +++ b/core-bundle/src/Resources/contao/themes/flexible/main.css @@ -1533,9 +1533,6 @@ ul.sgallery li { .tl_metawizard li.odd { background:#f9f9fb; } -.tl_metawizard span img { - cursor:pointer; -} .tl_metawizard label { float:left; width:18%; @@ -1554,26 +1551,21 @@ ul.sgallery li { } .tl_metawizard .lang { display:block; - margin-bottom:9px; + margin:3px 0 9px; font-weight:600; position:relative; } -@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi) { - .tl_metawizard .lang { - font-weight:500; - } -} -.tl_metawizard_img { +.tl_metawizard .lang img { position:absolute; right:0; top:-1px; + cursor:pointer; } -.tl_metawizard_new .tl_select { - margin-right:4px; - max-width:200px; -} -.tl_metawizard_new .tl_submit { - line-height:1; + +@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi) { + .tl_metawizard .lang { + font-weight:500; + } } /* Section wizard */ diff --git a/core-bundle/src/Resources/contao/themes/flexible/main.min.css b/core-bundle/src/Resources/contao/themes/flexible/main.min.css index 9c6969081aa..2d8ea4b55b9 100644 --- a/core-bundle/src/Resources/contao/themes/flexible/main.min.css +++ b/core-bundle/src/Resources/contao/themes/flexible/main.min.css @@ -1 +1 @@ -body{background:#cfcfd3;overflow-y:scroll}body.popup{background:#fff}#header{min-height:40px;text-align:left}#header .inner{background:#f47c00}body:not(.fullscreen) #header .inner{max-width:1440px;margin:0 auto}#header,#header a{color:#fff}#header h1{position:absolute}#header h1 a{display:block;padding:12px 12px 12px 43px;background:url(icons/logo.svg) no-repeat 10px center;font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#header h1 a{font-weight:300}}#tmenu{display:flex;justify-content:flex-end}#tmenu li{position:relative}#tmenu a,#tmenu .h2{padding:13px 12px;display:inline-block}#tmenu img{vertical-align:top}#tmenu sup{position:absolute;top:5px;left:20px;z-index:1;font-size:.6rem;background:#fff;padding:2px;border-radius:2px;text-indent:0;color:#f47c00;font-weight:400}#tmenu .burger{display:none}#tmenu .burger button{margin-right:-2px;padding:8px 10px 9px;background:0 0;border:0;vertical-align:top}#tmenu .burger svg{margin-bottom:-1px;vertical-align:middle}#tmenu .h2{padding-right:26px;cursor:pointer;background:url(icons/chevron-down.svg) right 9px top 14px no-repeat;font-size:.875rem;font-weight:400;color:#fff}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tmenu .h2{font-weight:300}}#tmenu a,#tmenu .h2,#tmenu .burger button{transition:background-color .3s ease}#tmenu a:hover,#tmenu a.hover,#tmenu li:hover .h2,#tmenu .active .h2,#tmenu .burger button:hover{background-color:#e87600}#tmenu ul.menu_level_1{min-width:150px;position:absolute;right:6px;margin-top:5px;padding:6px 0 9px;background:#fff;box-shadow:0 0 3px #999;z-index:1;color:#444;text-align:left;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}#tmenu .active ul.menu_level_1{opacity:1;visibility:visible}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tmenu ul.menu_level_1{color:#222}}#tmenu ul.menu_level_1 li a{display:block;color:inherit;padding:6px 20px 6px 40px;white-space:nowrap}#tmenu ul.menu_level_1 li a:hover{background-color:#eee}#tmenu ul.menu_level_1 .info{color:gray;padding:9px 20px 15px;border-bottom:1px solid #e6e6e8;line-height:1.4;margin-bottom:9px;white-space:nowrap}#tmenu ul.menu_level_1 strong{color:#222;display:block}#tmenu ul.menu_level_1:before{content:"";display:block;width:0;height:0;position:absolute;right:9px;top:-14px;border:7px solid transparent;border-bottom-color:#fff}#tmenu ul.menu_level_1:after{content:"";display:block;width:100%;height:5px;position:absolute;top:-5px}#tmenu .icon-alert{width:16px;margin-bottom:-2px;position:relative;background:url(icons/alert.svg) center center no-repeat;overflow:hidden;white-space:nowrap;text-indent:28px}#tmenu .icon-profile{background:url(icons/profile_dark.svg) 20px center no-repeat}#tmenu .icon-security{background:url(icons/shield_dark.svg) 20px center no-repeat}#tmenu .icon-logout{background:url(icons/exit_dark.svg) 20px center no-repeat}#container{display:flex;min-height:calc(100vh - 40px);background:#cfcfd3}body:not(.fullscreen) #container{max-width:1440px;margin:0 auto}.popup #container{padding:0;width:auto;min-height:0;background:#fff;max-width:none}#left{float:left;width:225px;min-height:calc(100vh - 40px);background:#0f1c26;display:flex;flex-direction:column}#left .version{margin-top:4em;padding:15px;border-top:1px solid #3a454d;font-size:.75rem;line-height:1.4;background:#172b3b}#left .version,#left .version a{color:#9fa4a8}#main{float:left;width:calc(100% - 225px);min-height:calc(100vh - 40px);background:#eaeaec}.popup #main{float:none;width:auto;max-width:none;min-height:0;margin:0;padding:0;border:0;display:initial}#main .content{margin:15px;background:#fff;border:1px solid #d0d0d2}.popup #main .content{margin:0;border:0}#tl_navigation{flex-grow:1}#tl_navigation .menu_level_0{padding-top:18px}#tl_navigation .menu_level_0>li:after{content:"";width:calc(100% - 30px);height:1px;background:#3a454d;display:block;margin:15px auto}#tl_navigation .menu_level_0>li.last:after{display:none}#tl_navigation .menu_level_0>li>a{display:block;margin:0 15px;padding:3px 3px 3px 24px;color:#9fa4a8;font-size:.75rem;text-transform:uppercase}#tl_navigation .group-content{background:url(icons/content.svg) 3px 2px no-repeat}#tl_navigation .group-design{background:url(icons/monitor.svg) 3px 2px no-repeat}#tl_navigation .group-accounts{background:url(icons/person.svg) 3px 2px no-repeat}#tl_navigation .group-system{background:url(icons/wrench.svg) 3px 2px no-repeat}#tl_navigation .menu_level_1{padding:6px 0 0}#tl_navigation .menu_level_1 li{border-left:4px solid #0f1c26}#tl_navigation .menu_level_1 li.current{background-color:#172b3b;border-left-color:#f47c00}#tl_navigation .menu_level_1 a{display:block;padding:6px 18px 6px 35px;font-weight:400;color:#d3d5d7;transition:color .2s ease}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tl_navigation .menu_level_1 a{font-weight:300}}#tl_navigation .tl_level_2 li:hover a{color:#fff}#tl_navigation .collapsed .menu_level_1{display:none}#tl_buttons{margin:0;padding:12px 15px;text-align:right}.toggleWrap{cursor:pointer}.opacity{-moz-opacity:.8;opacity:.8}#main_headline{margin:20px 0;padding:0 16px;font-size:1.1rem}.popup #main_headline{display:none}#main_headline span:nth-child(even){font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#main_headline span:nth-child(even){font-weight:300}}h2.sub_headline{margin:3px 18px;padding:7px 0}.tl_gerror{margin:12px;padding:3px 0 3px 22px;background:url(icons/error.svg) no-repeat left center}.tl_error,.tl_confirm,.tl_info,.tl_new{margin:0 0 1px;padding:11px 18px 11px 37px;line-height:1.3}.tl_error{background:#faebeb url(icons/error.svg) no-repeat 16px 12px}.tl_confirm{background:#eefcde url(icons/ok.svg) no-repeat 16px 12px}.tl_info{background:#e9f0f7 url(icons/show.svg) no-repeat 16px 12px}.tl_new{background:#fbf2e5 url(icons/featured.svg) no-repeat 16px 12px}.tl_gerror,.tl_gerror a,.tl_error,.tl_error a{color:#c33}.tl_confirm,.tl_confirm a{color:#589b0e}.tl_info,.tl_info a{color:#006494}.tl_new,.tl_new a{color:#d68c23}.tl_panel,.tl_version_panel{padding:8px 10px 8px 0;background:#f3f3f5;border-bottom:1px solid #ddd;text-align:right}.tl_version_panel .tl_select{max-width:280px}.tl_version_panel .tl_formbody{position:relative}.tl_img_submit{width:16px;height:16px;border:0;margin:0;padding:0;text-indent:16px;white-space:nowrap;overflow:hidden;position:relative;top:9px;vertical-align:top;cursor:pointer}.filter_apply{background:url(icons/filter-apply.svg) center center no-repeat}.filter_reset{background:url(icons/filter-reset.svg) center center no-repeat}.tl_subpanel{float:right;letter-spacing:-.31em}.tl_subpanel *{letter-spacing:normal}.tl_subpanel strong,.tl_search span{vertical-align:middle}.tl_submit_panel{min-width:32px;padding-left:6px;padding-right:3px}.tl_panel .active,.tl_panel_bottom .active,#search .active{background-color:#fffce1}.tl_filter{width:100%}.tl_filter .tl_select{max-width:14.65%;margin-left:3px}.tl_submit_panel+.tl_filter{width:86%}.tl_limit{width:22%}.tl_limit .tl_select{width:52%;margin-left:3px}.tl_search{width:40%}.tl_search .tl_select{width:38%;margin-left:3px;margin-right:1%}.tl_search .tl_text{width:30%;margin-left:1%;-webkit-appearance:textfield;box-sizing:content-box}.tl_sorting{width:26%}.tl_sorting .tl_select{width:60%;margin-left:1%}.tl_xpl{padding:0 18px}.tl_tbox,.tl_box{padding:12px 0 25px;border-bottom:1px solid #e6e6e8}.tl_tbox:last-child,.tl_box:last-child{border-bottom:0}.tl_box h3,.tl_tbox h3,.tl_xpl h3{margin:0;padding-top:13px;height:16px;font-size:.875rem}.tl_box h4,.tl_tbox h4{margin:6px 0 0;padding:0;font-size:.875rem}.tl_tbox.theme_import{padding-left:15px;padding-right:15px}.tl_tbox.theme_import h3,.tl_tbox.theme_import h4,.tl_tbox.theme_import p{line-height:1.3}.tl_help,.tl_help *{font-size:.75rem}.tl_help,.tl_help a{margin-bottom:0;line-height:1.2;color:gray}.tl_help a:hover,.tl_help a:focus,.tl_help a:active{text-decoration:underline}.tl_formbody_edit.nogrid .w50{float:none}.tl_formbody_edit.nogrid .m12{margin-top:0;margin-bottom:0}.tl_edit_form .tl_formbody_edit{border-top:1px solid #e6e6e8}.tl_formbody_submit{border-top:1px solid #ddd}.tl_submit_container{padding:8px 15px;background:#f3f3f5}.tl_submit_container .tl_submit{margin-top:2px;margin-bottom:2px}.maintenance_active{padding-top:12px}.maintenance_active,.maintenance_inactive{border-top:1px solid #e6e6e8}.maintenance_inactive:last-child{padding-bottom:9px}.maintenance_inactive .tl_tbox{border:0!important;padding:6px 15px 14px}.maintenance_inactive .tl_message{margin-top:0}.maintenance_inactive h2.sub_headline{margin:18px 15px 3px}.maintenance_inactive .tl_submit_container{background:0 0;padding:0 15px 24px;border:0}.maintenance_inactive:last-of-type .tl_submit_container{padding-bottom:10px}#tl_maintenance_mode .tl_message{margin-bottom:12px}#tl_maintenance_mode .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}#tl_maintenance_index .tl_tbox{margin-top:-12px}@keyframes crawl-progress-bar-stripes{0%{background-position-x:1rem}}#tl_crawl .tl_message{margin-bottom:24px}#tl_crawl .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}#tl_crawl .tl_tbox{padding-top:0}#tl_crawl .tl_tbox>div{max-width:562px}#tl_crawl .tl_checkbox_container{margin-top:9px}#tl_crawl .inner{position:relative;margin:0 18px 18px}#tl_crawl .progress{display:flex;height:20px;overflow:hidden;background-color:#f0f0f2;margin-right:20px;border-radius:2px}#tl_crawl .progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-size:10px 10px}#tl_crawl .progress-bar.running{background-color:#f47c00;background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);animation:crawl-progress-bar-stripes 1s linear infinite}#tl_crawl .progress-bar.finished{background-color:#589b0e}#tl_crawl .progress-count{margin:6px 0 24px}#tl_crawl .results h3{font-size:.9rem;margin:18px 0 9px}#tl_crawl .results p{margin-bottom:6px}#tl_crawl .results .spinner{margin:0;padding:10px;background:url(icons/loading.svg) no-repeat left center;background-size:16px}#tl_crawl .subscriber-log{display:none;padding:5px 0;margin-bottom:0}#tl_crawl .wait{margin-top:18px}#tl_crawl .debug-log{position:absolute;top:2px;right:0}#tl_crawl .results.running .show-when-finished,#tl_crawl .results.finished .show-when-running{display:none}#tl_crawl .results.running .show-when-running,#tl_crawl .results.finished .show-when-finished{display:block}#tl_crawl .result .summary.success{color:#589b0e}#tl_crawl .result .summary.failure{color:#c33}#tl_crawl .result .warning{display:none;color:#006494}.two-factor{border-top:1px solid #e6e6e8;padding-bottom:9px}.two-factor h2.sub_headline{margin:18px 15px 3px}.two-factor>p{margin:0 15px 12px;line-height:1.3}.two-factor li{margin-left:2em;list-style:initial}.two-factor .qr-code{margin:0 15px}.two-factor .tl_listing_container{margin-top:0}.two-factor .widget{height:auto;margin:15px 15px 12px}.two-factor .widget .tl_error{margin:0;padding:1px 0;background:0 0;font-size:.75rem;line-height:1.25}.two-factor .tl_submit_container{background:0 0;padding:0 15px 10px;border:0}.two-factor .submit_container{clear:both;margin:0 15px 12px}.two-factor .tl_message{margin-bottom:12px}.two-factor .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}.two-factor .tl_backup_codes>p,.two-factor .tl_trusted_devices>p{margin:0 15px 12px;line-height:1.3}.two-factor .backup-codes{max-width:224px;margin:15px 15px 24px;display:grid;grid-template-columns:repeat(2,1fr)}.two-factor .backup-codes li{margin:0;list-style:none}#search{margin:18px 18px -9px;text-align:right}#search .tl_text{max-width:160px;-webkit-appearance:textfield;box-sizing:content-box}.tl_edit_preview{margin-top:18px}.tl_edit_preview img{max-width:100%;height:auto;padding:2px;border:1px solid #ddd;background:#fff}.tl_edit_preview_enabled{position:relative;cursor:crosshair;display:inline-block}.tl_edit_preview_important_part{position:absolute;margin:-1px;border:1px solid #000;box-shadow:0 0 0 1px #fff,inset 0 0 0 1px #fff;opacity:.5}#tl_rebuild_index{padding:0 18px 18px;line-height:1.3}#index_note{margin:24px 0 18px;padding:11px 12px 12px;background:#ffc;border:1px solid #f90;font-size:.875rem}#index_loading{margin:0;padding:12px 12px 12px 24px;background:url(icons/loading.svg) no-repeat left center;background-size:16px;color:#006494}#index_complete{margin:0;padding:12px 12px 12px 24px;background:url(icons/ok.svg) no-repeat left center;color:#589b0e}table.tl_listing{width:100%}.tl_listing_container{padding:2px 0;margin:20px 15px 16px}.tl_listing_container.tree_view{position:relative}#tl_buttons+.tl_listing_container{margin-top:10px}#paste_hint+.tl_listing_container{margin-top:30px}.tl_folder_list,.tl_folder_tlist{padding:7px 0;border-bottom:1px solid #e9e9e9;background:#f3f3f5;font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_folder_list,.tl_folder_tlist{font-weight:500}}.tl_folder_tlist{border-top:1px solid #e9e9e9}.tl_file,.tl_file_list{padding:5px 0;border-bottom:1px solid #e9e9e9;background:#fff}.tl_file_list{padding:7px 0}.tl_file_list .ellipsis{height:16px;text-overflow:ellipsis;overflow:hidden;padding-right:18px;word-break:break-all}.tl_right_nowrap{padding:3px 0;text-align:right;white-space:nowrap}.tl_listing.picker .tl_file,.tl_listing.picker .tl_folder,.tl_listing.picker .tl_right_nowrap,.tl_listing_container.picker .tl_content_header,.tl_listing_container.picker .tl_content{background-image:linear-gradient(90deg,transparent calc(100% - 26px),#f0f0f2 26px)}.tl_listing.picker .tl_tree_checkbox,.tl_listing.picker .tl_tree_radio,.tl_listing_container.picker .tl_tree_checkbox,.tl_listing_container.picker .tl_tree_radio{margin-top:2px;margin-left:8px}.tl_listing.picker .tl_tree_checkbox:disabled,.tl_listing.picker .tl_tree_radio:disabled,.tl_listing_container.picker .tl_tree_checkbox:disabled,.tl_listing_container.picker .tl_tree_radio:disabled{visibility:hidden}.tl_listing_container.picker div[class^=ce_]{padding-right:24px}.tl_listing_container.picker .limit_toggler{width:calc(100% - 26px)}.tl_listing tr.odd td{background-color:#fafafc}.tl_listing th,.tl_listing td{padding-left:6px!important;padding-right:6px!important}.list_view .tl_listing img.theme_preview{margin-right:9px}.tl_show{width:96%;margin:18px 2%;padding:9px 0 18px}.tl_show td{padding:4px 6px;line-height:16px;white-space:pre-line;background:#f6f6f8}.tl_show td:first-child{width:34%;white-space:normal}.tl_show td p:last-of-type{margin-bottom:0}.tl_show tr:nth-child(2n) td{background:#fff}.tl_show small{display:block;color:gray}.tl_label{margin-right:12px;font-weight:600;white-space:nowrap}.tl_label small{font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_label small{font-weight:300}}.tl_empty{margin:0;padding:18px}.tl_empty_parent_view{margin:0;padding:18px 0 0}.tl_listing_container+.tl_empty{margin-top:-32px}.tl_noopt{margin:0 0 -1px}.tl_select_trigger{margin-top:-9px;padding:0 6px 3px 0;text-align:right}.tl_radio_reset{margin-top:6px;padding:0 6px 3px 0;text-align:right}.tl_select_label,.tl_radio_label{margin-right:2px;color:#999;font-size:.75rem}.tl_header{padding:6px;background:#f9f9fb;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.tl_header_table{line-height:1.3}.tl_content_header{padding:7px 6px;border-bottom:1px solid #e9e9e9;background:#f6f6f8;font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_content_header{font-weight:500}}.tl_content{padding:6px;border-bottom:1px solid #e9e9e9;overflow:hidden;position:relative;background-color:#fff}.parent_view>ul{background:#f3f3f5}.tl_content.wrapper_stop{border-top:1px solid #e9e9e9}.tl_content.wrapper_start h1{margin-bottom:9px}.tl_content.indent_1{margin-left:20px}.tl_content.indent_2{margin-left:40px}.tl_content.indent_3{margin-left:60px}.tl_content.indent_4{margin-left:80px}.tl_content.indent_5{margin-left:100px}.tl_content.indent_last{border-bottom:0}.tl_content h1,.tl_content h2,.tl_content h3,.tl_content h4,.tl_content h5,.tl_content h6{font-size:.875rem}.tl_content img{max-width:320px;height:auto}.tl_content pre{margin-top:3px;margin-bottom:3px;word-break:break-all;white-space:pre-wrap}.tl_content pre.disabled{color:#a6a6a6}.tl_content .ce_text a{color:#589b0e}.tl_content span.comment{color:#006494;display:inline-block;margin-bottom:3px}.tl_content_left{line-height:16px}.tl_content_right{float:right;text-align:right;margin-left:12px;margin-top:-1px}.tl_right button,.tl_content_right button{margin:0;padding:0;border:0;height:17px;background:0 0}.cte_type{margin:2px 0 6px;font-size:.75rem;color:gray}.cte_type.published,.cte_type.published a{color:#589b0e}.cte_type.unpublished,.cte_type.unpublished a{color:#c33}.limit_height{overflow:hidden;line-height:1.25}.limit_toggler{width:100%;position:absolute;bottom:0;left:0;background:#fff;line-height:11px;text-align:center}.limit_toggler button{margin:0;padding:0;border:0;background:#fff;width:24px;line-height:8px;color:#999;outline:0;border-top-left-radius:2px;border-top-right-radius:2px}.limit_toggler button span{position:relative;top:-4px}.limit_height input[type=file]{position:relative}.limit_height select{-moz-appearance:menulist;-webkit-appearance:menulist}.limit_height label,.limit_height .checkbox_container legend,.limit_height .radio_container legend{display:inline-block;width:180px;vertical-align:top;margin-top:3px}.limit_height .widget{margin-left:0;margin-right:0}.limit_height .widget-submit{margin-left:180px}.limit_height .checkbox_container label,.limit_height .radio_container label{display:initial}.tl_folder_top{padding:5px 0;border-top:1px solid #e3e3e3;border-bottom:1px solid #e3e3e3;background:#f0f0f2}.tl_folder{padding:5px 0;border-bottom:1px solid #e9e9e9;background:#f6f6f8}.tl_folder.tl_folder_dropping,.tl_folder_top.tl_folder_dropping{background-color:#4078a5!important;color:#fff!important}.tl_folder.tl_folder_dropping a,.tl_folder_top.tl_folder_dropping a{color:inherit}.tl_listing .tl_left{flex-grow:1;margin-left:40px;text-indent:-40px;box-sizing:border-box}.tl_listing .tl_left.tl_left_dragging{position:absolute;background:#4078a5;border-radius:10px;color:#fff;padding:5px 10px!important;margin-left:0;text-indent:0;white-space:nowrap}.tl_listing .tl_left.tl_left_dragging .preview-image,.tl_listing .tl_left.tl_left_dragging a img{display:none}.tl_listing .tl_left.tl_left_dragging a,.tl_listing .tl_left.tl_left_dragging .tl_gray{color:inherit}.tl_listing_dragging .hover-div:not(.tl_folder):hover{background-color:transparent!important}.tl_tree_xtnd .tl_file{padding-top:4px;padding-bottom:4px}.tl_tree_xtnd .tl_file .tl_left{margin-left:22px;text-indent:-22px}.tl_tree_xtnd .tl_file .tl_left *{line-height:normal}.tl_tree_xtnd .tl_file .tl_left a{position:relative;top:-1px}.tl_tree_xtnd .tl_file .tl_left img{margin-right:2px}.tl_left>a:last-of-type{vertical-align:middle}.tl_left>a:first-child{vertical-align:bottom}.tl_file_manager .tl_file .tl_left>a:first-child{vertical-align:1px}.tl_file_manager .preview-image{max-width:100px;max-height:75px;width:auto;height:auto;margin:0 0 2px -18px}.tl_file_manager .preview-important{max-width:80px;max-height:60px;width:auto;height:auto;margin:0 0 2px 0;vertical-align:bottom}.tl_listing .tl_right{padding:0 0 1px 9px;white-space:nowrap}.tl_listing,.tl_listing ul{margin:0;padding:0}.tl_listing li{display:flex;margin:0;padding-left:6px;padding-right:6px;list-style-type:none}.tl_listing li.parent{display:inline;padding-left:0;padding-right:0}label.tl_change_selected{margin-right:2px;color:#999;font-size:.75rem}#tl_breadcrumb{margin:0 0 12px;padding:4px 6px;overflow:hidden;background:#fffce1;border:1px solid #d68c23;line-height:24px}#tl_breadcrumb li{margin:0;padding:0 3px;list-style-type:none;float:left}#tl_breadcrumb li a{display:inline-block}#tl_breadcrumb li img{width:16px;height:16px;vertical-align:-3px}.selector_container{margin-top:1px;position:relative}.selector_container>ul{margin-bottom:1px;list-style-type:none;overflow:hidden}.selector_container>ul>li{margin-right:9px;padding:2px 0}.selector_container p{margin-bottom:1px}.selector_container ul:not(.sgallery) img{margin-right:1px;vertical-align:text-top}.selector_container .limit_height{height:auto!important;max-height:190px}.selector_container .limit_toggler{display:none}.selector_container h1,.selector_container h2,.selector_container h3,.selector_container h4{margin:0;padding:0}.selector_container pre{white-space:pre-wrap}ul.sgallery{overflow:hidden}ul.sgallery li{min-width:100px;min-height:75px;float:left;margin:2px 4px 2px 0;padding:0;background:#eee;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}.popup #tl_soverview{margin-top:15px}#tl_soverview>div{padding:0 15px 4px;border-bottom:1px solid #e6e6e8}#tl_soverview>div:last-child{border-bottom:0}#tl_soverview table{width:100%;margin-bottom:14px}#tl_messages h2,#tl_shortcuts h2,#tl_versions h2{margin:18px 0 6px}#tl_messages p:not([class]){margin-top:6px}#tl_messages .tl_error,#tl_messages .tl_confirm,#tl_messages .tl_info,#tl_messages .tl_new{padding:3px 6px 3px 21px;background-position:left 4px;background-color:transparent}#tl_shortcuts p a{text-decoration:underline}#tl_versions{margin-bottom:0}#tl_versions th{background:#f6f6f8;padding:6px;border-top:1px solid #e9e9e9}#tl_versions th,#tl_versions td{border-bottom:1px solid #e9e9e9}#tl_versions td{padding:4px}#tl_versions td:first-child{white-space:nowrap}#tl_versions td:last-child{width:32px;white-space:nowrap;text-align:right}#tl_versions img.undo{padding-right:2px}#tl_versions .pagination{margin-top:18px;margin-bottom:14px}.tl_chmod{width:100%}.tl_chmod th{height:18px;text-align:center;font-weight:400;background:#f0f0f2}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_chmod th{font-weight:300}}.tl_chmod td{text-align:center;background:#f6f6f8}.tl_chmod th,.tl_chmod td{width:14.2857%;padding:6px;border:1px solid #fff}.tl_modulewizard button,.tl_optionwizard button,.tl_key_value_wizard button,.tl_tablewizard button,.tl_listwizard button,.tl_checkbox_wizard button,.tl_metawizard button,.tl_sectionwizard button{margin:0;padding:0;border:0;background:0 0}.tl_modulewizard{width:100%;max-width:800px;margin-top:2px}.tl_modulewizard td{position:relative;padding:0 3px 0 0}.tl_modulewizard th{font-size:.75rem;font-weight:400;padding:0 6px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_modulewizard th{font-weight:300}}.tl_modulewizard td:last-child{width:1%;white-space:nowrap}.tl_modulewizard img{position:relative;top:1px}.js .tl_modulewizard input.mw_enable{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.tl_modulewizard img.mw_enable{display:none}.js .tl_modulewizard img.mw_enable{display:inline;margin-right:1px}.tl_optionwizard{width:100%;max-width:600px}.tl_key_value_wizard{width:100%;max-width:450px}.tl_optionwizard,.tl_key_value_wizard{margin-top:2px}.tl_optionwizard label,.tl_key_value_wizard label{margin-right:3px}.tl_optionwizard td,.tl_key_value_wizard td{padding:0 3px 0 0}.tl_optionwizard th,.tl_key_value_wizard th{font-size:.75rem;font-weight:400;padding:0 6px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_optionwizard th,.tl_key_value_wizard th{font-weight:300}}.tl_optionwizard td:nth-child(n+3),.tl_key_value_wizard td:nth-child(n+3){width:1%;white-space:nowrap}.tl_optionwizard img,.tl_key_value_wizard img{position:relative;top:1px}.tl_optionwizard .fw_checkbox,.tl_key_value_wizard .fw_checkbox{margin:0 1px}#tl_tablewizard{margin-top:2px;padding-bottom:2px;overflow:auto}.tl_tablewizard td{padding:0 3px 0 0}.tl_tablewizard thead td{padding-bottom:3px;text-align:center;white-space:nowrap}.tl_tablewizard tbody td:last-child{white-space:nowrap}.tl_tablewizard td.tcontainer{vertical-align:top}.tl_listwizard .tl_text{width:78%}.tl_checkbox_wizard .fixed{display:block;margin-top:1px}.tl_checkbox_wizard .sortable span{display:block}.tl_checkbox_wizard .sortable img{vertical-align:bottom}.tl_metawizard{margin:3px 0}.tl_metawizard li{overflow:hidden;margin-bottom:2px;padding:9px;background:#f3f3f5}.tl_metawizard li.odd{background:#f9f9fb}.tl_metawizard span img{cursor:pointer}.tl_metawizard label{float:left;width:18%;margin-top:9px}.tl_metawizard .tl_text{float:left;width:82%;margin:1px 0}.tl_metawizard .tl_text+a{top:6px}.tl_metawizard br{clear:left}.tl_metawizard .lang{display:block;margin-bottom:9px;font-weight:600;position:relative}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_metawizard .lang{font-weight:500}}.tl_metawizard_img{position:absolute;right:0;top:-1px}.tl_metawizard_new .tl_select{margin-right:4px;max-width:200px}.tl_metawizard_new .tl_submit{line-height:1}.tl_sectionwizard{margin-top:2px;width:100%;max-width:680px}.tl_sectionwizard td{width:25%;position:relative;padding:0 3px 0 0}.tl_sectionwizard th{font-size:.75rem;font-weight:400;padding:0 4px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_sectionwizard th{font-weight:300}}.tl_sectionwizard td:last-child{white-space:nowrap}#paste_hint{position:relative;z-index:1}.tl_message+#paste_hint{margin-top:-12px}#paste_hint p{position:absolute;font-family:"Architects Daughter",cursive;font-size:1rem;color:#838990;top:0;right:30px;padding:0 36px 24px 0;background:url(icons/arrow_right.svg) bottom right no-repeat;transform:rotate(-1deg)}.sort_hint{position:absolute;font-family:"Architects Daughter",cursive;font-size:1rem;color:#838990;top:-50px;left:160px;padding:0 6px 24px 42px;background:url(icons/arrow_left.svg) 6px bottom no-repeat;transform:rotate(-2deg)}.widget+.subpal .sort_hint{left:260px}.widget+.widget .sort_hint{left:320px}.serp-preview{max-width:600px;margin:2px 0;padding:4px 6px;font-family:Arial,sans-serif;font-weight:400;color:#3c4043;border:1px dotted #aaa;border-radius:2px}.serp-preview p{margin-bottom:0;line-height:1.3}.serp-preview .url{margin-bottom:4px}.serp-preview .title{margin-bottom:2px;font-size:18px;color:#1a0dab;line-height:1}.serp-preview .description:not(:empty){margin-top:3px}#tl_ajaxBox{width:300px;padding:2em;box-sizing:border-box;position:absolute;left:50%;margin-left:-150px;background:#fff url(icons/loading.svg) no-repeat right 2em center;border:2px solid #111;border-radius:2px;font-size:1rem;text-align:left}#tl_ajaxOverlay{width:100%;height:100%;position:absolute;top:0;left:0;background:#fff;opacity:.5}.ce_gallery ul{overflow:hidden}.ce_gallery li{float:left;margin:0 6px 6px 0}.drag-handle{cursor:move}ul.sortable li{cursor:move;position:relative}ul.sortable li .dirname{display:none}ul.sortable li:hover .dirname{display:inline}ul.sortable button{position:absolute;top:0;right:0;border:0;background:#eee;margin:0;padding:0 0 3px;font-size:22px;line-height:9px;cursor:pointer;transition:all .1s linear}ul.sortable button:hover{background:#f9f9f9}ul.sortable button[disabled]{color:#999;cursor:not-allowed}ul.sortable button[disabled]:hover{background:rgba(255,255,255,.7)}#picker-menu{padding:9px 6px 0;border-bottom:1px solid #ddd}#picker-menu li{display:inline-block;padding:8px 0;background-color:#f9f9fb;border:1px solid #ddd;border-radius:2px 2px 0 0;position:relative;top:1px}#picker-menu li:hover{background-color:#f3f3f5}#picker-menu li.current{background-color:#f3f3f5;border-bottom-color:#f3f3f5}#picker-menu a{padding:3px 12px 3px 32px;background:url(icons/manager.svg) 12px center no-repeat}#picker-menu a:hover{color:#444}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#picker-menu a:hover{color:#222}}#picker-menu a.pagePicker{background-image:url(icons/pagemounts.svg);background-size:16px}#picker-menu a.filePicker{background-image:url(icons/filemounts.svg);background-size:14px}#picker-menu a.articlePicker{background-image:url(icons/articles.svg);background-size:16px}#picker-menu a.close{background-image:url(icons/back.svg)}.ace_editor{padding:3px;z-index:0}.ace_editor,.ace_editor *{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important;font-size:.75rem!important;color:#444}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.ace_editor,.ace_editor *{color:#222}}.ace-fullsize{overflow:hidden!important}.ace-fullsize .ace_editor{position:fixed!important;top:0;right:0;bottom:0;left:0;width:auto!important;height:auto!important;margin:0;border:0;z-index:10000}div.mce-edit-area{width:99.9%}time[title]{cursor:help}.float_left{float:left}.float_right{float:right}#manager{padding:9px 6px 0;border-bottom:1px solid #ddd}#manager a{padding:3px 12px 3px 32px;background:url(icons/manager.svg) 12px center no-repeat}.header_icon,.header_clipboard,.header_back,.header_new,.header_rss,.header_edit_all,.header_delete_all,.header_new_folder,.header_css_import,.header_theme_import,.header_store,.header_toggle,.header_sync{padding:3px 0 3px 21px;background-position:left center;background-repeat:no-repeat;margin-left:15px}.list_icon{margin-left:-3px;padding-left:20px;background-position:left center;background-repeat:no-repeat}.list_icon_new{width:16px;background-position:1px center;background-repeat:no-repeat}.header_clipboard{background-image:url(icons/clipboard.svg)}.header_back{background-image:url(icons/back.svg)}.header_new{background-image:url(icons/new.svg)}.header_rss{background-image:url(icons/rss.svg)}.header_edit_all{background-image:url(icons/all.svg)}.header_delete_all{background-image:url(icons/deleteAll.svg)}.header_new_folder{padding-left:24px;background-image:url(icons/newfolder.svg)}.header_css_import{background-image:url(icons/cssimport.svg)}.header_theme_import{background-image:url(icons/theme_import.svg)}.header_store{padding-left:18px;background-image:url(icons/store.svg)}.header_toggle{background-image:url(icons/folPlus.svg)}.header_sync{background-image:url(icons/sync.svg)}.tl_text_trbl,.tl_imageSize_0,.tl_imageSize_1,#ctrl_playerSize input{background:url(icons/hints.svg) no-repeat right 1px top 2px}#ctrl_playerSize_1,.tl_imageSize_1{background-position:right 1px top -28px!important}.trbl_top{background-position:right 1px top -59px!important}.trbl_right{background-position:right 1px top -89px!important}.trbl_bottom{background-position:right 1px top -119px!important}.trbl_left{background-position:right 1px top -149px!important}#ctrl_shadowsize_top{background-position:right 1px top -179px!important}#ctrl_shadowsize_right{background-position:right 1px top -209px!important}#ctrl_shadowsize_bottom{background-position:right 1px top -238px!important}#ctrl_shadowsize_left{background-position:right 1px top -269px!important}#ctrl_borderradius_top{background-position:left -299px!important}#ctrl_borderradius_right{background-position:right 1px top -329px!important}#ctrl_borderradius_bottom{background-position:right 1px top -352px!important}#ctrl_borderradius_left{background-position:left -382px!important}label.error,legend.error,.tl_checkbox_container.error legend{color:#c33}.tl_tbox .tl_error,.tl_box .tl_error{background:0 0;padding:0;margin-bottom:0;font-size:.75rem}.tl_formbody_edit>.tl_error{margin-top:9px}.broken-image{display:inline-block;padding:12px 12px 12px 30px;background:#faebeb url(icons/error.svg) no-repeat 9px center;color:#c33;text-indent:0}fieldset.tl_tbox,fieldset.tl_box{margin-top:5px;padding-top:0;border-top:none;border-left:0;border-right:0}fieldset.tl_tbox.nolegend,fieldset.tl_box.nolegend{border-top:0}fieldset.tl_tbox>legend,fieldset.tl_box>legend{box-sizing:border-box;color:#6a6a6c;padding:9px 12px 9px 28px;background:url(icons/navcol.svg) 13px 11px no-repeat;cursor:pointer}fieldset.collapsed{margin-bottom:0;padding-bottom:5px}fieldset.collapsed div{display:none!important}fieldset.collapsed>legend{background:url(icons/navexp.svg) 13px 11px no-repeat}#tl_maintenance_cache table{width:100%}#tl_maintenance_cache th,#tl_maintenance_cache td{border-bottom:1px solid #e9e9e9}#tl_maintenance_cache th{background:#f6f6f8;padding:6px;border-top:1px solid #e9e9e9}#tl_maintenance_cache td{padding:6px;line-height:1.2}#tl_maintenance_cache tr:nth-child(even) td{background:#fcfcfe}#tl_maintenance_cache td span{color:#999}#tl_maintenance_cache td:first-child{width:16px}.mac #tl_maintenance_cache td:first-child .tl_checkbox{top:-2px}#tl_maintenance_cache .nw{white-space:nowrap}#tl_maintenance_cache .tl_checkbox_container{margin-top:12px}#tl_maintenance_cache .tl_checkbox_container label{font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tl_maintenance_cache .tl_checkbox_container label{font-weight:500}}.pagination{overflow:hidden;background:#f6f6f8;margin-bottom:18px;border:solid #e9e9e9;border-width:1px 0;padding:12px 15px}.pagination ul{width:60%;float:right;text-align:right;padding-right:3px}.pagination p{width:30%;float:left;margin-bottom:0}.pagination li{display:inline;padding-right:3px}.pagination .active{color:#999}.pagination-lp{margin-left:15px;margin-right:15px}#sync-results{overflow:hidden;margin:18px 15px 0}#sync-results p{margin-bottom:0}#sync-results .left{float:left;padding:2px 0}#sync-results .right{float:right}#result-list{margin:15px}#result-list .tl_error,#result-list .tl_confirm,#result-list .tl_info,#result-list .tl_new{padding:3px 0;background:0 0}.dropzone{margin:2px 0;min-height:auto!important;border:3px dashed #ddd!important;border-radius:2px}.dropzone-filetree{display:none;position:absolute;top:0;left:0;width:100%;height:100%;opacity:.8;z-index:1}.dropzone-filetree-enabled{display:block}.dz-message span{font-size:1.3125rem;color:#ccc}.tox-tinymce{margin:3px 0}.tox .tox-menubar,.tox .tox-toolbar__primary,.tox .tox-statusbar{background-color:#f7f7f9!important}@media (max-width:991px){body{background:#eaeaec}#header{width:100%;padding:0;position:fixed;top:0;z-index:2;transition:transform .2s ease;-webkit-transform:none;transform:none;will-change:transform}#container{display:block;padding-top:40px;overflow:hidden}#main,#left{float:none;overflow:hidden}#main{width:100%!important;position:relative;transition:transform .2s ease;-webkit-transform:none;transform:none;will-change:transform}.show-navigation #main{-webkit-transform:translateX(240px);transform:translateX(240px)}#left{position:absolute;top:40px;width:240px;transition:transform .2s ease;-webkit-transform:translateX(-240px);transform:translateX(-240px);will-change:transform}.show-navigation #left{-webkit-transform:none;transform:none}#tmenu .burger{display:inline}}@media (max-width:767px){#header.down{-webkit-transform:translateY(-40px);transform:translateY(-40px)}#header h1 a{width:22px;padding:12px;text-indent:34px;overflow:hidden}#tmenu>li>a{width:16px;margin-bottom:-2px;position:relative;overflow:hidden;white-space:nowrap;text-indent:28px;background-size:18px!important}#tmenu sup{top:6px;font-size:.5rem}#tmenu .icon-debug{background:url(icons/debug.svg) center center no-repeat}#tmenu .icon-preview{background:url(icons/preview.svg) center center no-repeat}#tmenu .h2{width:16px;margin:0 0 -2px;padding-right:12px;overflow:hidden;white-space:nowrap;text-indent:28px;background:url(icons/profile.svg) center center no-repeat;background-size:18px}#main .content{margin:15px 10px}#main_headline{margin:15px 0;padding:0 11px}div.tl_tbox,div.tl_box{position:relative}.tl_content_left{width:100%;float:none}.showColumns th,.showColumns td{display:block}.showColumns th:empty{display:none}.tl_label{white-space:normal}.list_view .tl_listing img.theme_preview{display:none}.tl_filter{box-sizing:border-box;padding:0 3px 0 7px}.tl_filter strong{display:none}.tl_filter .tl_select{display:block;max-width:100%}.tl_search{width:76%;max-width:283px}.tl_search .tl_select{width:36%}.tl_search .tl_text{width:26%}.tl_sorting{width:60%;max-width:212px}.tl_limit{width:50%;max-width:177px}.tl_submit_panel{float:right;z-index:1}input.tl_submit{margin-top:3px;margin-bottom:3px;padding-left:6px!important;padding-right:7px!important}.tl_listing .tl_left,.tl_show td{word-break:break-word}#tl_breadcrumb li{padding:3px}#tl_versions{display:none}.tl_version_panel .tl_select{width:44%}.tl_modulewizard td:first-child{width:1%}.tl_modulewizard td:first-child .tl_select{max-width:52vw}#paste_hint,.sort_hint{display:none}#tl_maintenance_cache table{width:100%}#tl_maintenance_cache tr th:last-child,#tl_maintenance_cache tr td:last-child{display:none}.tl_file_list .ellipsis{padding-right:10px}}@media (max-width:599px){.tl_metawizard label{width:auto;float:none;font-size:.9em;display:block;margin-top:3px}.tl_metawizard .tl_text{width:100%}}@media (max-width:479px){.tl_modulewizard td:first-child .tl_select{max-width:48vw}} \ No newline at end of file +body{background:#cfcfd3;overflow-y:scroll}body.popup{background:#fff}#header{min-height:40px;text-align:left}#header .inner{background:#f47c00}body:not(.fullscreen) #header .inner{max-width:1440px;margin:0 auto}#header,#header a{color:#fff}#header h1{position:absolute}#header h1 a{display:block;padding:12px 12px 12px 43px;background:url(icons/logo.svg) no-repeat 10px center;font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#header h1 a{font-weight:300}}#tmenu{display:flex;justify-content:flex-end}#tmenu li{position:relative}#tmenu a,#tmenu .h2{padding:13px 12px;display:inline-block}#tmenu img{vertical-align:top}#tmenu sup{position:absolute;top:5px;left:20px;z-index:1;font-size:.6rem;background:#fff;padding:2px;border-radius:2px;text-indent:0;color:#f47c00;font-weight:400}#tmenu .burger{display:none}#tmenu .burger button{margin-right:-2px;padding:8px 10px 9px;background:0 0;border:0;vertical-align:top}#tmenu .burger svg{margin-bottom:-1px;vertical-align:middle}#tmenu .h2{padding-right:26px;cursor:pointer;background:url(icons/chevron-down.svg) right 9px top 14px no-repeat;font-size:.875rem;font-weight:400;color:#fff}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tmenu .h2{font-weight:300}}#tmenu a,#tmenu .h2,#tmenu .burger button{transition:background-color .3s ease}#tmenu a:hover,#tmenu a.hover,#tmenu li:hover .h2,#tmenu .active .h2,#tmenu .burger button:hover{background-color:#e87600}#tmenu ul.menu_level_1{min-width:150px;position:absolute;right:6px;margin-top:5px;padding:6px 0 9px;background:#fff;box-shadow:0 0 3px #999;z-index:1;color:#444;text-align:left;opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}#tmenu .active ul.menu_level_1{opacity:1;visibility:visible}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tmenu ul.menu_level_1{color:#222}}#tmenu ul.menu_level_1 li a{display:block;color:inherit;padding:6px 20px 6px 40px;white-space:nowrap}#tmenu ul.menu_level_1 li a:hover{background-color:#eee}#tmenu ul.menu_level_1 .info{color:gray;padding:9px 20px 15px;border-bottom:1px solid #e6e6e8;line-height:1.4;margin-bottom:9px;white-space:nowrap}#tmenu ul.menu_level_1 strong{color:#222;display:block}#tmenu ul.menu_level_1:before{content:"";display:block;width:0;height:0;position:absolute;right:9px;top:-14px;border:7px solid transparent;border-bottom-color:#fff}#tmenu ul.menu_level_1:after{content:"";display:block;width:100%;height:5px;position:absolute;top:-5px}#tmenu .icon-alert{width:16px;margin-bottom:-2px;position:relative;background:url(icons/alert.svg) center center no-repeat;overflow:hidden;white-space:nowrap;text-indent:28px}#tmenu .icon-profile{background:url(icons/profile_dark.svg) 20px center no-repeat}#tmenu .icon-security{background:url(icons/shield_dark.svg) 20px center no-repeat}#tmenu .icon-logout{background:url(icons/exit_dark.svg) 20px center no-repeat}#container{display:flex;min-height:calc(100vh - 40px);background:#cfcfd3}body:not(.fullscreen) #container{max-width:1440px;margin:0 auto}.popup #container{padding:0;width:auto;min-height:0;background:#fff;max-width:none}#left{float:left;width:225px;min-height:calc(100vh - 40px);background:#0f1c26;display:flex;flex-direction:column}#left .version{margin-top:4em;padding:15px;border-top:1px solid #3a454d;font-size:.75rem;line-height:1.4;background:#172b3b}#left .version,#left .version a{color:#9fa4a8}#main{float:left;width:calc(100% - 225px);min-height:calc(100vh - 40px);background:#eaeaec}.popup #main{float:none;width:auto;max-width:none;min-height:0;margin:0;padding:0;border:0;display:initial}#main .content{margin:15px;background:#fff;border:1px solid #d0d0d2}.popup #main .content{margin:0;border:0}#tl_navigation{flex-grow:1}#tl_navigation .menu_level_0{padding-top:18px}#tl_navigation .menu_level_0>li:after{content:"";width:calc(100% - 30px);height:1px;background:#3a454d;display:block;margin:15px auto}#tl_navigation .menu_level_0>li.last:after{display:none}#tl_navigation .menu_level_0>li>a{display:block;margin:0 15px;padding:3px 3px 3px 24px;color:#9fa4a8;font-size:.75rem;text-transform:uppercase}#tl_navigation .group-content{background:url(icons/content.svg) 3px 2px no-repeat}#tl_navigation .group-design{background:url(icons/monitor.svg) 3px 2px no-repeat}#tl_navigation .group-accounts{background:url(icons/person.svg) 3px 2px no-repeat}#tl_navigation .group-system{background:url(icons/wrench.svg) 3px 2px no-repeat}#tl_navigation .menu_level_1{padding:6px 0 0}#tl_navigation .menu_level_1 li{border-left:4px solid #0f1c26}#tl_navigation .menu_level_1 li.current{background-color:#172b3b;border-left-color:#f47c00}#tl_navigation .menu_level_1 a{display:block;padding:6px 18px 6px 35px;font-weight:400;color:#d3d5d7;transition:color .2s ease}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tl_navigation .menu_level_1 a{font-weight:300}}#tl_navigation .tl_level_2 li:hover a{color:#fff}#tl_navigation .collapsed .menu_level_1{display:none}#tl_buttons{margin:0;padding:12px 15px;text-align:right}.toggleWrap{cursor:pointer}.opacity{-moz-opacity:.8;opacity:.8}#main_headline{margin:20px 0;padding:0 16px;font-size:1.1rem}.popup #main_headline{display:none}#main_headline span:nth-child(even){font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#main_headline span:nth-child(even){font-weight:300}}h2.sub_headline{margin:3px 18px;padding:7px 0}.tl_gerror{margin:12px;padding:3px 0 3px 22px;background:url(icons/error.svg) no-repeat left center}.tl_error,.tl_confirm,.tl_info,.tl_new{margin:0 0 1px;padding:11px 18px 11px 37px;line-height:1.3}.tl_error{background:#faebeb url(icons/error.svg) no-repeat 16px 12px}.tl_confirm{background:#eefcde url(icons/ok.svg) no-repeat 16px 12px}.tl_info{background:#e9f0f7 url(icons/show.svg) no-repeat 16px 12px}.tl_new{background:#fbf2e5 url(icons/featured.svg) no-repeat 16px 12px}.tl_gerror,.tl_gerror a,.tl_error,.tl_error a{color:#c33}.tl_confirm,.tl_confirm a{color:#589b0e}.tl_info,.tl_info a{color:#006494}.tl_new,.tl_new a{color:#d68c23}.tl_panel,.tl_version_panel{padding:8px 10px 8px 0;background:#f3f3f5;border-bottom:1px solid #ddd;text-align:right}.tl_version_panel .tl_select{max-width:280px}.tl_version_panel .tl_formbody{position:relative}.tl_img_submit{width:16px;height:16px;border:0;margin:0;padding:0;text-indent:16px;white-space:nowrap;overflow:hidden;position:relative;top:9px;vertical-align:top;cursor:pointer}.filter_apply{background:url(icons/filter-apply.svg) center center no-repeat}.filter_reset{background:url(icons/filter-reset.svg) center center no-repeat}.tl_subpanel{float:right;letter-spacing:-.31em}.tl_subpanel *{letter-spacing:normal}.tl_subpanel strong,.tl_search span{vertical-align:middle}.tl_submit_panel{min-width:32px;padding-left:6px;padding-right:3px}.tl_panel .active,.tl_panel_bottom .active,#search .active{background-color:#fffce1}.tl_filter{width:100%}.tl_filter .tl_select{max-width:14.65%;margin-left:3px}.tl_submit_panel+.tl_filter{width:86%}.tl_limit{width:22%}.tl_limit .tl_select{width:52%;margin-left:3px}.tl_search{width:40%}.tl_search .tl_select{width:38%;margin-left:3px;margin-right:1%}.tl_search .tl_text{width:30%;margin-left:1%;-webkit-appearance:textfield;box-sizing:content-box}.tl_sorting{width:26%}.tl_sorting .tl_select{width:60%;margin-left:1%}.tl_xpl{padding:0 18px}.tl_tbox,.tl_box{padding:12px 0 25px;border-bottom:1px solid #e6e6e8}.tl_tbox:last-child,.tl_box:last-child{border-bottom:0}.tl_box h3,.tl_tbox h3,.tl_xpl h3{margin:0;padding-top:13px;height:16px;font-size:.875rem}.tl_box h4,.tl_tbox h4{margin:6px 0 0;padding:0;font-size:.875rem}.tl_tbox.theme_import{padding-left:15px;padding-right:15px}.tl_tbox.theme_import h3,.tl_tbox.theme_import h4,.tl_tbox.theme_import p{line-height:1.3}.tl_help,.tl_help *{font-size:.75rem}.tl_help,.tl_help a{margin-bottom:0;line-height:1.2;color:gray}.tl_help a:hover,.tl_help a:focus,.tl_help a:active{text-decoration:underline}.tl_formbody_edit.nogrid .w50{float:none}.tl_formbody_edit.nogrid .m12{margin-top:0;margin-bottom:0}.tl_edit_form .tl_formbody_edit{border-top:1px solid #e6e6e8}.tl_formbody_submit{border-top:1px solid #ddd}.tl_submit_container{padding:8px 15px;background:#f3f3f5}.tl_submit_container .tl_submit{margin-top:2px;margin-bottom:2px}.maintenance_active{padding-top:12px}.maintenance_active,.maintenance_inactive{border-top:1px solid #e6e6e8}.maintenance_inactive:last-child{padding-bottom:9px}.maintenance_inactive .tl_tbox{border:0!important;padding:6px 15px 14px}.maintenance_inactive .tl_message{margin-top:0}.maintenance_inactive h2.sub_headline{margin:18px 15px 3px}.maintenance_inactive .tl_submit_container{background:0 0;padding:0 15px 24px;border:0}.maintenance_inactive:last-of-type .tl_submit_container{padding-bottom:10px}#tl_maintenance_mode .tl_message{margin-bottom:12px}#tl_maintenance_mode .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}#tl_maintenance_index .tl_tbox{margin-top:-12px}@keyframes crawl-progress-bar-stripes{0%{background-position-x:1rem}}#tl_crawl .tl_message{margin-bottom:24px}#tl_crawl .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}#tl_crawl .tl_tbox{padding-top:0}#tl_crawl .tl_tbox>div{max-width:562px}#tl_crawl .tl_checkbox_container{margin-top:9px}#tl_crawl .inner{position:relative;margin:0 18px 18px}#tl_crawl .progress{display:flex;height:20px;overflow:hidden;background-color:#f0f0f2;margin-right:20px;border-radius:2px}#tl_crawl .progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-size:10px 10px}#tl_crawl .progress-bar.running{background-color:#f47c00;background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);animation:crawl-progress-bar-stripes 1s linear infinite}#tl_crawl .progress-bar.finished{background-color:#589b0e}#tl_crawl .progress-count{margin:6px 0 24px}#tl_crawl .results h3{font-size:.9rem;margin:18px 0 9px}#tl_crawl .results p{margin-bottom:6px}#tl_crawl .results .spinner{margin:0;padding:10px;background:url(icons/loading.svg) no-repeat left center;background-size:16px}#tl_crawl .subscriber-log{display:none;padding:5px 0;margin-bottom:0}#tl_crawl .wait{margin-top:18px}#tl_crawl .debug-log{position:absolute;top:2px;right:0}#tl_crawl .results.running .show-when-finished,#tl_crawl .results.finished .show-when-running{display:none}#tl_crawl .results.running .show-when-running,#tl_crawl .results.finished .show-when-finished{display:block}#tl_crawl .result .summary.success{color:#589b0e}#tl_crawl .result .summary.failure{color:#c33}#tl_crawl .result .warning{display:none;color:#006494}.two-factor{border-top:1px solid #e6e6e8;padding-bottom:9px}.two-factor h2.sub_headline{margin:18px 15px 3px}.two-factor>p{margin:0 15px 12px;line-height:1.3}.two-factor li{margin-left:2em;list-style:initial}.two-factor .qr-code{margin:0 15px}.two-factor .tl_listing_container{margin-top:0}.two-factor .widget{height:auto;margin:15px 15px 12px}.two-factor .widget .tl_error{margin:0;padding:1px 0;background:0 0;font-size:.75rem;line-height:1.25}.two-factor .tl_submit_container{background:0 0;padding:0 15px 10px;border:0}.two-factor .submit_container{clear:both;margin:0 15px 12px}.two-factor .tl_message{margin-bottom:12px}.two-factor .tl_message>p{padding-top:0;padding-bottom:0;background-color:transparent;background-position-y:center}.two-factor .tl_backup_codes>p,.two-factor .tl_trusted_devices>p{margin:0 15px 12px;line-height:1.3}.two-factor .backup-codes{max-width:224px;margin:15px 15px 24px;display:grid;grid-template-columns:repeat(2,1fr)}.two-factor .backup-codes li{margin:0;list-style:none}#search{margin:18px 18px -9px;text-align:right}#search .tl_text{max-width:160px;-webkit-appearance:textfield;box-sizing:content-box}.tl_edit_preview{margin-top:18px}.tl_edit_preview img{max-width:100%;height:auto;padding:2px;border:1px solid #ddd;background:#fff}.tl_edit_preview_enabled{position:relative;cursor:crosshair;display:inline-block}.tl_edit_preview_important_part{position:absolute;margin:-1px;border:1px solid #000;box-shadow:0 0 0 1px #fff,inset 0 0 0 1px #fff;opacity:.5}#tl_rebuild_index{padding:0 18px 18px;line-height:1.3}#index_note{margin:24px 0 18px;padding:11px 12px 12px;background:#ffc;border:1px solid #f90;font-size:.875rem}#index_loading{margin:0;padding:12px 12px 12px 24px;background:url(icons/loading.svg) no-repeat left center;background-size:16px;color:#006494}#index_complete{margin:0;padding:12px 12px 12px 24px;background:url(icons/ok.svg) no-repeat left center;color:#589b0e}table.tl_listing{width:100%}.tl_listing_container{padding:2px 0;margin:20px 15px 16px}.tl_listing_container.tree_view{position:relative}#tl_buttons+.tl_listing_container{margin-top:10px}#paste_hint+.tl_listing_container{margin-top:30px}.tl_folder_list,.tl_folder_tlist{padding:7px 0;border-bottom:1px solid #e9e9e9;background:#f3f3f5;font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_folder_list,.tl_folder_tlist{font-weight:500}}.tl_folder_tlist{border-top:1px solid #e9e9e9}.tl_file,.tl_file_list{padding:5px 0;border-bottom:1px solid #e9e9e9;background:#fff}.tl_file_list{padding:7px 0}.tl_file_list .ellipsis{height:16px;text-overflow:ellipsis;overflow:hidden;padding-right:18px;word-break:break-all}.tl_right_nowrap{padding:3px 0;text-align:right;white-space:nowrap}.tl_listing.picker .tl_file,.tl_listing.picker .tl_folder,.tl_listing.picker .tl_right_nowrap,.tl_listing_container.picker .tl_content_header,.tl_listing_container.picker .tl_content{background-image:linear-gradient(90deg,transparent calc(100% - 26px),#f0f0f2 26px)}.tl_listing.picker .tl_tree_checkbox,.tl_listing.picker .tl_tree_radio,.tl_listing_container.picker .tl_tree_checkbox,.tl_listing_container.picker .tl_tree_radio{margin-top:2px;margin-left:8px}.tl_listing.picker .tl_tree_checkbox:disabled,.tl_listing.picker .tl_tree_radio:disabled,.tl_listing_container.picker .tl_tree_checkbox:disabled,.tl_listing_container.picker .tl_tree_radio:disabled{visibility:hidden}.tl_listing_container.picker div[class^=ce_]{padding-right:24px}.tl_listing_container.picker .limit_toggler{width:calc(100% - 26px)}.tl_listing tr.odd td{background-color:#fafafc}.tl_listing th,.tl_listing td{padding-left:6px!important;padding-right:6px!important}.list_view .tl_listing img.theme_preview{margin-right:9px}.tl_show{width:96%;margin:18px 2%;padding:9px 0 18px}.tl_show td{padding:4px 6px;line-height:16px;white-space:pre-line;background:#f6f6f8}.tl_show td:first-child{width:34%;white-space:normal}.tl_show td p:last-of-type{margin-bottom:0}.tl_show tr:nth-child(2n) td{background:#fff}.tl_show small{display:block;color:gray}.tl_label{margin-right:12px;font-weight:600;white-space:nowrap}.tl_label small{font-weight:400}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_label small{font-weight:300}}.tl_empty{margin:0;padding:18px}.tl_empty_parent_view{margin:0;padding:18px 0 0}.tl_listing_container+.tl_empty{margin-top:-32px}.tl_noopt{margin:0 0 -1px}.tl_select_trigger{margin-top:-9px;padding:0 6px 3px 0;text-align:right}.tl_radio_reset{margin-top:6px;padding:0 6px 3px 0;text-align:right}.tl_select_label,.tl_radio_label{margin-right:2px;color:#999;font-size:.75rem}.tl_header{padding:6px;background:#f9f9fb;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.tl_header_table{line-height:1.3}.tl_content_header{padding:7px 6px;border-bottom:1px solid #e9e9e9;background:#f6f6f8;font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_content_header{font-weight:500}}.tl_content{padding:6px;border-bottom:1px solid #e9e9e9;overflow:hidden;position:relative;background-color:#fff}.parent_view>ul{background:#f3f3f5}.tl_content.wrapper_stop{border-top:1px solid #e9e9e9}.tl_content.wrapper_start h1{margin-bottom:9px}.tl_content.indent_1{margin-left:20px}.tl_content.indent_2{margin-left:40px}.tl_content.indent_3{margin-left:60px}.tl_content.indent_4{margin-left:80px}.tl_content.indent_5{margin-left:100px}.tl_content.indent_last{border-bottom:0}.tl_content h1,.tl_content h2,.tl_content h3,.tl_content h4,.tl_content h5,.tl_content h6{font-size:.875rem}.tl_content img{max-width:320px;height:auto}.tl_content pre{margin-top:3px;margin-bottom:3px;word-break:break-all;white-space:pre-wrap}.tl_content pre.disabled{color:#a6a6a6}.tl_content .ce_text a{color:#589b0e}.tl_content span.comment{color:#006494;display:inline-block;margin-bottom:3px}.tl_content_left{line-height:16px}.tl_content_right{float:right;text-align:right;margin-left:12px;margin-top:-1px}.tl_right button,.tl_content_right button{margin:0;padding:0;border:0;height:17px;background:0 0}.cte_type{margin:2px 0 6px;font-size:.75rem;color:gray}.cte_type.published,.cte_type.published a{color:#589b0e}.cte_type.unpublished,.cte_type.unpublished a{color:#c33}.limit_height{overflow:hidden;line-height:1.25}.limit_toggler{width:100%;position:absolute;bottom:0;left:0;background:#fff;line-height:11px;text-align:center}.limit_toggler button{margin:0;padding:0;border:0;background:#fff;width:24px;line-height:8px;color:#999;outline:0;border-top-left-radius:2px;border-top-right-radius:2px}.limit_toggler button span{position:relative;top:-4px}.limit_height input[type=file]{position:relative}.limit_height select{-moz-appearance:menulist;-webkit-appearance:menulist}.limit_height label,.limit_height .checkbox_container legend,.limit_height .radio_container legend{display:inline-block;width:180px;vertical-align:top;margin-top:3px}.limit_height .widget{margin-left:0;margin-right:0}.limit_height .widget-submit{margin-left:180px}.limit_height .checkbox_container label,.limit_height .radio_container label{display:initial}.tl_folder_top{padding:5px 0;border-top:1px solid #e3e3e3;border-bottom:1px solid #e3e3e3;background:#f0f0f2}.tl_folder{padding:5px 0;border-bottom:1px solid #e9e9e9;background:#f6f6f8}.tl_folder.tl_folder_dropping,.tl_folder_top.tl_folder_dropping{background-color:#4078a5!important;color:#fff!important}.tl_folder.tl_folder_dropping a,.tl_folder_top.tl_folder_dropping a{color:inherit}.tl_listing .tl_left{flex-grow:1;margin-left:40px;text-indent:-40px;box-sizing:border-box}.tl_listing .tl_left.tl_left_dragging{position:absolute;background:#4078a5;border-radius:10px;color:#fff;padding:5px 10px!important;margin-left:0;text-indent:0;white-space:nowrap}.tl_listing .tl_left.tl_left_dragging .preview-image,.tl_listing .tl_left.tl_left_dragging a img{display:none}.tl_listing .tl_left.tl_left_dragging a,.tl_listing .tl_left.tl_left_dragging .tl_gray{color:inherit}.tl_listing_dragging .hover-div:not(.tl_folder):hover{background-color:transparent!important}.tl_tree_xtnd .tl_file{padding-top:4px;padding-bottom:4px}.tl_tree_xtnd .tl_file .tl_left{margin-left:22px;text-indent:-22px}.tl_tree_xtnd .tl_file .tl_left *{line-height:normal}.tl_tree_xtnd .tl_file .tl_left a{position:relative;top:-1px}.tl_tree_xtnd .tl_file .tl_left img{margin-right:2px}.tl_left>a:last-of-type{vertical-align:middle}.tl_left>a:first-child{vertical-align:bottom}.tl_file_manager .tl_file .tl_left>a:first-child{vertical-align:1px}.tl_file_manager .preview-image{max-width:100px;max-height:75px;width:auto;height:auto;margin:0 0 2px -18px}.tl_file_manager .preview-important{max-width:80px;max-height:60px;width:auto;height:auto;margin:0 0 2px 0;vertical-align:bottom}.tl_listing .tl_right{padding:0 0 1px 9px;white-space:nowrap}.tl_listing,.tl_listing ul{margin:0;padding:0}.tl_listing li{display:flex;margin:0;padding-left:6px;padding-right:6px;list-style-type:none}.tl_listing li.parent{display:inline;padding-left:0;padding-right:0}label.tl_change_selected{margin-right:2px;color:#999;font-size:.75rem}#tl_breadcrumb{margin:0 0 12px;padding:4px 6px;overflow:hidden;background:#fffce1;border:1px solid #d68c23;line-height:24px}#tl_breadcrumb li{margin:0;padding:0 3px;list-style-type:none;float:left}#tl_breadcrumb li a{display:inline-block}#tl_breadcrumb li img{width:16px;height:16px;vertical-align:-3px}.selector_container{margin-top:1px;position:relative}.selector_container>ul{margin-bottom:1px;list-style-type:none;overflow:hidden}.selector_container>ul>li{margin-right:9px;padding:2px 0}.selector_container p{margin-bottom:1px}.selector_container ul:not(.sgallery) img{margin-right:1px;vertical-align:text-top}.selector_container .limit_height{height:auto!important;max-height:190px}.selector_container .limit_toggler{display:none}.selector_container h1,.selector_container h2,.selector_container h3,.selector_container h4{margin:0;padding:0}.selector_container pre{white-space:pre-wrap}ul.sgallery{overflow:hidden}ul.sgallery li{min-width:100px;min-height:75px;float:left;margin:2px 4px 2px 0;padding:0;background:#eee;display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center}.popup #tl_soverview{margin-top:15px}#tl_soverview>div{padding:0 15px 4px;border-bottom:1px solid #e6e6e8}#tl_soverview>div:last-child{border-bottom:0}#tl_soverview table{width:100%;margin-bottom:14px}#tl_messages h2,#tl_shortcuts h2,#tl_versions h2{margin:18px 0 6px}#tl_messages p:not([class]){margin-top:6px}#tl_messages .tl_error,#tl_messages .tl_confirm,#tl_messages .tl_info,#tl_messages .tl_new{padding:3px 6px 3px 21px;background-position:left 4px;background-color:transparent}#tl_shortcuts p a{text-decoration:underline}#tl_versions{margin-bottom:0}#tl_versions th{background:#f6f6f8;padding:6px;border-top:1px solid #e9e9e9}#tl_versions th,#tl_versions td{border-bottom:1px solid #e9e9e9}#tl_versions td{padding:4px}#tl_versions td:first-child{white-space:nowrap}#tl_versions td:last-child{width:32px;white-space:nowrap;text-align:right}#tl_versions img.undo{padding-right:2px}#tl_versions .pagination{margin-top:18px;margin-bottom:14px}.tl_chmod{width:100%}.tl_chmod th{height:18px;text-align:center;font-weight:400;background:#f0f0f2}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_chmod th{font-weight:300}}.tl_chmod td{text-align:center;background:#f6f6f8}.tl_chmod th,.tl_chmod td{width:14.2857%;padding:6px;border:1px solid #fff}.tl_modulewizard button,.tl_optionwizard button,.tl_key_value_wizard button,.tl_tablewizard button,.tl_listwizard button,.tl_checkbox_wizard button,.tl_metawizard button,.tl_sectionwizard button{margin:0;padding:0;border:0;background:0 0}.tl_modulewizard{width:100%;max-width:800px;margin-top:2px}.tl_modulewizard td{position:relative;padding:0 3px 0 0}.tl_modulewizard th{font-size:.75rem;font-weight:400;padding:0 6px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_modulewizard th{font-weight:300}}.tl_modulewizard td:last-child{width:1%;white-space:nowrap}.tl_modulewizard img{position:relative;top:1px}.js .tl_modulewizard input.mw_enable{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.tl_modulewizard img.mw_enable{display:none}.js .tl_modulewizard img.mw_enable{display:inline;margin-right:1px}.tl_optionwizard{width:100%;max-width:600px}.tl_key_value_wizard{width:100%;max-width:450px}.tl_optionwizard,.tl_key_value_wizard{margin-top:2px}.tl_optionwizard label,.tl_key_value_wizard label{margin-right:3px}.tl_optionwizard td,.tl_key_value_wizard td{padding:0 3px 0 0}.tl_optionwizard th,.tl_key_value_wizard th{font-size:.75rem;font-weight:400;padding:0 6px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_optionwizard th,.tl_key_value_wizard th{font-weight:300}}.tl_optionwizard td:nth-child(n+3),.tl_key_value_wizard td:nth-child(n+3){width:1%;white-space:nowrap}.tl_optionwizard img,.tl_key_value_wizard img{position:relative;top:1px}.tl_optionwizard .fw_checkbox,.tl_key_value_wizard .fw_checkbox{margin:0 1px}#tl_tablewizard{margin-top:2px;padding-bottom:2px;overflow:auto}.tl_tablewizard td{padding:0 3px 0 0}.tl_tablewizard thead td{padding-bottom:3px;text-align:center;white-space:nowrap}.tl_tablewizard tbody td:last-child{white-space:nowrap}.tl_tablewizard td.tcontainer{vertical-align:top}.tl_listwizard .tl_text{width:78%}.tl_checkbox_wizard .fixed{display:block;margin-top:1px}.tl_checkbox_wizard .sortable span{display:block}.tl_checkbox_wizard .sortable img{vertical-align:bottom}.tl_metawizard{margin:3px 0}.tl_metawizard li{overflow:hidden;margin-bottom:2px;padding:9px;background:#f3f3f5}.tl_metawizard li.odd{background:#f9f9fb}.tl_metawizard label{float:left;width:18%;margin-top:9px}.tl_metawizard .tl_text{float:left;width:82%;margin:1px 0}.tl_metawizard .tl_text+a{top:6px}.tl_metawizard br{clear:left}.tl_metawizard .lang{display:block;margin:3px 0 9px;font-weight:600;position:relative}.tl_metawizard .lang img{position:absolute;right:0;top:-1px;cursor:pointer}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_metawizard .lang{font-weight:500}}.tl_sectionwizard{margin-top:2px;width:100%;max-width:680px}.tl_sectionwizard td{width:25%;position:relative;padding:0 3px 0 0}.tl_sectionwizard th{font-size:.75rem;font-weight:400;padding:0 4px 1px 0}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.tl_sectionwizard th{font-weight:300}}.tl_sectionwizard td:last-child{white-space:nowrap}#paste_hint{position:relative;z-index:1}.tl_message+#paste_hint{margin-top:-12px}#paste_hint p{position:absolute;font-family:"Architects Daughter",cursive;font-size:1rem;color:#838990;top:0;right:30px;padding:0 36px 24px 0;background:url(icons/arrow_right.svg) bottom right no-repeat;transform:rotate(-1deg)}.sort_hint{position:absolute;font-family:"Architects Daughter",cursive;font-size:1rem;color:#838990;top:-50px;left:160px;padding:0 6px 24px 42px;background:url(icons/arrow_left.svg) 6px bottom no-repeat;transform:rotate(-2deg)}.widget+.subpal .sort_hint{left:260px}.widget+.widget .sort_hint{left:320px}.serp-preview{max-width:600px;margin:2px 0;padding:4px 6px;font-family:Arial,sans-serif;font-weight:400;color:#3c4043;border:1px dotted #aaa;border-radius:2px}.serp-preview p{margin-bottom:0;line-height:1.3}.serp-preview .url{margin-bottom:4px}.serp-preview .title{margin-bottom:2px;font-size:18px;color:#1a0dab;line-height:1}.serp-preview .description:not(:empty){margin-top:3px}#tl_ajaxBox{width:300px;padding:2em;box-sizing:border-box;position:absolute;left:50%;margin-left:-150px;background:#fff url(icons/loading.svg) no-repeat right 2em center;border:2px solid #111;border-radius:2px;font-size:1rem;text-align:left}#tl_ajaxOverlay{width:100%;height:100%;position:absolute;top:0;left:0;background:#fff;opacity:.5}.ce_gallery ul{overflow:hidden}.ce_gallery li{float:left;margin:0 6px 6px 0}.drag-handle{cursor:move}ul.sortable li{cursor:move;position:relative}ul.sortable li .dirname{display:none}ul.sortable li:hover .dirname{display:inline}ul.sortable button{position:absolute;top:0;right:0;border:0;background:#eee;margin:0;padding:0 0 3px;font-size:22px;line-height:9px;cursor:pointer;transition:all .1s linear}ul.sortable button:hover{background:#f9f9f9}ul.sortable button[disabled]{color:#999;cursor:not-allowed}ul.sortable button[disabled]:hover{background:rgba(255,255,255,.7)}#picker-menu{padding:9px 6px 0;border-bottom:1px solid #ddd}#picker-menu li{display:inline-block;padding:8px 0;background-color:#f9f9fb;border:1px solid #ddd;border-radius:2px 2px 0 0;position:relative;top:1px}#picker-menu li:hover{background-color:#f3f3f5}#picker-menu li.current{background-color:#f3f3f5;border-bottom-color:#f3f3f5}#picker-menu a{padding:3px 12px 3px 32px;background:url(icons/manager.svg) 12px center no-repeat}#picker-menu a:hover{color:#444}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#picker-menu a:hover{color:#222}}#picker-menu a.pagePicker{background-image:url(icons/pagemounts.svg);background-size:16px}#picker-menu a.filePicker{background-image:url(icons/filemounts.svg);background-size:14px}#picker-menu a.articlePicker{background-image:url(icons/articles.svg);background-size:16px}#picker-menu a.close{background-image:url(icons/back.svg)}.ace_editor{padding:3px;z-index:0}.ace_editor,.ace_editor *{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important;font-size:.75rem!important;color:#444}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){.ace_editor,.ace_editor *{color:#222}}.ace-fullsize{overflow:hidden!important}.ace-fullsize .ace_editor{position:fixed!important;top:0;right:0;bottom:0;left:0;width:auto!important;height:auto!important;margin:0;border:0;z-index:10000}div.mce-edit-area{width:99.9%}time[title]{cursor:help}.float_left{float:left}.float_right{float:right}#manager{padding:9px 6px 0;border-bottom:1px solid #ddd}#manager a{padding:3px 12px 3px 32px;background:url(icons/manager.svg) 12px center no-repeat}.header_icon,.header_clipboard,.header_back,.header_new,.header_rss,.header_edit_all,.header_delete_all,.header_new_folder,.header_css_import,.header_theme_import,.header_store,.header_toggle,.header_sync{padding:3px 0 3px 21px;background-position:left center;background-repeat:no-repeat;margin-left:15px}.list_icon{margin-left:-3px;padding-left:20px;background-position:left center;background-repeat:no-repeat}.list_icon_new{width:16px;background-position:1px center;background-repeat:no-repeat}.header_clipboard{background-image:url(icons/clipboard.svg)}.header_back{background-image:url(icons/back.svg)}.header_new{background-image:url(icons/new.svg)}.header_rss{background-image:url(icons/rss.svg)}.header_edit_all{background-image:url(icons/all.svg)}.header_delete_all{background-image:url(icons/deleteAll.svg)}.header_new_folder{padding-left:24px;background-image:url(icons/newfolder.svg)}.header_css_import{background-image:url(icons/cssimport.svg)}.header_theme_import{background-image:url(icons/theme_import.svg)}.header_store{padding-left:18px;background-image:url(icons/store.svg)}.header_toggle{background-image:url(icons/folPlus.svg)}.header_sync{background-image:url(icons/sync.svg)}.tl_text_trbl,.tl_imageSize_0,.tl_imageSize_1,#ctrl_playerSize input{background:url(icons/hints.svg) no-repeat right 1px top 2px}#ctrl_playerSize_1,.tl_imageSize_1{background-position:right 1px top -28px!important}.trbl_top{background-position:right 1px top -59px!important}.trbl_right{background-position:right 1px top -89px!important}.trbl_bottom{background-position:right 1px top -119px!important}.trbl_left{background-position:right 1px top -149px!important}#ctrl_shadowsize_top{background-position:right 1px top -179px!important}#ctrl_shadowsize_right{background-position:right 1px top -209px!important}#ctrl_shadowsize_bottom{background-position:right 1px top -238px!important}#ctrl_shadowsize_left{background-position:right 1px top -269px!important}#ctrl_borderradius_top{background-position:left -299px!important}#ctrl_borderradius_right{background-position:right 1px top -329px!important}#ctrl_borderradius_bottom{background-position:right 1px top -352px!important}#ctrl_borderradius_left{background-position:left -382px!important}label.error,legend.error,.tl_checkbox_container.error legend{color:#c33}.tl_tbox .tl_error,.tl_box .tl_error{background:0 0;padding:0;margin-bottom:0;font-size:.75rem}.tl_formbody_edit>.tl_error{margin-top:9px}.broken-image{display:inline-block;padding:12px 12px 12px 30px;background:#faebeb url(icons/error.svg) no-repeat 9px center;color:#c33;text-indent:0}fieldset.tl_tbox,fieldset.tl_box{margin-top:5px;padding-top:0;border-top:none;border-left:0;border-right:0}fieldset.tl_tbox.nolegend,fieldset.tl_box.nolegend{border-top:0}fieldset.tl_tbox>legend,fieldset.tl_box>legend{box-sizing:border-box;color:#6a6a6c;padding:9px 12px 9px 28px;background:url(icons/navcol.svg) 13px 11px no-repeat;cursor:pointer}fieldset.collapsed{margin-bottom:0;padding-bottom:5px}fieldset.collapsed div{display:none!important}fieldset.collapsed>legend{background:url(icons/navexp.svg) 13px 11px no-repeat}#tl_maintenance_cache table{width:100%}#tl_maintenance_cache th,#tl_maintenance_cache td{border-bottom:1px solid #e9e9e9}#tl_maintenance_cache th{background:#f6f6f8;padding:6px;border-top:1px solid #e9e9e9}#tl_maintenance_cache td{padding:6px;line-height:1.2}#tl_maintenance_cache tr:nth-child(even) td{background:#fcfcfe}#tl_maintenance_cache td span{color:#999}#tl_maintenance_cache td:first-child{width:16px}.mac #tl_maintenance_cache td:first-child .tl_checkbox{top:-2px}#tl_maintenance_cache .nw{white-space:nowrap}#tl_maintenance_cache .tl_checkbox_container{margin-top:12px}#tl_maintenance_cache .tl_checkbox_container label{font-weight:600}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:192dpi){#tl_maintenance_cache .tl_checkbox_container label{font-weight:500}}.pagination{overflow:hidden;background:#f6f6f8;margin-bottom:18px;border:solid #e9e9e9;border-width:1px 0;padding:12px 15px}.pagination ul{width:60%;float:right;text-align:right;padding-right:3px}.pagination p{width:30%;float:left;margin-bottom:0}.pagination li{display:inline;padding-right:3px}.pagination .active{color:#999}.pagination-lp{margin-left:15px;margin-right:15px}#sync-results{overflow:hidden;margin:18px 15px 0}#sync-results p{margin-bottom:0}#sync-results .left{float:left;padding:2px 0}#sync-results .right{float:right}#result-list{margin:15px}#result-list .tl_error,#result-list .tl_confirm,#result-list .tl_info,#result-list .tl_new{padding:3px 0;background:0 0}.dropzone{margin:2px 0;min-height:auto!important;border:3px dashed #ddd!important;border-radius:2px}.dropzone-filetree{display:none;position:absolute;top:0;left:0;width:100%;height:100%;opacity:.8;z-index:1}.dropzone-filetree-enabled{display:block}.dz-message span{font-size:1.3125rem;color:#ccc}.tox-tinymce{margin:3px 0}.tox .tox-menubar,.tox .tox-toolbar__primary,.tox .tox-statusbar{background-color:#f7f7f9!important}@media (max-width:991px){body{background:#eaeaec}#header{width:100%;padding:0;position:fixed;top:0;z-index:2;transition:transform .2s ease;-webkit-transform:none;transform:none;will-change:transform}#container{display:block;padding-top:40px;overflow:hidden}#main,#left{float:none;overflow:hidden}#main{width:100%!important;position:relative;transition:transform .2s ease;-webkit-transform:none;transform:none;will-change:transform}.show-navigation #main{-webkit-transform:translateX(240px);transform:translateX(240px)}#left{position:absolute;top:40px;width:240px;transition:transform .2s ease;-webkit-transform:translateX(-240px);transform:translateX(-240px);will-change:transform}.show-navigation #left{-webkit-transform:none;transform:none}#tmenu .burger{display:inline}}@media (max-width:767px){#header.down{-webkit-transform:translateY(-40px);transform:translateY(-40px)}#header h1 a{width:22px;padding:12px;text-indent:34px;overflow:hidden}#tmenu>li>a{width:16px;margin-bottom:-2px;position:relative;overflow:hidden;white-space:nowrap;text-indent:28px;background-size:18px!important}#tmenu sup{top:6px;font-size:.5rem}#tmenu .icon-debug{background:url(icons/debug.svg) center center no-repeat}#tmenu .icon-preview{background:url(icons/preview.svg) center center no-repeat}#tmenu .h2{width:16px;margin:0 0 -2px;padding-right:12px;overflow:hidden;white-space:nowrap;text-indent:28px;background:url(icons/profile.svg) center center no-repeat;background-size:18px}#main .content{margin:15px 10px}#main_headline{margin:15px 0;padding:0 11px}div.tl_tbox,div.tl_box{position:relative}.tl_content_left{width:100%;float:none}.showColumns th,.showColumns td{display:block}.showColumns th:empty{display:none}.tl_label{white-space:normal}.list_view .tl_listing img.theme_preview{display:none}.tl_filter{box-sizing:border-box;padding:0 3px 0 7px}.tl_filter strong{display:none}.tl_filter .tl_select{display:block;max-width:100%}.tl_search{width:76%;max-width:283px}.tl_search .tl_select{width:36%}.tl_search .tl_text{width:26%}.tl_sorting{width:60%;max-width:212px}.tl_limit{width:50%;max-width:177px}.tl_submit_panel{float:right;z-index:1}input.tl_submit{margin-top:3px;margin-bottom:3px;padding-left:6px!important;padding-right:7px!important}.tl_listing .tl_left,.tl_show td{word-break:break-word}#tl_breadcrumb li{padding:3px}#tl_versions{display:none}.tl_version_panel .tl_select{width:44%}.tl_modulewizard td:first-child{width:1%}.tl_modulewizard td:first-child .tl_select{max-width:52vw}#paste_hint,.sort_hint{display:none}#tl_maintenance_cache table{width:100%}#tl_maintenance_cache tr th:last-child,#tl_maintenance_cache tr td:last-child{display:none}.tl_file_list .ellipsis{padding-right:10px}}@media (max-width:599px){.tl_metawizard label{width:auto;float:none;font-size:.9em;display:block;margin-top:3px}.tl_metawizard .tl_text{width:100%}}@media (max-width:479px){.tl_modulewizard td:first-child .tl_select{max-width:48vw}} \ No newline at end of file diff --git a/core-bundle/src/Resources/contao/widgets/MetaWizard.php b/core-bundle/src/Resources/contao/widgets/MetaWizard.php index 73a5e10a9eb..af43cbbc60a 100644 --- a/core-bundle/src/Resources/contao/widgets/MetaWizard.php +++ b/core-bundle/src/Resources/contao/widgets/MetaWizard.php @@ -165,19 +165,6 @@ public function generate() '; } - $options = array(''); - - // Add the remaining languages - foreach ($languages as $k=>$v) - { - $options[] = ''; - } - - $return .= ' -
- -
'; - return $return; } } diff --git a/core-bundle/src/Resources/public/core.js b/core-bundle/src/Resources/public/core.js index 56916530ab9..2986661ee94 100644 --- a/core-bundle/src/Resources/public/core.js +++ b/core-bundle/src/Resources/public/core.js @@ -2266,70 +2266,13 @@ var Backend = }); }, - /** - * Meta wizard - * - * @param {object} el The submit button - * @param {string} ul The DOM element - */ - metaWizard: function(el, ul) { - var opt = el.getParent('div').getElement('select'); - - if (opt.value == '') { - return; // no language given - } - - var li = $(ul).getLast('li').clone(true, true), - span = li.getElement('span'), - img = span.getElement('img'); - - // Update the data-language attribute - li.setProperty('data-language', opt.value); - - // Update the language text - span.set('text', opt.options[opt.selectedIndex].text + ' '); - img.inject(span, 'bottom'); - - // Update the name, label and ID attributes - li.getElements('input').each(function(inp) { - inp.value = ''; - inp.name = inp.name.replace(/\[[a-z]{2}(_[A-Z]{2})?]/, '[' + opt.value + ']'); - var lbl = inp.getPrevious('label'), - i = parseInt(lbl.get('for').replace(/ctrl_[^_]+_/, '')); - lbl.set('for', lbl.get('for').replace(i, i+1)); - inp.id = lbl.get('for'); - }); - - // Update the class name - li.className = (li.className == 'even') ? 'odd' : 'even'; - li.inject($(ul), 'bottom'); - - // Update the picker - li.getElements('a[id^=pp_]').each(function(link) { - var i = parseInt(link.get('id').replace(/pp_[^_]+_/, '')); - link.id = link.get('id').replace(i, i + 1); - var script = link.getNext('script'); - script.set('html', script.get('html').replace(new RegExp('_' + i, 'g'), '_' + (i + 1))); - eval(script.get('html')); - }); - - // Disable the "add language" button - el.getParent('div').getElement('input[type="button"]').setProperty('disabled', true); - - // Disable the option - opt.options[opt.selectedIndex].setProperty('disabled', true); - opt.value = ''; - }, - /** * Remove a meta entry * * @param {object} el The DOM element */ metaDelete: function(el) { - var li = el.getParent('li'), - select = el.getParent('div').getElement('select'), - opt; + var li = el.getParent('li'); // Empty the last element instead of removing it (see #4858) if (li.getPrevious() === null && li.getNext() === null) { @@ -2337,13 +2280,7 @@ var Backend = input.value = ''; }); } else { - // If the language code is valid and the option exists, enable it (see #1635) - if (opt = select.getElement('option[value=' + li.getProperty('data-language') + ']')) { - opt.removeProperty('disabled'); - } - li.destroy(); - select.fireEvent('liszt:updated'); } }, diff --git a/core-bundle/src/Resources/public/core.min.js b/core-bundle/src/Resources/public/core.min.js index cd74bf2e6d9..b728d5076ac 100644 --- a/core-bundle/src/Resources/public/core.min.js +++ b/core-bundle/src/Resources/public/core.min.js @@ -1 +1 @@ -var AjaxRequest={themePath:Contao.script_url+"system/themes/"+Contao.theme+"/",toggleNavigation:function(e,t,n){e.blur();var a=$(t),o=$(e).getParent("li");return a&&(o.hasClass("collapsed")?(o.removeClass("collapsed"),$(e).store("tip:title",Contao.lang.collapse),new Request.Contao({url:n}).post({action:"toggleNavigation",id:t,state:1,REQUEST_TOKEN:Contao.request_token})):(o.addClass("collapsed"),$(e).store("tip:title",Contao.lang.expand),new Request.Contao({url:n}).post({action:"toggleNavigation",id:t,state:0,REQUEST_TOKEN:Contao.request_token}))),!1},toggleStructure:function(i,l,s,r){i.blur();var e=$(l),c=$(i).getFirst("img");return e?"none"==e.getStyle("display")?(e.setStyle("display",null),c.src=AjaxRequest.themePath+"icons/folMinus.svg",$(i).store("tip:title",Contao.lang.collapse),new Request.Contao({field:i}).post({action:"toggleStructure",id:l,state:1,REQUEST_TOKEN:Contao.request_token})):(e.setStyle("display","none"),c.src=AjaxRequest.themePath+"icons/folPlus.svg",$(i).store("tip:title",Contao.lang.expand),new Request.Contao({field:i}).post({action:"toggleStructure",id:l,state:0,REQUEST_TOKEN:Contao.request_token})):new Request.Contao({field:i,evalScripts:!0,onRequest:AjaxRequest.displayBox(Contao.lang.loading+" …"),onSuccess:function(e){var t=new Element("li",{id:l,class:"parent",styles:{display:"inline"}});if(new Element("ul",{class:"level_"+s,html:e}).inject(t,"bottom"),5==r)t.inject($(i).getParent("li"),"after");else{for(var n,a=!1,o=$(i).getParent("li");"element"==typeOf(o)&&(n=o.getNext("li"));)if((o=n).hasClass("tl_folder")){a=!0;break}a?t.inject(o,"before"):t.inject(o,"after")}t.getElements("a").each(function(e){e.href=e.href.replace(/&ref=[a-f0-9]+/,"&ref="+Contao.referer_id)}),$(i).store("tip:title",Contao.lang.collapse),c.src=AjaxRequest.themePath+"icons/folMinus.svg",window.fireEvent("structure"),AjaxRequest.hideBox(),window.fireEvent("ajax_change")}}).post({action:"loadStructure",id:l,level:s,state:1,REQUEST_TOKEN:Contao.request_token}),!1},toggleFileManager:function(n,a,e,o){n.blur();var t=$(a),i=$(n).getFirst("img");return t?"none"==t.getStyle("display")?(t.setStyle("display",null),i.src=AjaxRequest.themePath+"icons/folMinus.svg",$(n).store("tip:title",Contao.lang.collapse),new Request.Contao({field:n}).post({action:"toggleFileManager",id:a,state:1,REQUEST_TOKEN:Contao.request_token})):(t.setStyle("display","none"),i.src=AjaxRequest.themePath+"icons/folPlus.svg",$(n).store("tip:title",Contao.lang.expand),new Request.Contao({field:n}).post({action:"toggleFileManager",id:a,state:0,REQUEST_TOKEN:Contao.request_token})):new Request.Contao({field:n,evalScripts:!0,onRequest:AjaxRequest.displayBox(Contao.lang.loading+" …"),onSuccess:function(e){var t=new Element("li",{id:a,class:"parent",styles:{display:"inline"}});new Element("ul",{class:"level_"+o,html:e}).inject(t,"bottom"),t.inject($(n).getParent("li"),"after"),t.getElements("a").each(function(e){e.href=e.href.replace(/&ref=[a-f0-9]+/,"&ref="+Contao.referer_id)}),$(n).store("tip:title",Contao.lang.collapse),i.src=AjaxRequest.themePath+"icons/folMinus.svg",AjaxRequest.hideBox(),window.fireEvent("ajax_change")}}).post({action:"loadFileManager",id:a,level:o,folder:e,state:1,REQUEST_TOKEN:Contao.request_token}),!1},togglePagetree:function(n,a,e,t,o){n.blur(),Backend.getScrollOffset();var i=$(a),l=$(n).getFirst("img");return i?"none"==i.getStyle("display")?(i.setStyle("display",null),l.src=AjaxRequest.themePath+"icons/folMinus.svg",$(n).store("tip:title",Contao.lang.collapse),new Request.Contao({field:n}).post({action:"togglePagetree",id:a,state:1,REQUEST_TOKEN:Contao.request_token})):(i.setStyle("display","none"),l.src=AjaxRequest.themePath+"icons/folPlus.svg",$(n).store("tip:title",Contao.lang.expand),new Request.Contao({field:n}).post({action:"togglePagetree",id:a,state:0,REQUEST_TOKEN:Contao.request_token})):new Request.Contao({field:n,evalScripts:!0,onRequest:AjaxRequest.displayBox(Contao.lang.loading+" …"),onSuccess:function(e){var t=new Element("li",{id:a,class:"parent",styles:{display:"inline"}});new Element("ul",{class:"level_"+o,html:e}).inject(t,"bottom"),t.inject($(n).getParent("li"),"after"),t.getElements("a").each(function(e){e.href=e.href.replace(/&ref=[a-f0-9]+/,"&ref="+Contao.referer_id)}),$(n).store("tip:title",Contao.lang.collapse),l.src=AjaxRequest.themePath+"icons/folMinus.svg",AjaxRequest.hideBox(),window.fireEvent("ajax_change")}}).post({action:"loadPagetree",id:a,level:o,field:e,name:t,state:1,REQUEST_TOKEN:Contao.request_token}),!1},toggleFiletree:function(n,a,e,t,o,i){n.blur(),Backend.getScrollOffset();var l=$(a),s=$(n).getFirst("img");return l?"none"==l.getStyle("display")?(l.setStyle("display",null),s.src=AjaxRequest.themePath+"icons/folMinus.svg",$(n).store("tip:title",Contao.lang.collapse),new Request.Contao({field:n}).post({action:"toggleFiletree",id:a,state:1,REQUEST_TOKEN:Contao.request_token})):(l.setStyle("display","none"),s.src=AjaxRequest.themePath+"icons/folPlus.svg",$(n).store("tip:title",Contao.lang.expand),new Request.Contao({field:n}).post({action:"toggleFiletree",id:a,state:0,REQUEST_TOKEN:Contao.request_token})):new Request.Contao({field:n,evalScripts:!0,onRequest:AjaxRequest.displayBox(Contao.lang.loading+" …"),onSuccess:function(e){var t=new Element("li",{id:a,class:"parent",styles:{display:"inline"}});new Element("ul",{class:"level_"+i,html:e}).inject(t,"bottom"),t.inject($(n).getParent("li"),"after"),t.getElements("a").each(function(e){e.href=e.href.replace(/&ref=[a-f0-9]+/,"&ref="+Contao.referer_id)}),$(n).store("tip:title",Contao.lang.collapse),s.src=AjaxRequest.themePath+"icons/folMinus.svg",AjaxRequest.hideBox(),window.fireEvent("ajax_change")}}).post({action:"loadFiletree",id:a,folder:e,level:i,field:t,name:o,state:1,REQUEST_TOKEN:Contao.request_token}),!1},toggleSubpalette:function(a,o,e){a.blur();var t=$(o);t?a.value?(a.value="",a.checked="",t.setStyle("display","none"),t.getElements("[required]").each(function(e){e.set("required",null).set("data-required","")}),new Request.Contao({field:a}).post({action:"toggleSubpalette",id:o,field:e,state:0,REQUEST_TOKEN:Contao.request_token})):(a.value=1,a.checked="checked",t.setStyle("display",null),t.getElements("[data-required]").each(function(e){e.set("required","").set("data-required",null)}),new Request.Contao({field:a}).post({action:"toggleSubpalette",id:o,field:e,state:1,REQUEST_TOKEN:Contao.request_token})):new Request.Contao({field:a,evalScripts:!1,onRequest:AjaxRequest.displayBox(Contao.lang.loading+" …"),onSuccess:function(e,t){var n=new Element("div",{id:o,class:"subpal cf",html:e,styles:{display:"block"}}).inject($(a).getParent("div").getParent("div"),"after");t.javascript&&(document.write=function(e){var n="";e.replace(/