diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php index 5fd0e3ce34db244bc6850f91de8b461491684903..3ff6907baf148be6759fd149b931402cf5ed5ecc 100644 --- a/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php +++ b/typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php @@ -26,6 +26,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Tests\Functional\DataHandling\Framework\DataSet; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\ResponseContent; /** * Functional test for the DataHandler @@ -265,4 +267,141 @@ abstract class AbstractDataHandlerActionTestCase extends \TYPO3\CMS\Core\Tests\F return $differentFields; } + /** + * @param ResponseContent $responseContent + * @param string $structureRecordIdentifier + * @param string $structureFieldName + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentStructureHasRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) { + $nonMatchingVariants = array(); + + foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values); + + if (empty($nonMatchingValues)) { + // Increase assertion counter + $this->assertEmpty($nonMatchingValues); + return; + } + + $nonMatchingVariants[$path] = $nonMatchingValues; + } + + $nonMatchingMessage = ''; + foreach ($nonMatchingVariants as $path => $nonMatchingValues) { + $nonMatchingMessage .= '* ' . $path . ': ' . implode(', ', $nonMatchingValues); + } + + $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '"' . LF . $nonMatchingMessage); + } + + /** + * @param ResponseContent $responseContent + * @param string $structureRecordIdentifier + * @param string $structureFieldName + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentStructureDoesNotHaveRecords(ResponseContent $responseContent, $structureRecordIdentifier, $structureFieldName, $tableName, $fieldName, $values) { + if (is_string($values)) { + $values = array($values); + } + + $matchingVariants = array(); + + foreach ($responseContent->findStructures($structureRecordIdentifier, $structureFieldName) as $path => $structure) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($structure, $tableName, $fieldName, $values); + $matchingValues = array_diff($values, $nonMatchingValues); + + if (!empty($matchingValues)) { + $matchingVariants[$path] = $matchingValues; + } + } + + if (empty($matchingVariants)) { + // Increase assertion counter + $this->assertEmpty($matchingVariants); + return; + } + + $matchingMessage = ''; + foreach ($matchingVariants as $path => $matchingValues) { + $matchingMessage .= '* ' . $path . ': ' . implode(', ', $matchingValues); + } + + $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '"' . LF . $matchingMessage); + } + + /** + * @param ResponseContent $responseContent + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentHasRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) { + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values); + + if (!empty($nonMatchingValues)) { + $this->fail('Could not assert all values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $nonMatchingValues)); + } + + // Increase assertion counter + $this->assertEmpty($nonMatchingValues); + } + + /** + * @param ResponseContent $responseContent + * @param string $tableName + * @param string $fieldName + * @param string|array $values + */ + protected function assertResponseContentDoesNotHaveRecords(ResponseContent $responseContent, $tableName, $fieldName, $values) { + if (is_string($values)) { + $values = array($values); + } + + $nonMatchingValues = $this->getNonMatchingValuesFrontendResponseRecords($responseContent->getRecords(), $tableName, $fieldName, $values); + $matchingValues = array_diff($values, $nonMatchingValues); + + if (!empty($matchingValues)) { + $this->fail('Could not assert not having values for "' . $tableName . '.' . $fieldName . '": ' . implode(', ', $matchingValues)); + } + + // Increase assertion counter + $this->assertTrue(TRUE); + } + + /** + * @param string|array $data + * @param string $tableName + * @param string $fieldName + * @param string|array $values + * @return array + */ + protected function getNonMatchingValuesFrontendResponseRecords($data, $tableName, $fieldName, $values) { + if (empty($data) || !is_array($data)) { + $this->fail('Frontend Response data does not have any records'); + } + + if (is_string($values)) { + $values = array($values); + } + + foreach ($data as $recordIdentifier => $recordData) { + if (strpos($recordIdentifier, $tableName . ':') !== 0) { + continue; + } + + if (($foundValueIndex = array_search($recordData[$fieldName], $values)) !== FALSE) { + unset($values[$foundValueIndex]); + } + } + + return $values; + } + } diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..59202ed3595e5da57d165890b0b16465a024c104 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php @@ -0,0 +1,71 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Handler for backend user + */ +class BackendUserHandler implements \TYPO3\CMS\Core\SingletonInterface { + + /** + * @param array $parameters + * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController + */ + public function initialize(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) { + $backendUserId = (int)GeneralUtility::_GP('backendUserId'); + $workspaceId = (int)GeneralUtility::_GP('workspaceId'); + + if (empty($backendUserId) || empty($workspaceId)) { + return; + } + + $backendUser = $this->createBackendUser(); + $backendUser->user = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'be_users', 'uid=' . $backendUserId); + $backendUser->setTemporaryWorkspace($workspaceId); + $frontendController->beUserLogin = 1; + + $parameters['BE_USER'] = $backendUser; + $GLOBALS['BE_USER'] = $backendUser; + } + + /** + * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication + */ + protected function createBackendUser() { + return GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Backend\\FrontendBackendUserAuthentication' + ); + } + + /** + * @return \TYPO3\CMS\Core\Database\DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..e628a24d645bbdba04ae70aeeaa3b8a77002e89e --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/ContentObjectRendererWatcher.php @@ -0,0 +1,191 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderLevel; +use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RenderElement; + +/** + * Watcher for the content object rendering process + */ +class ContentObjectRendererWatcher implements \TYPO3\CMS\Frontend\ContentObject\ContentObjectPostInitHookInterface, \TYPO3\CMS\Core\SingletonInterface { + + /** + * @var RenderLevel + */ + protected $renderLevel; + + /** + * @var RenderElement + */ + protected $nextParentRenderElement; + + /** + * @var array + */ + protected $nextParentConfiguration; + + /** + * Holds parent objects (cObj) locally + * to avoid spl_object_hash() reassignments. + * + * @var array + */ + protected $localParentObjects = array(); + + /** + * @param string $name + * @param NULL|array $configuration + * @param string $typoScriptKey + * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject + * @return string + */ + public function cObjGetSingleExt($name, $configuration, $typoScriptKey, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject) { + $this->localParentObjects[] = $parentObject; + $this->nextParentRenderElement = NULL; + $this->nextParentConfiguration = NULL; + + if (($foundRenderElement = $this->renderLevel->findRenderElement($parentObject)) !== NULL) { + $this->nextParentRenderElement = $foundRenderElement; + $this->nextParentConfiguration = $configuration; + if (!empty($configuration['table'])) { + $this->nextParentRenderElement->addExpectedTableName($configuration['table']); + } + } + + $contentObject = $parentObject->getContentObject($name); + if ($contentObject) { + $contentObject->render($configuration); + } + + return ''; + } + + /** + * Hook for post processing the initialization of ContentObjectRenderer + * + * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object + */ + public function postProcessContentObjectInitialization(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer &$parentObject) { + $this->localParentObjects[] = $parentObject; + + if (!isset($this->renderLevel)) { + $this->renderLevel = RenderLevel::create($parentObject); + $this->renderLevel->add($parentObject); + } elseif (($foundRenderLevel = $this->renderLevel->findRenderLevel($parentObject)) !== NULL) { + $foundRenderLevel->add($parentObject); + } elseif ($this->nextParentRenderElement !== NULL) { + $level = $this->nextParentRenderElement->add($parentObject); + $level->add($parentObject); + if (!empty($this->nextParentConfiguration['watcher.']['parentRecordField'])) { + $level->setParentRecordField($this->nextParentConfiguration['watcher.']['parentRecordField']); + } + $this->nextParentRenderElement = NULL; + $this->nextParentConfiguration = NULL; + } + } + + /** + * @param string $query + * @param string $fromTable + */ + public function addQuery($query, $fromTable) { + if ($this->nextParentRenderElement === NULL) { + return; + } + + $this->nextParentRenderElement->addQuery($query, $fromTable); + } + + /** + * @param array $parameters + * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController + */ + public function show(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController) { + if (!isset($this->renderLevel) || empty($parameters['enableOutput']) || !empty($frontendController->content)) { + return; + } + + $tableFields = NULL; + if (!empty($this->getFrontendController()->tmpl->setup['watcher.']['tableFields.'])) { + $tableFields = $this->getFrontendController()->tmpl->setup['watcher.']['tableFields.']; + foreach ($tableFields as &$fieldList) { + $fieldList = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldList, TRUE); + } + unset($fieldList); + } + + $structureData = $this->renderLevel->structureData($tableFields); + + $result = array( + 'structure' => $structureData, + 'structurePaths' => $this->getStructurePaths($structureData), + 'records' => $this->renderLevel->mergeData($tableFields), + 'queries' => $this->renderLevel->mergeQueries(), + ); + + $frontendController->content = json_encode($result); + } + + /** + * @param array $structureData + * @param array $currentStructurePaths + * @return array + */ + protected function getStructurePaths(array $structureData, array $currentStructurePaths = array()) { + $structurePaths = array(); + + foreach ($structureData as $recordIdentifier => $recordData) { + $structurePaths[$recordIdentifier][] = $currentStructurePaths; + foreach ($recordData as $fieldName => $fieldValue) { + if (!is_array($fieldValue)) { + continue; + } + + $nestedStructurePaths = $this->getStructurePaths( + $fieldValue, + array_merge($currentStructurePaths, array($recordIdentifier, $fieldName)) + ); + + foreach ($nestedStructurePaths as $nestedRecordIdentifier => $nestedStructurePathDetails) { + $structurePaths[$nestedRecordIdentifier] = array_merge( + (array)$structurePaths[$nestedRecordIdentifier], + $nestedStructurePathDetails + ); + } + } + } + + return $structurePaths; + } + + /** + * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController + */ + protected function getFrontendController() { + return $GLOBALS['TSFE']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..02f0d7f7694991321953128b24dea8fb8629c9c7 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/DatabaseConnectionWatcher.php @@ -0,0 +1,143 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Hook; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Watcher for executed database queries + */ +class DatabaseConnectionWatcher implements \TYPO3\CMS\Core\Database\PostProcessQueryHookInterface, \TYPO3\CMS\Core\SingletonInterface { + + /** + * @var array + */ + protected $queries = array(); + + /** + * Constructs this object and ensures that full database + * queries are stored locally in the DatabaseConnection. + */ + public function __construct() { + $this->getDatabaseConnection()->store_lastBuiltQuery = TRUE; + } + + public function getQueries() { + return $this->queries; + } + + /** + * Post-processor for the SELECTquery method. + * + * @param string $select_fields Fields to be selected + * @param string $from_table Table to select data from + * @param string $where_clause Where clause + * @param string $groupBy Group by statement + * @param string $orderBy Order by statement + * @param integer $limit Database return limit + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_SELECTquery_postProcessAction(&$select_fields, &$from_table, &$where_clause, &$groupBy, &$orderBy, &$limit, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + $this->getContentObjectRendererWatcher()->addQuery( + $parentObject->debug_lastBuiltQuery, + $from_table + ); + } + + /** + * Post-processor for the exec_INSERTquery method. + * + * @param string $table Database table name + * @param array $fieldsValues Field values as key => value pairs + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_INSERTquery_postProcessAction(&$table, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_INSERTmultipleRows method. + * + * @param string $table Database table name + * @param array $fields Field names + * @param array $rows Table rows + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_INSERTmultipleRows_postProcessAction(&$table, array &$fields, array &$rows, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_UPDATEquery method. + * + * @param string $table Database table name + * @param string $where WHERE clause + * @param array $fieldsValues Field values as key => value pairs + * @param string /array $noQuoteFields List/array of keys NOT to quote + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_UPDATEquery_postProcessAction(&$table, &$where, array &$fieldsValues, &$noQuoteFields, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_DELETEquery method. + * + * @param string $table Database table name + * @param string $where WHERE clause + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_DELETEquery_postProcessAction(&$table, &$where, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * Post-processor for the exec_TRUNCATEquery method. + * + * @param string $table Database table name + * @param \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject + * @return void + */ + public function exec_TRUNCATEquery_postProcessAction(&$table, \TYPO3\CMS\Core\Database\DatabaseConnection $parentObject) { + } + + /** + * @return ContentObjectRendererWatcher + */ + protected function getContentObjectRendererWatcher() { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\Hook\\ContentObjectRendererWatcher' + ); + } + + /** + * @return \TYPO3\CMS\Core\Database\DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php new file mode 100644 index 0000000000000000000000000000000000000000..fd6c8a5b0e2f4fa41fe6ecd5f5762346b36b8055 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderElement.php @@ -0,0 +1,240 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * Model of rendered content elements + */ +class RenderElement { + + /** + * @var array + */ + protected $recordData; + + /** + * @var string + */ + protected $recordIdentifier; + + /** + * @var string + */ + protected $recordTableName; + + /** + * @var array|RenderLevel[] + */ + protected $levels; + + /** + * @var array + */ + protected $expectedTableNames = array(); + + /** + * @var array + */ + protected $queries = array(); + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderElement + */ + public static function create(ContentObjectRenderer $contentObjectRenderer) { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderElement', + $contentObjectRenderer + ); + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + */ + public function __construct(ContentObjectRenderer $contentObjectRenderer) { + $this->recordIdentifier = $contentObjectRenderer->currentRecord; + list($this->recordTableName) = explode(':', $this->recordIdentifier); + $this->recordData = $contentObjectRenderer->data; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderLevel + */ + public function add(ContentObjectRenderer $contentObjectRenderer) { + $level = RenderLevel::create($contentObjectRenderer); + $level->setParentRecordIdentifier($this->recordIdentifier); + $this->levels[] = $level; + return $level; + } + + /** + * @return string + */ + public function getRecordIdentifier() { + return $this->recordIdentifier; + } + + /** + * @param string $expectedTableName + */ + public function addExpectedTableName($expectedTableName) { + if (!$this->hasExpectedTableName($expectedTableName)) { + $this->expectedTableNames[] = $expectedTableName; + } + } + + /** + * @param string $tableName + * @return bool + */ + public function hasExpectedTableName($tableName) { + if (in_array($tableName, $this->expectedTableNames)) { + return TRUE; + } + // Handling JOIN constructions + // e.g. "sys_category JOIN sys_category_record_mm ON sys_category_record_mm.uid_local = sys_category.uid" + foreach ($this->getExpectedTableNames() as $expectedTableName) { + if (strpos($tableName, $expectedTableName . ' ') === 0) { + return TRUE; + } + } + return FALSE; + } + + /** + * @return array + */ + public function getExpectedTableNames() { + return $this->expectedTableNames; + } + + /** + * @param string $query + * @param string $fromTable + */ + public function addQuery($query, $fromTable) { + if (empty($this->expectedTableNames) || $this->hasExpectedTableName($fromTable)) { + $this->queries[] = $query; + } + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderLevel + */ + public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) { + if (empty($this->levels)) { + return NULL; + } + + foreach ($this->levels as $level) { + $result = $level->findRenderLevel($contentObjectRenderer); + if ($result !== NULL) { + return $result; + } + } + + return NULL; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function getRecordData(array $tableFields = NULL) { + $recordData = $this->recordData; + + if (!empty($tableFields[$this->recordTableName])) { + $recordData = array_intersect_key( + $recordData, + array_flip($tableFields[$this->recordTableName]) + ); + } + + return $recordData; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function structureData(array $tableFields = NULL) { + $data = array( + $this->recordIdentifier => $this->getRecordData($tableFields) + ); + + foreach ($this->levels as $level) { + $parentRecordIdentifier = $level->getParentRecordIdentifier(); + $parentRecordField = $level->getParentRecordField(); + + foreach ($level->getElements() as $element) { + if (empty($parentRecordIdentifier) || empty($parentRecordField) || !isset($data[$parentRecordIdentifier])) { + $data = array_merge($data, $element->structureData($tableFields)); + continue; + } + + if (!isset($data[$parentRecordIdentifier][$parentRecordField]) || !is_array($data[$parentRecordIdentifier][$parentRecordField])) { + $data[$parentRecordIdentifier][$parentRecordField] = array(); + } + + $data[$parentRecordIdentifier][$parentRecordField] = array_merge( + $data[$parentRecordIdentifier][$parentRecordField], + $element->structureData($tableFields) + ); + } + } + + return $data; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function mergeData(array $tableFields = NULL) { + $data = array( + $this->recordIdentifier => $this->getRecordData($tableFields), + ); + foreach ($this->levels as $level) { + $data = array_merge($data, $level->mergeData($tableFields)); + } + return $data; + } + + /** + * @return array + */ + public function mergeQueries() { + $queries = $this->queries; + foreach ($this->levels as $level) { + $queries = array_merge($queries, $level->mergeQueries()); + } + return $queries; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php new file mode 100644 index 0000000000000000000000000000000000000000..545e3180bf18040c56a3cb615ce9a7ba0a34c5a1 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RenderLevel.php @@ -0,0 +1,206 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +use \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; + +/** + * Model of rendered content levels + */ +class RenderLevel { + + /** + * @var string + */ + protected $identifier; + + /** + * @var string + */ + protected $parentRecordIdentifier; + + /** + * @var string + */ + protected $parentRecordField; + + /** + * @var array|RenderElement[] + */ + protected $elements = array(); + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderLevel + */ + public static function create(ContentObjectRenderer $contentObjectRenderer) { + return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( + 'TYPO3\\CMS\\Core\\Tests\\Functional\\Framework\\Frontend\\RenderLevel', + $contentObjectRenderer + ); + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + */ + public function __construct(ContentObjectRenderer $contentObjectRenderer) { + $this->identifier = spl_object_hash($contentObjectRenderer); + } + + /** + * @return string + */ + public function getIdentifier() { + return $this->identifier; + } + + /** + * @param string $parentRecordIdentifier + */ + public function setParentRecordIdentifier($parentRecordIdentifier) { + $this->parentRecordIdentifier = $parentRecordIdentifier; + } + + public function getParentRecordIdentifier() { + return $this->parentRecordIdentifier; + } + + /** + * @param string $parentRecordField + */ + public function setParentRecordField($parentRecordField) { + $this->parentRecordField = $parentRecordField; + } + + /** + * @return string + */ + public function getParentRecordField() { + return $this->parentRecordField; + } + + /** + * @return array|RenderElement[] + */ + public function getElements() { + return $this->elements; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return RenderElement + */ + public function add(ContentObjectRenderer $contentObjectRenderer) { + $element = RenderElement::create($contentObjectRenderer); + $this->elements[] = $element; + return $element; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderLevel + */ + public function findRenderLevel(ContentObjectRenderer $contentObjectRenderer) { + if (spl_object_hash($contentObjectRenderer) === $this->identifier) { + return $this; + } + + foreach ($this->elements as $element) { + $result = $element->findRenderLevel($contentObjectRenderer); + if ($result !== NULL) { + return $result; + } + } + + return NULL; + } + + /** + * @param ContentObjectRenderer $contentObjectRenderer + * @return NULL|RenderElement + */ + public function findRenderElement(ContentObjectRenderer $contentObjectRenderer) { + $foundRenderLevel = $this->findRenderLevel($contentObjectRenderer); + + if ($foundRenderLevel === NULL) { + return NULL; + } + + if ($foundRenderLevel !== $this) { + return $foundRenderLevel->findRenderElement($contentObjectRenderer); + } + + + foreach ($this->elements as $element) { + if ($element->getRecordIdentifier() === $contentObjectRenderer->currentRecord) { + return $element; + } + } + + return NULL; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function structureData(array $tableFields = NULL) { + $data = array(); + + foreach ($this->elements as $element) { + $data = array_merge($data, $element->structureData($tableFields)); + } + + return $data; + } + + /** + * @param NULL|array $tableFields + * @return array + */ + public function mergeData(array $tableFields = NULL) { + $data = array(); + + foreach ($this->elements as $element) { + $data = array_merge($data, $element->mergeData($tableFields)); + } + + return $data; + } + + /** + * @return array + */ + public function mergeQueries() { + $queries = array(); + + foreach ($this->elements as $element) { + $queries = array_merge($queries, $element->mergeQueries()); + } + + return $queries; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..623a02357d87c8e378c416a8bcc18dcc8c91510c --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php @@ -0,0 +1,111 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Bootstrap for direct CLI Request + */ +class RequestBootstrap { + + /** + * @return void + */ + static public function setGlobalVariables() { + if (empty($_SERVER['argv'][1]) || ($requestArguments = json_decode($_SERVER['argv'][1], TRUE)) === FALSE) { + die('No JSON encoded arguments given'); + } + + if (empty($requestArguments['documentRoot'])) { + die('No documentRoot given'); + } + + if (empty($requestArguments['requestUrl']) || ($requestUrlParts = parse_url($requestArguments['requestUrl'])) === FALSE) { + die('No valid request URL given'); + } + + // Populating $_GET and $_REQUEST is query part is set: + if (isset($requestUrlParts['query'])) { + parse_str($requestUrlParts['query'], $_GET); + parse_str($requestUrlParts['query'], $_REQUEST); + } + + // Populating $_POST + $_POST = array(); + // Populating $_COOKIE + $_COOKIE = array(); + + // Setting up the server environment + $_SERVER = array(); + $_SERVER['DOCUMENT_ROOT'] = $requestArguments['documentRoot']; + $_SERVER['HTTP_USER_AGENT'] = 'TYPO3 Functional Test Request'; + $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = $requestUrlParts['host']; + $_SERVER['SERVER_ADDR'] = $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = '/index.php'; + $_SERVER['SCRIPT_FILENAME'] = $_SERVER['_'] = $_SERVER['PATH_TRANSLATED'] = $requestArguments['documentRoot'] . '/index.php'; + $_SERVER['QUERY_STRING'] = (isset($requestUrlParts['query']) ? $requestUrlParts['query'] : ''); + $_SERVER['REQUEST_URI'] = $requestUrlParts['path'] . (isset($requestUrlParts['query']) ? '?' . $requestUrlParts['query'] : ''); + $_SERVER['REQUEST_METHOD'] = 'GET'; + + // Define a port if used in the URL: + if (isset($requestUrlParts['port'])) { + $_SERVER['SERVER_PORT'] = $requestUrlParts['port']; + } + // Define HTTPS disposal: + if ($requestUrlParts['scheme'] === 'https') { + $_SERVER['HTTPS'] = 'on'; + } + + if (!is_dir($_SERVER['DOCUMENT_ROOT'])) { + die('Document root directory "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist'); + } + + if (!is_file($_SERVER['SCRIPT_FILENAME'])) { + die('Script file "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist'); + } + } + + /** + * @return void + */ + static public function executeAndOutput() { + global $TT, $TSFE, $TYPO3_CONF_VARS, $BE_USER, $TYPO3_MISC; + + $result = array('status' => 'failure', 'content' => NULL, 'error' => NULL); + + ob_start(); + try { + chdir($_SERVER['DOCUMENT_ROOT']); + include($_SERVER['SCRIPT_FILENAME']); + $result['status'] = 'success'; + $result['content'] = ob_get_contents(); + } catch(\Exception $exception) { + $result['error'] = $exception->__toString(); + } + ob_end_clean(); + + echo json_encode($result); + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php new file mode 100644 index 0000000000000000000000000000000000000000..b0e68392a4a1ba2e9696f5f72b05ee27333efebe --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php @@ -0,0 +1,97 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Model of frontend response + */ +class Response { + + const STATUS_Success = 'success'; + const STATUS_Failure = 'failure'; + + /** + * @var string + */ + protected $status; + + /** + * @var NULL|string|array + */ + protected $content; + + /** + * @var string + */ + protected $error; + + /** + * @var ResponseContent + */ + protected $responseContent; + + /** + * @param string $status + * @param string $content + * @param string $error + */ + public function __construct($status, $content, $error) { + $this->status = $status; + $this->content = $content; + $this->error = $error; + } + + /** + * @return string + */ + public function getStatus() { + return $this->status; + } + + /** + * @return array|NULL|string + */ + public function getContent() { + return $this->content; + } + + /** + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * @return ResponseContent + */ + public function getResponseContent() { + if (!isset($this->responseContent)) { + $this->responseContent = new ResponseContent($this); + } + return $this->responseContent; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php new file mode 100644 index 0000000000000000000000000000000000000000..93ccb36c078f9955bd2c7836c6bebc32b9b7cd29 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php @@ -0,0 +1,136 @@ +<?php +namespace TYPO3\CMS\Core\Tests\Functional\Framework\Frontend; + +/*************************************************************** + * Copyright notice + * + * (c) 2014 Oliver Hader <oliver.hader@typo3.org> + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + ***************************************************************/ + +/** + * Model of frontend response content + */ +class ResponseContent { + + /** + * @var Response + */ + protected $response; + + /** + * @var array + */ + protected $structure; + + /** + * @var array + */ + protected $structurePaths; + + /** + * @var array + */ + protected $records; + + /** + * @var array + */ + protected $queries; + + /** + * @param Response $response + */ + public function __construct(Response $response) { + $this->response = $response; + $content = json_decode($response->getContent(), TRUE); + + if ($content !== NULL && is_array($content)) { + $this->structure = $content['structure']; + $this->structurePaths = $content['structurePaths']; + $this->records = $content['records']; + $this->queries = $content['queries']; + } + } + + /** + * @return array + */ + public function getStructure() { + return $this->structure; + } + + /** + * @return array + */ + public function getStructurePaths() { + return $this->structurePaths; + } + + /** + * @return array + */ + public function getRecords() { + return $this->records; + } + + /** + * @return array + */ + public function getQueries() { + return $this->queries; + } + + /** + * @param string $recordIdentifier + * @param string $fieldName + * @return array + */ + public function findStructures($recordIdentifier, $fieldName = '') { + $structures = array(); + + if (empty($this->structurePaths[$recordIdentifier])) { + return $structures; + } + + foreach ($this->structurePaths[$recordIdentifier] as $steps) { + $structure = $this->structure; + $steps[] = $recordIdentifier; + + if (!empty($fieldName)) { + $steps[] = $fieldName; + } + + foreach ($steps as $step) { + if (!isset($structure[$step])) { + $structure = NULL; + break; + } + $structure = $structure[$step]; + } + + if (!empty($structure)) { + $structures[implode('/', $steps)] = $structure; + } + } + + return $structures; + } + +} diff --git a/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php new file mode 100644 index 0000000000000000000000000000000000000000..ff599a6a9ab69bf84d609b45456c4e224c78c6fc --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php @@ -0,0 +1,8 @@ +<?php +require dirname(dirname(dirname(dirname(__DIR__)))) . '/Classes/Core/CliBootstrap.php'; +\TYPO3\CMS\Core\Core\CliBootstrap::checkEnvironmentOrDie(); + +require dirname(__DIR__) . '/Frontend/RequestBootstrap.php'; +\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::setGlobalVariables(); +\TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\RequestBootstrap::executeAndOutput(); +?> \ No newline at end of file diff --git a/typo3/sysext/core/Tests/FunctionalTestCase.php b/typo3/sysext/core/Tests/FunctionalTestCase.php index aa9204970eed9359820421c1b8f4077916edd1a4..f81624242ab51252ffb63af15785784bb90958cb 100644 --- a/typo3/sysext/core/Tests/FunctionalTestCase.php +++ b/typo3/sysext/core/Tests/FunctionalTestCase.php @@ -24,6 +24,8 @@ namespace TYPO3\CMS\Core\Tests; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ +use \TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response; + /** * Base test case class for functional tests, all TYPO3 CMS * functional tests should extend from this class! @@ -134,6 +136,13 @@ abstract class FunctionalTestCase extends BaseTestCase { */ private $bootstrapUtility = NULL; + /** + * Path to TYPO3 CMS test installation for this test case + * + * @var string + */ + private $instancePath; + /** * Set up creates a test instance and database. * @@ -146,7 +155,7 @@ abstract class FunctionalTestCase extends BaseTestCase { $this->markTestSkipped('Functional tests must be called through phpunit on CLI'); } $this->bootstrapUtility = new FunctionalTestCaseBootstrapUtility(); - $this->bootstrapUtility->setUp( + $this->instancePath = $this->bootstrapUtility->setUp( get_class($this), $this->coreExtensionsToLoad, $this->testExtensionsToLoad, @@ -273,4 +282,92 @@ abstract class FunctionalTestCase extends BaseTestCase { } } } + + /** + * @param int $pageId + * @param array $typoScriptFiles + */ + protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array()) { + $pageId = (int)$pageId; + $page = $this->getDatabase()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId); + + if (empty($page)) { + $this->fail('Cannot set up frontend root page "' . $pageId . '"'); + } + + $pagesFields = array( + 'is_siteroot' => 1 + ); + + $this->getDatabase()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields); + + $templateFields = array( + 'pid' => $pageId, + 'title' => '', + 'config' => '', + 'clear' => 3, + 'root' => 1, + ); + + foreach ($typoScriptFiles as $typoScriptFile) { + $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF; + } + + $this->getDatabase()->exec_INSERTquery('sys_template', $templateFields); + } + + /** + * @param int $pageId + * @param int $languageId + * @param int $backendUserId + * @param int $workspaceId + * @param bool $failOnFailure + * @return Response + */ + protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = TRUE) { + $pageId = (int)$pageId; + $languageId = (int)$languageId; + + if (defined('PHP_BINARY')) { + $phpExecutable = PHP_BINARY; + } else { + $phpExecutable = rtrim(PHP_BINDIR, '/') . '/php'; + } + + $additionalParameter = ''; + + if (!empty($backendUserId)) { + $additionalParameter .= '&backendUserId=' . (int)$backendUserId; + } + if (!empty($workspaceId)) { + $additionalParameter .= '&workspaceId=' . (int)$workspaceId; + } + + $arguments = array( + 'documentRoot' => $this->instancePath, + 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter, + ); + + $commandParts = array( + escapeshellcmd($phpExecutable), + escapeshellarg(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Framework/Scripts/Request.php'), + escapeshellarg(json_encode($arguments)), + ); + + $command = trim(implode(' ', $commandParts)); + $response = shell_exec($command); + $result = json_decode($response, TRUE); + + if ($result === FALSE) { + $this->fail('Frontend Response is empty'); + } + + if ($failOnFailure && $result['status'] === Response::STATUS_Failure) { + $this->fail('Frontend Response has failure:' . LF . $result['error']); + } + + $response = new Response($result['status'], $result['content'], $result['error']); + return $response; + } + } diff --git a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php index 069a8bf22cbd085ccab7c253513c15d2249e509f..36f7ce062644009e1a322b2c3d75d3380d3c4278 100644 --- a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php +++ b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php @@ -75,7 +75,7 @@ class FunctionalTestCaseBootstrapUtility { * @param array $coreExtensionsToLoad Array of core extensions to load * @param array $testExtensionsToLoad Array of test extensions to load * @param array $pathsToLinkInTestInstance Array of source => destination path pairs to be linked - * @return void + * @return string Path to TYPO3 CMS test installation for this test case */ public function setUp( $testCaseClassName, @@ -96,6 +96,8 @@ class FunctionalTestCaseBootstrapUtility { $this->setUpTestDatabase(); \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE); $this->createDatabaseStructure(); + + return $this->instancePath; } /**