From 12ce2be93f492262dc11ff90f31b0b7e329bab2d Mon Sep 17 00:00:00 2001 From: kamiyang Date: Fri, 21 Jun 2019 16:08:24 +0200 Subject: [PATCH 01/10] [TASK] Introduce templates submodule with dummy output --- Classes/Modules/Fluid/Templates.php | 66 +++++++++++++++++++ .../Private/Language/locallang_fluid.xlf | 3 + .../Private/Templates/Fluid/Templates.html | 1 + ext_localconf.php | 4 ++ 4 files changed, 74 insertions(+) create mode 100644 Classes/Modules/Fluid/Templates.php create mode 100644 Resources/Private/Templates/Fluid/Templates.html diff --git a/Classes/Modules/Fluid/Templates.php b/Classes/Modules/Fluid/Templates.php new file mode 100644 index 0000000..05e50cd --- /dev/null +++ b/Classes/Modules/Fluid/Templates.php @@ -0,0 +1,66 @@ +getLanguageService()->sL( + 'LLL:EXT:adminpanel_extended/Resources/Private/Language/locallang_fluid.xlf:submodule.templates.label' + ); + } + + /** + * Main method for content generation of an admin panel module. + * Return content as HTML. For modules implementing the DataProviderInterface + * the "ModuleData" object is automatically filled with the stored data - if + * no data is given a "fresh" ModuleData object is injected. + * + * @param \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData $data + * @return string + */ + public function getContent(ModuleData $data): string + { + $view = GeneralUtility::makeInstance(StandaloneView::class); + + $view->setTemplatePathAndFilename('EXT:adminpanel_extended/Resources/Private/Templates/Fluid/Templates.html'); + $view->assignMultiple($data->getArrayCopy()); + + return $view->render(); + } +} diff --git a/Resources/Private/Language/locallang_fluid.xlf b/Resources/Private/Language/locallang_fluid.xlf index eff3277..62985e0 100644 --- a/Resources/Private/Language/locallang_fluid.xlf +++ b/Resources/Private/Language/locallang_fluid.xlf @@ -28,6 +28,9 @@ Priority + + Templates + diff --git a/Resources/Private/Templates/Fluid/Templates.html b/Resources/Private/Templates/Fluid/Templates.html new file mode 100644 index 0000000..e8df1a8 --- /dev/null +++ b/Resources/Private/Templates/Fluid/Templates.html @@ -0,0 +1 @@ +Dummy text diff --git a/ext_localconf.php b/ext_localconf.php index 09abd6c..b12a9ae 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -39,6 +39,10 @@ function () { 'submodules' => [ 'general' => [ 'module' => \Psychomieze\AdminpanelExtended\Modules\Fluid\General::class + ], + 'templates' => [ + 'module' => \Psychomieze\AdminpanelExtended\Modules\Fluid\Templates::class, + 'after' => ['general'] ] ] ]; From 57dfddaf0d2915a2f6f3aaf4b72209d1b722d66b Mon Sep 17 00:00:00 2001 From: kamiyang Date: Fri, 21 Jun 2019 18:10:19 +0200 Subject: [PATCH 02/10] [TASK] Display templates that have been rendered due this request --- Classes/Modules/Fluid/TemplatePaths.php | 56 +++++++++++++++++++ Classes/Modules/Fluid/Templates.php | 43 +++++++++++++- .../Private/Templates/Fluid/Templates.html | 30 +++++++++- ext_localconf.php | 4 ++ 4 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 Classes/Modules/Fluid/TemplatePaths.php diff --git a/Classes/Modules/Fluid/TemplatePaths.php b/Classes/Modules/Fluid/TemplatePaths.php new file mode 100644 index 0000000..bac8a9d --- /dev/null +++ b/Classes/Modules/Fluid/TemplatePaths.php @@ -0,0 +1,56 @@ +logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); + } + + public function resolveTemplateFileForControllerAndActionAndFormat($controller, $action, $format = null): ?string + { + $templateName = parent::resolveTemplateFileForControllerAndActionAndFormat( + $controller, + $action, + $format + ); + + $message = $templateName; + + + if (StringUtility::beginsWith($message, Environment::getExtensionsPath())) { + $message = str_replace(Environment::getExtensionsPath() . DIRECTORY_SEPARATOR, 'EXT:', $message); + } elseif (StringUtility::beginsWith($message, Environment::getFrameworkBasePath())) { + $message = str_replace(Environment::getFrameworkBasePath() . DIRECTORY_SEPARATOR, 'EXT:', $message); + } + + + $this->logger->log( + LogLevel::DEBUG, + $message, + [ + 'controller' => $controller, + 'action' => $action, + 'format' => $format ?? $this->getFormat() + ] + ); + + return $templateName; + } +} diff --git a/Classes/Modules/Fluid/Templates.php b/Classes/Modules/Fluid/Templates.php index 05e50cd..f1ce188 100644 --- a/Classes/Modules/Fluid/Templates.php +++ b/Classes/Modules/Fluid/Templates.php @@ -10,18 +10,21 @@ * LICENSE file that was distributed with this source code. */ +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Adminpanel\Log\InMemoryLogWriter; use TYPO3\CMS\Adminpanel\ModuleApi\AbstractSubModule; use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface; +use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface; use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData; +use TYPO3\CMS\Core\Log\LogRecord; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Fluid\View\StandaloneView; /** * Fluid templates submodule */ -class Templates extends AbstractSubModule implements ContentProviderInterface +class Templates extends AbstractSubModule implements ContentProviderInterface, DataProviderInterface { - /** * Identifier for this module, * for example "preview" or "cache" @@ -63,4 +66,40 @@ public function getContent(ModuleData $data): string return $view->render(); } + + /** + * @param \Psr\Http\Message\ServerRequestInterface $request + * @return \TYPO3\CMS\Adminpanel\ModuleApi\ModuleData + */ + public function getDataToStore(ServerRequestInterface $request): ModuleData + { + $log = InMemoryLogWriter::$log; + /** @var LogRecord[] $templateRecords */ + $templateRecords = array_filter( + $log, + static function (LogRecord $entry) { + return $entry->getComponent() === 'Psychomieze.AdminpanelExtended.Modules.Fluid.TemplatePaths'; + } + ); + + $templates = []; + foreach ($templateRecords as $logRecord) { + $data = $logRecord->getData(); + if (isset($templates[$logRecord->getMessage()])) { + $templates[$logRecord->getMessage()]['renderCount'] += 1; + } else { + $templates[$logRecord->getMessage()] = [ + 'controller' => $data['controller'], + 'action' => $data['action'], + 'format' => $data['format'], + 'renderCount' => 1 + ]; + } + } + + return new ModuleData([ + 'templates' => $templates, + 'requestId' => $request->getAttribute('adminPanelRequestId') + ]); + } } diff --git a/Resources/Private/Templates/Fluid/Templates.html b/Resources/Private/Templates/Fluid/Templates.html index e8df1a8..174f9cd 100644 --- a/Resources/Private/Templates/Fluid/Templates.html +++ b/Resources/Private/Templates/Fluid/Templates.html @@ -1 +1,29 @@ -Dummy text + + + + + + + + + + + + + + + + + + + + + + + + +
PathControllerActionFormatRender Times
{path}{data.controller}{data.action}{data.format}{data.renderCount}
+
+ No templates +
+ diff --git a/ext_localconf.php b/ext_localconf.php index b12a9ae..ae15268 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -46,5 +46,9 @@ function () { ] ] ]; + + $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Fluid\View\TemplatePaths::class] = [ + 'className' => \Psychomieze\AdminpanelExtended\Modules\Fluid\TemplatePaths::class + ]; } ); From 6a2497ac80982d6990cf4cd349a6141a4c1a2646 Mon Sep 17 00:00:00 2001 From: kamiyang Date: Fri, 21 Jun 2019 19:33:29 +0200 Subject: [PATCH 03/10] [FEATURE] Add Template fetching via ajax and google code prettify highlighting --- .../Controller/TemplatesAjaxController.php | 62 +++++++++++++++++ Classes/Modules/Fluid/TemplatePaths.php | 18 ++--- Classes/Modules/Fluid/Templates.php | 69 +++++++++++++++---- Configuration/Backend/AjaxRoutes.php | 4 ++ README.md | 4 ++ .../Private/Templates/Fluid/Templates.html | 8 +-- Resources/Public/Css/Templates.css | 28 ++++++++ Resources/Public/Css/vendor/desert.css | 17 +++++ Resources/Public/JavaScript/Templates.js | 68 ++++++++++++++++++ .../Public/JavaScript/vendor/prettify.js | 46 +++++++++++++ 10 files changed, 299 insertions(+), 25 deletions(-) create mode 100644 Classes/Controller/TemplatesAjaxController.php create mode 100644 Resources/Public/Css/Templates.css create mode 100644 Resources/Public/Css/vendor/desert.css create mode 100644 Resources/Public/JavaScript/Templates.js create mode 100644 Resources/Public/JavaScript/vendor/prettify.js diff --git a/Classes/Controller/TemplatesAjaxController.php b/Classes/Controller/TemplatesAjaxController.php new file mode 100644 index 0000000..ea99a1d --- /dev/null +++ b/Classes/Controller/TemplatesAjaxController.php @@ -0,0 +1,62 @@ +getQueryParams(); + $templateId = (string)($queryParams['templateId'] ?? ''); + $requestId = (string)($queryParams['requestId'] ?? ''); + $this->validateParameters($templateId, $requestId); + $moduleData = GeneralUtility::makeInstance(ModuleDataService::class) + ->getModuleDataByRequestId(Templates::class, $requestId); + $templateData = []; + $statusCode = 404; + + if ($moduleData instanceof ModuleData) { + $templateRecord = $moduleData['templates'][$templateId] ?? null; + if (is_array($templateRecord)) { + $absTemplatePath = GeneralUtility::getFileAbsFileName($templateRecord['path']); + + $content = file_get_contents($absTemplatePath); + $statusCode = 200; + $templateData['templateId'] = $templateId; + $templateData['template'] = $content; + } + } + + return new JsonResponse($templateData, $statusCode); + } + + private function validateParameters(string $templateId, string $requestId): void + { + if (!$templateId || !$requestId) { + throw new \InvalidArgumentException( + 'Missing parameters, templateId and requestId need to be set.', + 1561386190 + ); + } + } +} diff --git a/Classes/Modules/Fluid/TemplatePaths.php b/Classes/Modules/Fluid/TemplatePaths.php index bac8a9d..6f00293 100644 --- a/Classes/Modules/Fluid/TemplatePaths.php +++ b/Classes/Modules/Fluid/TemplatePaths.php @@ -31,23 +31,23 @@ public function resolveTemplateFileForControllerAndActionAndFormat($controller, $format ); - $message = $templateName; - - - if (StringUtility::beginsWith($message, Environment::getExtensionsPath())) { - $message = str_replace(Environment::getExtensionsPath() . DIRECTORY_SEPARATOR, 'EXT:', $message); - } elseif (StringUtility::beginsWith($message, Environment::getFrameworkBasePath())) { - $message = str_replace(Environment::getFrameworkBasePath() . DIRECTORY_SEPARATOR, 'EXT:', $message); + if (StringUtility::beginsWith($templateName, Environment::getExtensionsPath())) { + $path = str_replace(Environment::getExtensionsPath() . DIRECTORY_SEPARATOR, 'EXT:', $templateName); + } elseif (StringUtility::beginsWith($templateName, Environment::getFrameworkBasePath())) { + $path = str_replace(Environment::getFrameworkBasePath() . DIRECTORY_SEPARATOR, 'EXT:', $templateName); } + $format = $format ?? $this->getFormat(); + $identifier = uniqid("template-{$controller}-{$action}-{$format}-", false); $this->logger->log( LogLevel::DEBUG, - $message, + $identifier, [ + 'path' => $path ?? $templateName, 'controller' => $controller, 'action' => $action, - 'format' => $format ?? $this->getFormat() + 'format' => $format ] ); diff --git a/Classes/Modules/Fluid/Templates.php b/Classes/Modules/Fluid/Templates.php index f1ce188..e5b7a43 100644 --- a/Classes/Modules/Fluid/Templates.php +++ b/Classes/Modules/Fluid/Templates.php @@ -16,6 +16,8 @@ use TYPO3\CMS\Adminpanel\ModuleApi\ContentProviderInterface; use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface; use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData; +use TYPO3\CMS\Adminpanel\ModuleApi\ResourceProviderInterface; +use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Log\LogRecord; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Fluid\View\StandaloneView; @@ -23,7 +25,7 @@ /** * Fluid templates submodule */ -class Templates extends AbstractSubModule implements ContentProviderInterface, DataProviderInterface +class Templates extends AbstractSubModule implements ContentProviderInterface, DataProviderInterface, ResourceProviderInterface { /** * Identifier for this module, @@ -63,6 +65,11 @@ public function getContent(ModuleData $data): string $view->setTemplatePathAndFilename('EXT:adminpanel_extended/Resources/Private/Templates/Fluid/Templates.html'); $view->assignMultiple($data->getArrayCopy()); + $url = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute( + 'ajax_adminPanelExtended_templateData', + ['requestId' => $data['requestId']] + ); + $view->assign('templateDataUri', $url); return $view->render(); } @@ -83,23 +90,61 @@ static function (LogRecord $entry) { ); $templates = []; + foreach ($templateRecords as $logRecord) { - $data = $logRecord->getData(); - if (isset($templates[$logRecord->getMessage()])) { - $templates[$logRecord->getMessage()]['renderCount'] += 1; - } else { - $templates[$logRecord->getMessage()] = [ - 'controller' => $data['controller'], - 'action' => $data['action'], - 'format' => $data['format'], - 'renderCount' => 1 - ]; - } + $templates[$logRecord->getMessage()] = $logRecord->getData(); } + $templates = array_unique($templates, SORT_REGULAR); + return new ModuleData([ 'templates' => $templates, 'requestId' => $request->getAttribute('adminPanelRequestId') ]); } + + protected function isTemplateAlreadyRendered(array $templates, LogRecord $logRecord): bool + { + foreach ($templates as $template) { + if ($template['path'] === $logRecord->getData()['path'] + && $template['controller'] === $logRecord->getData()['controller'] + && $template['action'] === $logRecord->getData()['action'] + && $template['format'] === $logRecord->getData()['format'] + ) { + return true; + } + } + + return false; + } + + /** + * Returns a string array with javascript files that will be rendered after the module + * + * Example: return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/Edit.js']; + * + * @return array + */ + public function getJavaScriptFiles(): array + { + return [ + 'EXT:adminpanel_extended/Resources/Public/JavaScript/Templates.js', + 'EXT:adminpanel_extended/Resources/Public/JavaScript/vendor/prettify.js', + ]; + } + + /** + * Returns a string array with css files that will be rendered after the module + * + * Example: return ['EXT:adminpanel/Resources/Public/JavaScript/Modules/Edit.css']; + * + * @return array + */ + public function getCssFiles(): array + { + return [ + 'EXT:adminpanel_extended/Resources/Public/Css/Templates.css', + 'EXT:adminpanel_extended/Resources/Public/Css/vendor/desert.css' + ]; + } } diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index 0a18fe7..95e2340 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -14,4 +14,8 @@ 'path' => '/adminpanelExtended/signals/data', 'target' => \Psychomieze\AdminpanelExtended\Controller\SignalsAjaxController::class . '::getData' ], + 'adminPanelExtended_templateData' => [ + 'path' => '/adminpanelExtended/templates/data', + 'target' => \Psychomieze\AdminpanelExtended\Controller\TemplatesAjaxController::class . '::getData' + ], ]; diff --git a/README.md b/README.md index 5e55816..1144090 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Currently in alpha phase! - Show currently online users and info if someone is editing current page - Show fluid global namespaces - Show fluid preprocessors +- Show fluid templates that have been rendered with code highlighting ### Development @@ -25,3 +26,6 @@ initiative, see + +## Misc +This project is highly inspired by the symfony profiler: https://symfony.com/doc/current/profiler.html diff --git a/Resources/Private/Templates/Fluid/Templates.html b/Resources/Private/Templates/Fluid/Templates.html index 174f9cd..701a237 100644 --- a/Resources/Private/Templates/Fluid/Templates.html +++ b/Resources/Private/Templates/Fluid/Templates.html @@ -8,17 +8,17 @@ Controller Action Format - Render Times - + - {path} + + {data.path} + {data.controller} {data.action} {data.format} - {data.renderCount} diff --git a/Resources/Public/Css/Templates.css b/Resources/Public/Css/Templates.css new file mode 100644 index 0000000..825c47b --- /dev/null +++ b/Resources/Public/Css/Templates.css @@ -0,0 +1,28 @@ +/* + * This file is part of the TYPO3 Adminpanel Initiative. + * + * For the full copyright and license information, please read the + * LICENSE file that was distributed with this source code. + */ +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint { + background-color: #333; +} +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint *{ + white-space: pre; +} +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L0, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L1, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L2, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L3, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L5, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L6, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L7, +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint li.L8 { + list-style-type: decimal; +} +#TSFE_ADMIN_PANEL_FORM.typo3-kidjls9dksoje.typo3-adminPanel pre.prettyprint ol.linenums { + padding-left: 40px; + margin-top: 0; + margin-bottom: 0; + color: #AEAEAE; +} diff --git a/Resources/Public/Css/vendor/desert.css b/Resources/Public/Css/vendor/desert.css new file mode 100644 index 0000000..ba7d735 --- /dev/null +++ b/Resources/Public/Css/vendor/desert.css @@ -0,0 +1,17 @@ +/* + + Copyright (C) 2006 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +pre .atn,pre .kwd,pre .tag{font-weight:700}pre.prettyprint{display:block;background-color:#333}pre .nocode{background-color:none;color:#000}pre .str{color:#ffa0a0}pre .kwd{color:khaki}pre .com{color:#87ceeb}pre .typ{color:#98fb98}pre .lit{color:#cd5c5c}pre .pln,pre .pun{color:#fff}pre .tag{color:khaki}pre .atn{color:#bdb76b}pre .atv{color:#ffa0a0}pre .dec{color:#98fb98}ol.linenums{margin-top:0;margin-bottom:0;color:#AEAEAE}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}@media print{pre.prettyprint{background-color:none}code .str,pre .str{color:#060}code .kwd,pre .kwd{color:#006;font-weight:700}code .com,pre .com{color:#600;font-style:italic}code .typ,pre .typ{color:#404;font-weight:700}code .lit,pre .lit{color:#044}code .pun,pre .pun{color:#440}code .pln,pre .pln{color:#000}code .tag,pre .tag{color:#006;font-weight:700}code .atn,pre .atn{color:#404}code .atv,pre .atv{color:#060}} diff --git a/Resources/Public/JavaScript/Templates.js b/Resources/Public/JavaScript/Templates.js new file mode 100644 index 0000000..41a271f --- /dev/null +++ b/Resources/Public/JavaScript/Templates.js @@ -0,0 +1,68 @@ +/* + * This file is part of the TYPO3 Adminpanel Initiative. + * + * For the full copyright and license information, please read the + * LICENSE file that was distributed with this source code. + */ +const Templates = {}; + +Templates.initialize = function () { + const templateLinks = document.querySelectorAll('[data-typo3-role=template-link]'); + for (let i = 0; i < templateLinks.length; i++) { + templateLinks[i].addEventListener('click', Templates.getTemplateData); + } +}; +Templates.getTemplateData = function (event) { + event.preventDefault(); + const uri = this.dataset.typo3AjaxUrl; + const request = new XMLHttpRequest(); + request.open('GET', uri); + request.send(); + + /** + * @param template + * @returns {HTMLTableRowElement} + */ + function createTableRow(template) { + const tableRow = document.createElement('tr'); + const templateData = document.createElement('td'); + const pre = document.createElement('pre'); + pre.setAttribute('class', 'prettyprint lang-html linenums'); + const code = document.createElement('code'); + code.innerText = template; + templateData.setAttribute('colspan', '5'); + + pre.appendChild(code); + templateData.appendChild(pre); + tableRow.appendChild(templateData); + return tableRow; + }; + + request.onreadystatechange = function () { + if (this.readyState === 4 && this.status === 200) { + const data = JSON.parse(this.responseText); + + const target = document.querySelector('[data-typo3-template-id=' + data.templateId + ']'); + const templateRow = createTableRow(data.template); + templateRow.id = data.templateId; + target.parentNode.parentNode.parentNode.insertBefore(templateRow, target.parentNode.parentNode.nextSibling); + + target.removeEventListener('click', Templates.getTemplateData); + PR.prettyPrint(); // Use google code prettify to pre format the template (https://github.com/google/code-prettify) + target.addEventListener('click', Templates.toggleRow); + } + }; +}; + +Templates.toggleRow = function () { + const templateId = this.dataset.typo3TemplateId; + let row = document.getElementById(templateId); + + if (row.style.display === 'none') { + row.style.display = 'table-row'; + } else { + row.style.display = 'none'; + } +}; + +window.addEventListener('load', Templates.initialize, false); diff --git a/Resources/Public/JavaScript/vendor/prettify.js b/Resources/Public/JavaScript/vendor/prettify.js new file mode 100644 index 0000000..477f03d --- /dev/null +++ b/Resources/Public/JavaScript/vendor/prettify.js @@ -0,0 +1,46 @@ +!function(){/* + + Copyright (C) 2006 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +"undefined"!==typeof window&&(window.PR_SHOULD_USE_CONTINUATION=!0); +(function(){function T(a){function d(e){var a=e.charCodeAt(0);if(92!==a)return a;var c=e.charAt(1);return(a=w[c])?a:"0"<=c&&"7">=c?parseInt(e.substring(1),8):"u"===c||"x"===c?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function c(e){var c=e.substring(1,e.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g")); +e=[];var a="^"===c[0],b=["["];a&&b.push("^");for(var a=a?1:0,g=c.length;ak||122k||90k||122h[0]&&(h[1]+1>h[0]&&b.push("-"),b.push(f(h[1])));b.push("]");return b.join("")}function m(e){for(var a=e.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),b=a.length,d=[],g=0,h=0;g/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null]));if(c=a.regexLiterals){var m=(c=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+ +("/(?=[^/*"+c+"])(?:[^/\\x5B\\x5C"+c+"]|\\x5C"+m+"|\\x5B(?:[^\\x5C\\x5D"+c+"]|\\x5C"+m+")*(?:\\x5D|$))+/")+")")])}(c=a.types)&&f.push(["typ",c]);c=(""+a.keywords).replace(/^ | $/g,"");c.length&&f.push(["kwd",new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);c="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(c+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i, +null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(c),null]);return G(d,f)}function L(a,d,f){function c(a){var b=a.nodeType;if(1==b&&!t.test(a.className))if("br"===a.nodeName.toLowerCase())m(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)c(a);else if((3==b||4==b)&&f){var e=a.nodeValue,d=e.match(q);d&&(b=e.substring(0,d.index),a.nodeValue=b,(e=e.substring(d.index+ +d[0].length))&&a.parentNode.insertBefore(l.createTextNode(e),a.nextSibling),m(a),b||a.parentNode.removeChild(a))}}function m(a){function c(a,b){var e=b?a.cloneNode(!1):a,k=a.parentNode;if(k){var k=c(k,1),d=a.nextSibling;k.appendChild(e);for(var f=d;f;f=d)d=f.nextSibling,k.appendChild(f)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=c(a.nextSibling,0);for(var e;(e=a.parentNode)&&1===e.nodeType;)a=e;b.push(a)}for(var t=/(?:^|\s)nocode(?:\s|$)/,q=/\r\n?|\n/,l=a.ownerDocument,n=l.createElement("li");a.firstChild;)n.appendChild(a.firstChild); +for(var b=[n],p=0;p=+m[1],d=/\n/g,t=a.a,q=t.length,f=0,l=a.c,n=l.length,c=0,b=a.g,p=b.length,w=0;b[p]=q;var r,e;for(e=r=0;e=h&&(c+=2);f>=k&&(w+=2)}}finally{g&&(g.style.display=a)}}catch(y){D.console&&console.log(y&&y.stack||y)}}var D="undefined"!==typeof window? +window:{},B=["break,continue,do,else,for,if,return,while"],F=[[B,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],H=[F,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"], +O=[F,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],P=[F,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"], +F=[F,"abstract,async,await,constructor,debugger,enum,eval,export,from,function,get,import,implements,instanceof,interface,let,null,of,set,undefined,var,with,yield,Infinity,NaN"],Q=[B,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],R=[B,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"], +B=[B,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],S=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,W=/\S/,X=x({keywords:[H,P,O,F,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",Q,R,B],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}), +I={};t(X,["default-code"]);t(G([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));t(G([["pln",/^[\s]+/, +null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]); +t(G([],[["atv",/^[\s\S]+/]]),["uq.val"]);t(x({keywords:H,hashComments:!0,cStyleComments:!0,types:S}),"c cc cpp cxx cyc m".split(" "));t(x({keywords:"null,true,false"}),["json"]);t(x({keywords:P,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:S}),["cs"]);t(x({keywords:O,cStyleComments:!0}),["java"]);t(x({keywords:B,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);t(x({keywords:Q,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);t(x({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END", +hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);t(x({keywords:R,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);t(x({keywords:F,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]);t(x({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0, +regexLiterals:!0}),["coffee"]);t(G([],[["str",/^[\s\S]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:G,registerLangHandler:t,sourceDecorator:x,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,f){f=f||!1;d=d||null;var c=document.createElement("div");c.innerHTML="
"+a+"
"; +c=c.firstChild;f&&L(c,f,!0);M({j:d,m:f,h:c,l:1,a:null,i:null,c:null,g:null});return c.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function f(){for(var c=D.PR_SHOULD_USE_CONTINUATION?b.now()+250:Infinity;p Date: Tue, 25 Jun 2019 16:14:20 +0200 Subject: [PATCH 04/10] [FEATURE] Change return type and fix markdown link usage --- Classes/Controller/TemplatesAjaxController.php | 3 +-- README.md | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/Controller/TemplatesAjaxController.php b/Classes/Controller/TemplatesAjaxController.php index ea99a1d..48ba7fc 100644 --- a/Classes/Controller/TemplatesAjaxController.php +++ b/Classes/Controller/TemplatesAjaxController.php @@ -10,7 +10,6 @@ * LICENSE file that was distributed with this source code. */ -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; @@ -24,7 +23,7 @@ class TemplatesAjaxController implements LoggerAwareInterface { use LoggerAwareTrait; - public function getData(ServerRequestInterface $request): ResponseInterface + public function getData(ServerRequestInterface $request): JsonResponse { $queryParams = $request->getQueryParams(); $templateId = (string)($queryParams['templateId'] ?? ''); diff --git a/README.md b/README.md index 1144090..6d35c19 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,5 @@ initiative, see ## Misc -This project is highly inspired by the symfony profiler: https://symfony.com/doc/current/profiler.html + +This project is highly inspired by the symfony profiler: From 47ee192fea739f9e70dc95a09c2a9c1abb6fcc57 Mon Sep 17 00:00:00 2001 From: kamiyang Date: Tue, 25 Jun 2019 16:24:57 +0200 Subject: [PATCH 05/10] [FEATURE] Introduce test for ajax controller --- .../Controller/SignalsAjaxControllerTest.php | 7 ++ .../TemplatesAjaxControllerTest.php | 97 +++++++++++++++++++ .../Repository/UserSessionRepositoryTest.php | 7 ++ 3 files changed, 111 insertions(+) create mode 100644 Tests/Unit/Controller/TemplatesAjaxControllerTest.php diff --git a/Tests/Unit/Controller/SignalsAjaxControllerTest.php b/Tests/Unit/Controller/SignalsAjaxControllerTest.php index e245292..5f16da9 100644 --- a/Tests/Unit/Controller/SignalsAjaxControllerTest.php +++ b/Tests/Unit/Controller/SignalsAjaxControllerTest.php @@ -3,6 +3,13 @@ namespace Psychomieze\AdminpanelExtended\Tests\Unit\Controller; +/* + * This file is part of the TYPO3 Adminpanel Initiative. + * + * For the full copyright and license information, please read the + * LICENSE file that was distributed with this source code. + */ + use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ServerRequestInterface; use Psychomieze\AdminpanelExtended\Controller\SignalsAjaxController; diff --git a/Tests/Unit/Controller/TemplatesAjaxControllerTest.php b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php new file mode 100644 index 0000000..82a5db0 --- /dev/null +++ b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php @@ -0,0 +1,97 @@ +subject = new TemplatesAjaxController(); + $this->request = new ServerRequest(); + $this->request = $this->request->withQueryParams(['templateId' => 'some id', 'requestId' => 'some id']); + } + + /** + * @test + * @param array $queryParams + * @dataProvider queryParamsDataProvider + */ + public function getDataThrowsExceptionValidatesRequiredParameters(array $queryParams): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Missing parameters, templateId and requestId need to be set.'); + $this->expectExceptionCode(1561386190); + + $this->request = $this->request->withQueryParams($queryParams); + $actual = $this->subject->getData($this->request); + + self::assertSame($queryParams, $this->request->getQueryParams()); + } + + /** + * @test + */ + public function getDataReturnsEmptyJsonResponseIfModuleDataIsNotFound(): void + { + $moduleDataService = $this->prophesize(ModuleDataService::class); + $moduleDataService->getModuleDataByRequestId(Templates::class, Argument::any()) + ->willReturn(null); + + $response = $this->subject->getData($this->request); + + static::assertSame(404, $response->getStatusCode()); + static::assertSame('', $response->getBody()->getContents()); + } + + /** + * @return array + */ + public function queryParamsDataProvider(): array + { + return [ + 'empty query params' => [ + 'queryParams' => [] + ], + 'only template id' => [ + 'queryParams' => ['templateId' => 'some id'] + ], + 'only request id' => [ + 'queryParams' => ['requestId' => 'some id'] + ], + 'empty template id' => [ + 'queryParams' => ['templateId' => '', 'requestId' => 'some id'] + ], + 'empty request id' => [ + 'queryParams' => ['templateId' => 'some id', 'requestId' => ''] + ], + 'empty request id and template id' => [ + 'queryParams' => ['templateId' => '', 'requestId' => ''] + ] + ]; + } +} diff --git a/Tests/Unit/Domain/Repository/UserSessionRepositoryTest.php b/Tests/Unit/Domain/Repository/UserSessionRepositoryTest.php index 1670263..2784c4e 100644 --- a/Tests/Unit/Domain/Repository/UserSessionRepositoryTest.php +++ b/Tests/Unit/Domain/Repository/UserSessionRepositoryTest.php @@ -3,6 +3,13 @@ namespace Psychomieze\AdminpanelExtended\Tests\Unit\Domain\Repository; +/* + * This file is part of the TYPO3 Adminpanel Initiative. + * + * For the full copyright and license information, please read the + * LICENSE file that was distributed with this source code. + */ + use Psychomieze\AdminpanelExtended\Domain\Repository\UserSessionRepository; use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface; use TYPO3\CMS\Core\Session\SessionManager; From 18d1e3dfdc3f41a85627af0a7376dc7248f767af Mon Sep 17 00:00:00 2001 From: kamiyang Date: Tue, 25 Jun 2019 16:57:59 +0200 Subject: [PATCH 06/10] [FEATURE] Introduce templates module test --- Tests/Unit/Modules/Fluid/TemplatesTest.php | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Tests/Unit/Modules/Fluid/TemplatesTest.php diff --git a/Tests/Unit/Modules/Fluid/TemplatesTest.php b/Tests/Unit/Modules/Fluid/TemplatesTest.php new file mode 100644 index 0000000..e30cb36 --- /dev/null +++ b/Tests/Unit/Modules/Fluid/TemplatesTest.php @@ -0,0 +1,96 @@ +subject = new Templates(); + } + + /** + * @test + */ + public function getIdentifier(): void + { + static::assertSame('psychomieze_fluid_templates', $this->subject->getIdentifier()); + } + + /** + * @test + */ + public function getLabel(): void + { + $languageService = $this->prophesize(LanguageService::class); + $languageService->sL('LLL:EXT:adminpanel_extended/Resources/Private/Language/locallang_fluid.xlf:submodule.templates.label') + ->willReturn('some label'); + $GLOBALS['LANG'] = $languageService->reveal(); + + self::assertSame('some label', $this->subject->getLabel()); + + unset($GLOBALS['LANG']); + } + + /** + * @test + */ + public function getCssFiles(): void + { + static::assertSame( + [ + 'EXT:adminpanel_extended/Resources/Public/Css/Templates.css', + 'EXT:adminpanel_extended/Resources/Public/Css/vendor/desert.css' + ], + $this->subject->getCssFiles() + ); + } + + /** + * @test + */ + public function getContent(): void + { + $actual = $this->subject->getContent(); + } + + /** + * @test + */ + public function getJavaScriptFiles(): void + { + static::assertSame( + [ + 'EXT:adminpanel_extended/Resources/Public/JavaScript/Templates.js', + 'EXT:adminpanel_extended/Resources/Public/JavaScript/vendor/prettify.js' + ], + $this->subject->getJavaScriptFiles() + ); + } + + /** + * @test + */ + public function getDataToStore(): void + { + $actual = $this->subject->getDataToStore(); + } +} From 6fd6fd243e07cbf39e99a99af41e0c4be1a7d89d Mon Sep 17 00:00:00 2001 From: kamiyang Date: Wed, 26 Jun 2019 16:27:40 +0200 Subject: [PATCH 07/10] [FEATURE] Add test for getContent and remove unused method --- Classes/Modules/Fluid/TemplatePaths.php | 39 ++++++++++++++++++++++ Classes/Modules/Fluid/Templates.php | 15 --------- Tests/Unit/Modules/Fluid/TemplatesTest.php | 37 ++++++++++++++------ 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Classes/Modules/Fluid/TemplatePaths.php b/Classes/Modules/Fluid/TemplatePaths.php index 6f00293..1b10154 100644 --- a/Classes/Modules/Fluid/TemplatePaths.php +++ b/Classes/Modules/Fluid/TemplatePaths.php @@ -3,6 +3,13 @@ namespace Psychomieze\AdminpanelExtended\Modules\Fluid; +/* + * This file is part of the TYPO3 Adminpanel Initiative. + * + * For the full copyright and license information, please read the + * LICENSE file that was distributed with this source code. + */ + use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Log\LogLevel; use TYPO3\CMS\Core\Log\LogManager; @@ -16,6 +23,9 @@ class TemplatePaths extends \TYPO3\CMS\Fluid\View\TemplatePaths */ private $logger; + /** + * @param array|string|NULL $packageNameOrArray + */ public function __construct($packageNameOrArray = null) { parent::__construct($packageNameOrArray); @@ -23,6 +33,35 @@ public function __construct($packageNameOrArray = null) $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__); } + /** + * Attempts to resolve an absolute filename + * of a template (i.e. `templateRootPaths`) + * using a controller name, action and format. + * + * Works _backwards_ through template paths in + * order to achieve an "overlay"-type behavior + * where the last paths added are the first to + * be checked and the first path added acts as + * fallback if no other paths have the file. + * + * If the file does not exist in any path, + * including fallback path, `NULL` is returned. + * + * Path configurations filled from TypoScript + * is automatically recorded in the right + * order (see `fillFromTypoScriptArray`), but + * when manually setting the paths that should + * be checked, you as user must be aware of + * this reverse behavior (which you should + * already be, given that it is the same way + * TypoScript path configurations work). + * + * @param string $controller + * @param string $action + * @param string $format + * @return string|NULL + * @api + */ public function resolveTemplateFileForControllerAndActionAndFormat($controller, $action, $format = null): ?string { $templateName = parent::resolveTemplateFileForControllerAndActionAndFormat( diff --git a/Classes/Modules/Fluid/Templates.php b/Classes/Modules/Fluid/Templates.php index e5b7a43..e995f1e 100644 --- a/Classes/Modules/Fluid/Templates.php +++ b/Classes/Modules/Fluid/Templates.php @@ -103,21 +103,6 @@ static function (LogRecord $entry) { ]); } - protected function isTemplateAlreadyRendered(array $templates, LogRecord $logRecord): bool - { - foreach ($templates as $template) { - if ($template['path'] === $logRecord->getData()['path'] - && $template['controller'] === $logRecord->getData()['controller'] - && $template['action'] === $logRecord->getData()['action'] - && $template['format'] === $logRecord->getData()['format'] - ) { - return true; - } - } - - return false; - } - /** * Returns a string array with javascript files that will be rendered after the module * diff --git a/Tests/Unit/Modules/Fluid/TemplatesTest.php b/Tests/Unit/Modules/Fluid/TemplatesTest.php index e30cb36..50686ef 100644 --- a/Tests/Unit/Modules/Fluid/TemplatesTest.php +++ b/Tests/Unit/Modules/Fluid/TemplatesTest.php @@ -11,11 +11,16 @@ */ use Psychomieze\AdminpanelExtended\Modules\Fluid\Templates; +use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData; +use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Fluid\View\StandaloneView; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class TemplatesTest extends UnitTestCase { + protected $resetSingletonInstances = true; /** * @var \Psychomieze\AdminpanelExtended\Modules\Fluid\Templates @@ -58,7 +63,7 @@ public function getCssFiles(): void static::assertSame( [ 'EXT:adminpanel_extended/Resources/Public/Css/Templates.css', - 'EXT:adminpanel_extended/Resources/Public/Css/vendor/desert.css' + 'EXT:adminpanel_extended/Resources/Public/Css/vendor/desert.css', ], $this->subject->getCssFiles() ); @@ -69,7 +74,25 @@ public function getCssFiles(): void */ public function getContent(): void { - $actual = $this->subject->getContent(); + $moduleData = new ModuleData(); + $moduleData['requestId'] = 'some request id'; + + $view = $this->prophesize(StandaloneView::class); + $view->setTemplatePathAndFilename('EXT:adminpanel_extended/Resources/Private/Templates/Fluid/Templates.html')->shouldBeCalled(); + $view->assignMultiple($moduleData->getArrayCopy())->shouldBeCalled(); + $view->assign('templateDataUri', 'some url')->shouldBeCalled(); + $view->render()->willReturn('some html'); + GeneralUtility::addInstance(StandaloneView::class, $view->reveal()); + + $uriBuilder = $this->prophesize(UriBuilder::class); + GeneralUtility::setSingletonInstance(UriBuilder::class, $uriBuilder->reveal()); + + $uriBuilder->buildUriFromRoute( + 'ajax_adminPanelExtended_templateData', + ['requestId' => $moduleData['requestId']] + )->willReturn('some url'); + + $this->subject->getContent($moduleData); } /** @@ -80,17 +103,9 @@ public function getJavaScriptFiles(): void static::assertSame( [ 'EXT:adminpanel_extended/Resources/Public/JavaScript/Templates.js', - 'EXT:adminpanel_extended/Resources/Public/JavaScript/vendor/prettify.js' + 'EXT:adminpanel_extended/Resources/Public/JavaScript/vendor/prettify.js', ], $this->subject->getJavaScriptFiles() ); } - - /** - * @test - */ - public function getDataToStore(): void - { - $actual = $this->subject->getDataToStore(); - } } From bec48f801cf54f9ea26d7ad2b7867626604c134c Mon Sep 17 00:00:00 2001 From: kamiyang Date: Wed, 26 Jun 2019 16:46:26 +0200 Subject: [PATCH 08/10] [FEATURE] Adjust test to use mock instead of GeneralUtility --- .../TemplatesAjaxControllerTest.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/Tests/Unit/Controller/TemplatesAjaxControllerTest.php b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php index 82a5db0..41ec789 100644 --- a/Tests/Unit/Controller/TemplatesAjaxControllerTest.php +++ b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php @@ -14,7 +14,9 @@ use Psychomieze\AdminpanelExtended\Controller\TemplatesAjaxController; use Psychomieze\AdminpanelExtended\Modules\Fluid\Templates; use Psychomieze\AdminpanelExtended\Service\ModuleDataService; +use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData; use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class TemplatesAjaxControllerTest extends UnitTestCase @@ -55,12 +57,15 @@ public function getDataThrowsExceptionValidatesRequiredParameters(array $queryPa /** * @test + * @dataProvider moduleDataProvider + * @param $moduleData */ - public function getDataReturnsEmptyJsonResponseIfModuleDataIsNotFound(): void + public function getDataReturnsEmptyJsonResponseIfModuleDataIsNotFoundOrTemplateRecordIsNotAnArray($moduleData): void { $moduleDataService = $this->prophesize(ModuleDataService::class); $moduleDataService->getModuleDataByRequestId(Templates::class, Argument::any()) - ->willReturn(null); + ->willReturn($moduleData); + GeneralUtility::addInstance(ModuleDataService::class, $moduleDataService->reveal()); $response = $this->subject->getData($this->request); @@ -75,23 +80,31 @@ public function queryParamsDataProvider(): array { return [ 'empty query params' => [ - 'queryParams' => [] + 'queryParams' => [], ], 'only template id' => [ - 'queryParams' => ['templateId' => 'some id'] + 'queryParams' => ['templateId' => 'some id'], ], 'only request id' => [ - 'queryParams' => ['requestId' => 'some id'] + 'queryParams' => ['requestId' => 'some id'], ], 'empty template id' => [ - 'queryParams' => ['templateId' => '', 'requestId' => 'some id'] + 'queryParams' => ['templateId' => '', 'requestId' => 'some id'], ], 'empty request id' => [ - 'queryParams' => ['templateId' => 'some id', 'requestId' => ''] + 'queryParams' => ['templateId' => 'some id', 'requestId' => ''], ], 'empty request id and template id' => [ - 'queryParams' => ['templateId' => '', 'requestId' => ''] - ] + 'queryParams' => ['templateId' => '', 'requestId' => ''], + ], + ]; + } + + public function moduleDataProvider(): array + { + return [ + ['moduleData' => null], + ['moudleData' => new ModuleData()], ]; } } From d4123e1f657677e42b960ecb585ed85e527ff2cd Mon Sep 17 00:00:00 2001 From: kamiyang Date: Thu, 27 Jun 2019 17:11:43 +0200 Subject: [PATCH 09/10] [FEATURE] Add test for correctly fetching template file content --- .../Controller/TemplatesAjaxController.php | 11 ++-- .../TemplatesAjaxControllerTest.php | 61 +++++++++++++++++-- Tests/Unit/Fixtures/DummyTemplate.html | 1 + 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 Tests/Unit/Fixtures/DummyTemplate.html diff --git a/Classes/Controller/TemplatesAjaxController.php b/Classes/Controller/TemplatesAjaxController.php index 48ba7fc..ebc4b36 100644 --- a/Classes/Controller/TemplatesAjaxController.php +++ b/Classes/Controller/TemplatesAjaxController.php @@ -38,11 +38,12 @@ public function getData(ServerRequestInterface $request): JsonResponse $templateRecord = $moduleData['templates'][$templateId] ?? null; if (is_array($templateRecord)) { $absTemplatePath = GeneralUtility::getFileAbsFileName($templateRecord['path']); - - $content = file_get_contents($absTemplatePath); - $statusCode = 200; - $templateData['templateId'] = $templateId; - $templateData['template'] = $content; + if (GeneralUtility::isAllowedAbsPath($absTemplatePath) && file_exists($absTemplatePath)) { + $content = file_get_contents($absTemplatePath); + $statusCode = 200; + $templateData['templateId'] = $templateId; + $templateData['template'] = $content; + } } } diff --git a/Tests/Unit/Controller/TemplatesAjaxControllerTest.php b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php index 41ec789..807a701 100644 --- a/Tests/Unit/Controller/TemplatesAjaxControllerTest.php +++ b/Tests/Unit/Controller/TemplatesAjaxControllerTest.php @@ -33,6 +33,8 @@ class TemplatesAjaxControllerTest extends UnitTestCase protected function setUp(): void { + parent::setUp(); + $this->subject = new TemplatesAjaxController(); $this->request = new ServerRequest(); $this->request = $this->request->withQueryParams(['templateId' => 'some id', 'requestId' => 'some id']); @@ -62,10 +64,24 @@ public function getDataThrowsExceptionValidatesRequiredParameters(array $queryPa */ public function getDataReturnsEmptyJsonResponseIfModuleDataIsNotFoundOrTemplateRecordIsNotAnArray($moduleData): void { - $moduleDataService = $this->prophesize(ModuleDataService::class); - $moduleDataService->getModuleDataByRequestId(Templates::class, Argument::any()) - ->willReturn($moduleData); - GeneralUtility::addInstance(ModuleDataService::class, $moduleDataService->reveal()); + $this->mockModuleDataService($moduleData); + + $response = $this->subject->getData($this->request); + + static::assertSame(404, $response->getStatusCode()); + static::assertSame('', $response->getBody()->getContents()); + } + + /** + * @test + * @dataProvider invalidPathDataProvider + * @param string $path + */ + public function getDataReturnsEmptyJsonResponseOnInvalidPath(string $path): void + { + $moduleData = new ModuleData(); + $moduleData['templates']['some id'] = ['path' => $path]; + $this->mockModuleDataService($moduleData); $response = $this->subject->getData($this->request); @@ -73,6 +89,25 @@ public function getDataReturnsEmptyJsonResponseIfModuleDataIsNotFoundOrTemplateR static::assertSame('', $response->getBody()->getContents()); } + /** + * @test + */ + public function getDataReturnsJsonResponseWithHtmlContentAsTemplate(): void + { + $path = 'EXT:adminpanel_extended/Tests/Unit/Fixtures/DummyTemplate.html'; + $moduleData = new ModuleData(); + $moduleData['templates']['some id'] = ['path' => $path]; + $this->mockModuleDataService($moduleData); + + $response = $this->subject->getData($this->request); + + static::assertSame(200, $response->getStatusCode()); + $jsonDecodedContent = json_decode($response->getBody()->getContents(), true); + static::assertArrayHasKey('templateId', $jsonDecodedContent); + static::assertArrayHasKey('template', $jsonDecodedContent); + static::assertStringContainsString('some html code', $jsonDecodedContent['template']); + } + /** * @return array */ @@ -107,4 +142,22 @@ public function moduleDataProvider(): array ['moudleData' => new ModuleData()], ]; } + + /** + * @param $moduleData + */ + protected function mockModuleDataService($moduleData): void + { + $moduleDataService = $this->prophesize(ModuleDataService::class); + $moduleDataService->getModuleDataByRequestId(Templates::class, Argument::any())->willReturn($moduleData); + GeneralUtility::addInstance(ModuleDataService::class, $moduleDataService->reveal()); + } + + public function invalidPathDataProvider(): array + { + return [ + 'non existing relative template path' => ['foo'], + 'unallowed abs filename' => ['/baa'], + ]; + } } diff --git a/Tests/Unit/Fixtures/DummyTemplate.html b/Tests/Unit/Fixtures/DummyTemplate.html new file mode 100644 index 0000000..e97820c --- /dev/null +++ b/Tests/Unit/Fixtures/DummyTemplate.html @@ -0,0 +1 @@ +some html code From 3e58ff6310857af4faaddedc9a54f00246db699a Mon Sep 17 00:00:00 2001 From: kamiyang Date: Thu, 27 Jun 2019 17:50:48 +0200 Subject: [PATCH 10/10] [FEATURE] Introduce TemplatePathsTest --- Classes/Modules/Fluid/TemplatePaths.php | 36 +++++----- .../Unit/Modules/Fluid/TemplatePathsTest.php | 67 +++++++++++++++++++ 2 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 Tests/Unit/Modules/Fluid/TemplatePathsTest.php diff --git a/Classes/Modules/Fluid/TemplatePaths.php b/Classes/Modules/Fluid/TemplatePaths.php index 1b10154..527c996 100644 --- a/Classes/Modules/Fluid/TemplatePaths.php +++ b/Classes/Modules/Fluid/TemplatePaths.php @@ -70,25 +70,27 @@ public function resolveTemplateFileForControllerAndActionAndFormat($controller, $format ); - if (StringUtility::beginsWith($templateName, Environment::getExtensionsPath())) { - $path = str_replace(Environment::getExtensionsPath() . DIRECTORY_SEPARATOR, 'EXT:', $templateName); - } elseif (StringUtility::beginsWith($templateName, Environment::getFrameworkBasePath())) { - $path = str_replace(Environment::getFrameworkBasePath() . DIRECTORY_SEPARATOR, 'EXT:', $templateName); - } + if (is_string($templateName)) { + if (StringUtility::beginsWith($templateName, Environment::getExtensionsPath())) { + $path = str_replace(Environment::getExtensionsPath().DIRECTORY_SEPARATOR, 'EXT:', $templateName); + } elseif (StringUtility::beginsWith($templateName, Environment::getFrameworkBasePath())) { + $path = str_replace(Environment::getFrameworkBasePath().DIRECTORY_SEPARATOR, 'EXT:', $templateName); + } - $format = $format ?? $this->getFormat(); - $identifier = uniqid("template-{$controller}-{$action}-{$format}-", false); + $format = $format ?? $this->getFormat(); + $identifier = uniqid("template-{$controller}-{$action}-{$format}-", false); - $this->logger->log( - LogLevel::DEBUG, - $identifier, - [ - 'path' => $path ?? $templateName, - 'controller' => $controller, - 'action' => $action, - 'format' => $format - ] - ); + $this->logger->log( + LogLevel::DEBUG, + $identifier, + [ + 'path' => $path ?? $templateName, + 'controller' => $controller, + 'action' => $action, + 'format' => $format, + ] + ); + } return $templateName; } diff --git a/Tests/Unit/Modules/Fluid/TemplatePathsTest.php b/Tests/Unit/Modules/Fluid/TemplatePathsTest.php new file mode 100644 index 0000000..bad0f6b --- /dev/null +++ b/Tests/Unit/Modules/Fluid/TemplatePathsTest.php @@ -0,0 +1,67 @@ +subject = new TemplatePaths(); + $this->logger = $this->prophesize(Logger::class); + } + + /** + * @test + */ + public function getTemplateIdentifierShouldNotLogAnythingIfTemplateNameIsNotAString(): void + { + $this->logger->log(LogLevel::DEBUG, Argument::any(), Argument::any())->shouldNotBeCalled(); + $this->mockLogger(); + $this->subject->getTemplateIdentifier(); + } + + /** + * @test + */ + public function getTemplateIdentifierLogsPathSetBySetTemplatePathAndFileName(): void + { + $this->mockLogger(); + $this->subject->setTemplatePathAndFilename('EXT:adminpanel_extended/Resources/Private/Templates/Fluid/Templates.html'); + $foo = $this->subject->getTemplateIdentifier(); + + } + + protected function mockLogger(): void + { + $logManager = $this->prophesize(LogManager::class); + $logManager->getLogger(Argument::any())->willReturn($this->logger->reveal()); + + GeneralUtility::setSingletonInstance(LogManager::class, $logManager->reveal()); + } +}