/** * Search for commented INCLUDE_TYPOSCRIPT statements * and save the content between the BEGIN and the END line to the specified file * * @param string $string Template content * @param int $cycle_counter Counter for detecting endless loops * @param array $extractedFileNames * @param string $parentFilenameOrPath * * @throws \RuntimeException * @throws \UnexpectedValueException * @return string Template content with uncommented include statements */ public static function extractIncludes($string, $cycle_counter = 1, array $extractedFileNames = array(), $parentFilenameOrPath = '') { if ($cycle_counter > 10) { GeneralUtility::sysLog('It appears like TypoScript code is looping over itself. Check your templates for "<INCLUDE_TYPOSCRIPT: ..." tags', 'Core', GeneralUtility::SYSLOG_SEVERITY_WARNING); return ' ### ### ERROR: Recursion! ### '; } $expectedEndTag = ''; $fileContent = array(); $restContent = array(); $fileName = NULL; $inIncludePart = FALSE; $lines = preg_split("/\r\n|\n|\r/", $string); $skipNextLineIfEmpty = FALSE; $openingCommentedIncludeStatement = NULL; $optionalProperties = ''; foreach ($lines as $line) { // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts // an additional empty line, remove this again if ($skipNextLineIfEmpty) { if (trim($line) === '') { continue; } $skipNextLineIfEmpty = FALSE; } // Outside commented include statements if (!$inIncludePart) { // Search for beginning commented include statements if (preg_match('/###\\s*<INCLUDE_TYPOSCRIPT:\\s*source\\s*=\\s*"\\s*((?i)file|dir)\\s*:\\s*([^"]*)"(.*)>\\s*BEGIN/i', $line, $matches)) { // Found a commented include statement // Save this line in case there is no ending tag $openingCommentedIncludeStatement = trim($line); $openingCommentedIncludeStatement = preg_replace('/\\s*### Warning: .*###\\s*/', '', $openingCommentedIncludeStatement); // type of match: FILE or DIR $inIncludePart = strtoupper($matches[1]); $fileName = $matches[2]; $optionalProperties = $matches[3]; $expectedEndTag = '### <INCLUDE_TYPOSCRIPT: source="' . $inIncludePart . ':' . $fileName . '"' . $optionalProperties . '> END'; // Strip all whitespace characters to make comparison safer $expectedEndTag = strtolower(preg_replace('/\\s/', '', $expectedEndTag)); } else { // If this is not a beginning commented include statement this line goes into the rest content $restContent[] = $line; } //if (is_array($matches)) GeneralUtility::devLog('matches', 'TypoScriptParser', 0, $matches); } else { // Inside commented include statements // Search for the matching ending commented include statement $strippedLine = preg_replace('/\\s/', '', $line); if (stripos($strippedLine, $expectedEndTag) !== FALSE) { // Found the matching ending include statement $fileContentString = implode(PHP_EOL, $fileContent); // Write the content to the file // Resolve a possible relative paths if a parent file is given if ($parentFilenameOrPath !== '' && $fileName[0] === '.') { $realFileName = PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($parentFilenameOrPath, $fileName); } else { $realFileName = $fileName; } $realFileName = GeneralUtility::getFileAbsFileName($realFileName); if ($inIncludePart === 'FILE') { // Some file checks if (!GeneralUtility::verifyFilenameAgainstDenyPattern($realFileName)) { throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858); } if (empty($realFileName)) { throw new \UnexpectedValueException(sprintf('"%s" is not a valid file location.', $fileName), 1294586441); } if (!is_writable($realFileName)) { throw new \RuntimeException(sprintf('"%s" is not writable.', $fileName), 1294586442); } if (in_array($realFileName, $extractedFileNames)) { throw new \RuntimeException(sprintf('Recursive/multiple inclusion of file "%s"', $realFileName), 1294586443); } $extractedFileNames[] = $realFileName; // Recursive call to detected nested commented include statements $fileContentString = self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName); // Write the content to the file if (!GeneralUtility::writeFile($realFileName, $fileContentString)) { throw new \RuntimeException(sprintf('Could not write file "%s"', $realFileName), 1294586444); } // Insert reference to the file in the rest content $restContent[] = '<INCLUDE_TYPOSCRIPT: source="FILE:' . $fileName . '"' . $optionalProperties . '>'; } else { // must be DIR // Some file checks if (empty($realFileName)) { throw new \UnexpectedValueException(sprintf('"%s" is not a valid location.', $fileName), 1366493602); } if (!is_dir($realFileName)) { throw new \RuntimeException(sprintf('"%s" is not a directory.', $fileName), 1366493603); } if (in_array($realFileName, $extractedFileNames)) { throw new \RuntimeException(sprintf('Recursive/multiple inclusion of directory "%s"', $realFileName), 1366493604); } $extractedFileNames[] = $realFileName; // Recursive call to detected nested commented include statements self::extractIncludes($fileContentString, $cycle_counter + 1, $extractedFileNames, $realFileName); // just drop content between tags since it should usually just contain individual files from that dir // Insert reference to the dir in the rest content $restContent[] = '<INCLUDE_TYPOSCRIPT: source="DIR:' . $fileName . '"' . $optionalProperties . '>'; } // Reset variables (preparing for the next commented include statement) $fileContent = array(); $fileName = NULL; $inIncludePart = FALSE; $openingCommentedIncludeStatement = NULL; // \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::checkIncludeLines inserts // an additional empty line, remove this again $skipNextLineIfEmpty = TRUE; } else { // If this is not a ending commented include statement this line goes into the file content $fileContent[] = $line; } } } // If we're still inside commented include statements copy the lines back to the rest content if ($inIncludePart) { $restContent[] = $openingCommentedIncludeStatement . ' ### Warning: Corresponding end line missing! ###'; $restContent = array_merge($restContent, $fileContent); } $restContentString = implode(PHP_EOL, $restContent); return $restContentString; }
/** * @param $baseFileName * @param $includeFileName * @param $expectedFileName * @test * @dataProvider getAbsolutePathOfRelativeReferencedFileOrPathResolvesFileCorrectlyDataProvider */ public function getAbsolutePathOfRelativeReferencedFileOrPathResolvesFileCorrectly($baseFileName, $includeFileName, $expectedFileName) { $resolvedFilename = \TYPO3\CMS\Core\Utility\PathUtility::getAbsolutePathOfRelativeReferencedFileOrPath($baseFileName, $includeFileName); $this->assertEquals($expectedFileName, $resolvedFilename); }