From 46fdd83d381a4d31be844cbce55deabca2c75367 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Mon, 4 Nov 2024 12:52:07 +1300 Subject: [PATCH] API Remove API which is now in silverstripe/silverstripe-template-engine --- _config/view.yml | 2 +- composer.json | 1 + phpcs.xml.dist | 7 - src/View/SSTemplateEngine.php | 468 -- src/View/SSTemplateParseException.php | 29 - src/View/SSTemplateParser.peg | 1353 ----- src/View/SSTemplateParser.php | 5386 ----------------- src/View/SSViewer_BasicIteratorSupport.php | 211 - src/View/SSViewer_Scope.php | 620 -- src/View/TemplateIteratorProvider.php | 47 - src/View/TemplateParser.php | 20 - src/View/ViewLayerData.php | 2 +- src/i18n/TextCollection/Parser.php | 2 +- src/includes/constants.php | 4 - tests/php/Core/Injector/InjectorTest.php | 4 +- .../View/SSTemplateEngineCacheBlockTest.php | 463 -- .../TestModel.php | 31 - .../VersionedModel.php | 28 - .../View/SSTemplateEngineFindTemplateTest.php | 320 - tests/php/View/SSTemplateEngineTest.php | 2279 ------- .../SSTemplateEngineTest/CacheTestData.php | 32 - .../SSTemplateEngineTest/LevelTestData.php | 37 - .../View/SSTemplateEngineTest/TestFixture.php | 85 - .../TestGlobalProvider.php | 51 - .../SSTemplateEngineTest/TestModelData.php | 45 - .../View/SSTemplateEngineTest/TestObject.php | 52 - .../javascript/RequirementsTest_a.js | 1 - .../SSTemplateEngineTestCommentsInclude.ss | 1 - ...emplateEngineTestIncludeObjectArguments.ss | 1 - ...ngineTestIncludeScopeInheritanceInclude.ss | 1 - ...stIncludeScopeInheritanceWithArgsInLoop.ss | 1 - ...udeScopeInheritanceWithArgsInNestedWith.ss | 1 - ...stIncludeScopeInheritanceWithArgsInWith.ss | 1 - ...TestIncludeScopeInheritanceWithUpAndTop.ss | 1 - ...STemplateEngineTestIncludeWithArguments.ss | 1 - .../SSTemplateEngineTestProcessHead.ss | 3 - .../SSTemplateEngineTestRecursiveInclude.ss | 6 - .../SSTemplateEngineTestTypePreservation.ss | 1 - .../templates/Namespace/IncludedOnceBase.ss | 1 - .../templates/Namespace/IncludedTwice.ss | 1 - .../Namespace/Includes/IncludedOnceSub.ss | 1 - .../Namespace/Includes/IncludedTwice.ss | 1 - .../Namespace/Includes/NamespaceInclude.ss | 1 - .../templates/RSSFeedTest.ss | 6 - .../SSTemplateEngineTestCommentsFullSource.ss | 5 - ...ngineTestCommentsFullSourceHTML4Doctype.ss | 6 - ...emplateEngineTestCommentsFullSourceIfIE.ss | 7 - ...gineTestCommentsFullSourceIfIENoDoctype.ss | 6 - ...teEngineTestCommentsFullSourceNoDoctype.ss | 4 - ...TemplateEngineTestCommentsPartialSource.ss | 1 - ...SSTemplateEngineTestCommentsWithInclude.ss | 1 - ...mplateEngineTestIncludeScopeInheritance.ss | 3 - ...gineTestIncludeScopeInheritanceWithArgs.ss | 3 - .../SSTemplateEngineTestLoopArray.ss | 3 - .../SSTemplateEngineTestPartialTemplate.ss | 1 - .../templates/SSTemplateEngineTestProcess.ss | 6 - .../themes/layouttest/templates/Controller.ss | 1 - .../layouttest/templates/Layout/Page.ss | 1 - .../layouttest/templates/Layout/Shortcodes.ss | 1 - .../themes/layouttest/templates/Page.ss | 1 - .../SSTemplateEngineTestModel_Controller.ss | 1 - .../_manifest_exclude | 0 .../module/Root.ss | 0 .../module/composer.json | 4 - .../module/subfolder/templates/Subfolder.ss | 0 .../module/templates/CustomTemplate.ss | 1 - .../module/templates/Layout/CustomPage.ss | 0 .../templates/Layout/CustomThemePage.ss | 0 .../module/templates/Layout/Page.ss | 0 .../templates/MyNamespace/Layout/MyClass.ss | 1 - .../module/templates/MyNamespace/MyClass.ss | 1 - .../MyNamespace/MySubnamespace/MySubclass.ss | 1 - .../module/templates/Page.ss | 0 .../module/tests/templates/Test.ss | 0 .../subtheme/templates/NestedThemePage.ss | 1 - .../myproject/_config.php | 1 - .../myproject/templates/CustomTemplate.ss | 1 - .../myproject/templates/Layout/.gitignore | 0 .../themes/theme/templates/CustomThemePage.ss | 0 .../theme/templates/Includes/Include.ss | 0 .../themes/theme/templates/Layout/Page.ss | 0 .../theme/templates/MyNamespace/MyClass.ss | 0 .../themes/theme/templates/Page.ss | 0 tests/php/i18n/i18nTestManifest.php | 6 +- thirdparty/_manifest_exclude | 0 thirdparty/php-peg/.piston.yml | 9 - thirdparty/php-peg/Compiler.php | 882 --- thirdparty/php-peg/LICENSE | 149 - thirdparty/php-peg/PHPBuilder.php | 127 - thirdparty/php-peg/Parser.php | 319 - thirdparty/php-peg/README.md | 329 - thirdparty/php-peg/cli.php | 5 - 92 files changed, 9 insertions(+), 13488 deletions(-) delete mode 100644 src/View/SSTemplateEngine.php delete mode 100644 src/View/SSTemplateParseException.php delete mode 100644 src/View/SSTemplateParser.peg delete mode 100644 src/View/SSTemplateParser.php delete mode 100644 src/View/SSViewer_BasicIteratorSupport.php delete mode 100644 src/View/SSViewer_Scope.php delete mode 100644 src/View/TemplateIteratorProvider.php delete mode 100644 src/View/TemplateParser.php delete mode 100644 tests/php/View/SSTemplateEngineCacheBlockTest.php delete mode 100644 tests/php/View/SSTemplateEngineCacheBlockTest/TestModel.php delete mode 100644 tests/php/View/SSTemplateEngineCacheBlockTest/VersionedModel.php delete mode 100644 tests/php/View/SSTemplateEngineFindTemplateTest.php delete mode 100644 tests/php/View/SSTemplateEngineTest.php delete mode 100644 tests/php/View/SSTemplateEngineTest/CacheTestData.php delete mode 100644 tests/php/View/SSTemplateEngineTest/LevelTestData.php delete mode 100644 tests/php/View/SSTemplateEngineTest/TestFixture.php delete mode 100644 tests/php/View/SSTemplateEngineTest/TestGlobalProvider.php delete mode 100644 tests/php/View/SSTemplateEngineTest/TestModelData.php delete mode 100644 tests/php/View/SSTemplateEngineTest/TestObject.php delete mode 100644 tests/php/View/SSTemplateEngineTest/javascript/RequirementsTest_a.js delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestCommentsInclude.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeObjectArguments.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceInclude.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInLoop.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInNestedWith.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInWith.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithUpAndTop.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeWithArguments.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestProcessHead.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestRecursiveInclude.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestTypePreservation.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedOnceBase.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedTwice.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedOnceSub.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedTwice.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/NamespaceInclude.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/RSSFeedTest.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSource.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceHTML4Doctype.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIE.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIENoDoctype.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceNoDoctype.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsPartialSource.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsWithInclude.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritance.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritanceWithArgs.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestLoopArray.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestPartialTemplate.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestProcess.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Controller.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Page.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Shortcodes.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Page.ss delete mode 100644 tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/TestNamespace/SSTemplateEngineTestModel_Controller.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/_manifest_exclude delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/Root.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/composer.json delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/subfolder/templates/Subfolder.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/CustomTemplate.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomPage.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomThemePage.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/Page.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/Layout/MyClass.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MyClass.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MySubnamespace/MySubclass.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Page.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/tests/templates/Test.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/module/themes/subtheme/templates/NestedThemePage.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/myproject/_config.php delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/myproject/templates/CustomTemplate.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/myproject/templates/Layout/.gitignore delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/themes/theme/templates/CustomThemePage.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/themes/theme/templates/Includes/Include.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/themes/theme/templates/Layout/Page.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/themes/theme/templates/MyNamespace/MyClass.ss delete mode 100644 tests/php/View/SSTemplateEngineTest_findTemplate/themes/theme/templates/Page.ss delete mode 100644 thirdparty/_manifest_exclude delete mode 100644 thirdparty/php-peg/.piston.yml delete mode 100644 thirdparty/php-peg/Compiler.php delete mode 100644 thirdparty/php-peg/LICENSE delete mode 100644 thirdparty/php-peg/PHPBuilder.php delete mode 100644 thirdparty/php-peg/Parser.php delete mode 100644 thirdparty/php-peg/README.md delete mode 100644 thirdparty/php-peg/cli.php diff --git a/_config/view.yml b/_config/view.yml index fd8293c9f3d..c2ad469aa2b 100644 --- a/_config/view.yml +++ b/_config/view.yml @@ -3,4 +3,4 @@ Name: view-config --- SilverStripe\Core\Injector\Injector: SilverStripe\View\TemplateEngine: - class: 'SilverStripe\View\SSTemplateEngine' + class: 'SilverStripe\TemplateEngine\SSTemplateEngine' diff --git a/composer.json b/composer.json index a88e48e38c9..b999cc896fe 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "silverstripe/config": "^3", "silverstripe/assets": "^3", "silverstripe/supported-modules": "^1.1", + "silverstripe/template-engine": "^1", "silverstripe/vendor-plugin": "^2", "sminnee/callbacklist": "^0.1.1", "symfony/cache": "^7.0", diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 723b740bbdb..ed73be69227 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -28,16 +28,9 @@ - - - /thirdparty/* - - - - */SSTemplateParser.php$ */_fakewebroot/* */fixtures/* diff --git a/src/View/SSTemplateEngine.php b/src/View/SSTemplateEngine.php deleted file mode 100644 index 22eca122fe9..00000000000 --- a/src/View/SSTemplateEngine.php +++ /dev/null @@ -1,468 +0,0 @@ -` template commands. - * - * Caching - * - * Compiled templates are cached, usually on the filesystem. - * If you put ?flush=1 on your URL, it will force the template to be recompiled. - * - */ -class SSTemplateEngine implements TemplateEngine, Flushable -{ - use Injectable; - use Configurable; - - /** - * Default prepended cache key for partial caching - */ - private static string $global_key = '$CurrentReadingMode, $CurrentUser.ID'; - - /** - * List of models being processed - */ - protected static array $topLevel = []; - - /** - * @internal - */ - private static bool $template_cache_flushed = false; - - /** - * @internal - */ - private static bool $cacheblock_cache_flushed = false; - - private ?CacheInterface $partialCacheStore = null; - - private ?TemplateParser $parser = null; - - /** - * A template or pool of candidate templates to choose from. - */ - private string|array $templateCandidates = []; - - /** - * Absolute path to chosen template file which will be used in the call to render() - */ - private ?string $chosen = null; - - /** - * Templates to use when looking up 'Layout' or 'Content' - */ - private array $subTemplates = []; - - public function __construct(string|array $templateCandidates = []) - { - if (!empty($templateCandidates)) { - $this->setTemplate($templateCandidates); - } - } - - /** - * Execute the given template, passing it the given data. - * Used by the <% include %> template tag to process included templates. - * - * @param array $overlay Associative array of fields (e.g. args into an include template) to inject into the - * template as properties. These override properties and methods with the same name from $data and from global - * template providers. - */ - public static function execute_template(array|string $template, ViewLayerData $data, array $overlay = [], ?SSViewer_Scope $scope = null): string - { - $engine = static::create($template); - return $engine->render($data, $overlay, $scope); - } - - /** - * Triggered early in the request when someone requests a flush. - */ - public static function flush(): void - { - SSTemplateEngine::flushTemplateCache(true); - SSTemplateEngine::flushCacheBlockCache(true); - } - - /** - * Clears all parsed template files in the cache folder. - * - * @param bool $force Set this to true to force a re-flush. If left to false, flushing - * will only be performed once a request. - */ - public static function flushTemplateCache(bool $force = false): void - { - if (!SSTemplateEngine::$template_cache_flushed || $force) { - $dir = dir(TEMP_PATH); - while (false !== ($file = $dir->read())) { - if (strstr($file ?? '', '.cache')) { - unlink(TEMP_PATH . DIRECTORY_SEPARATOR . $file); - } - } - SSTemplateEngine::$template_cache_flushed = true; - } - } - - /** - * Clears all partial cache blocks. - * - * @param bool $force Set this to true to force a re-flush. If left to false, flushing - * will only be performed once a request. - */ - public static function flushCacheBlockCache(bool $force = false): void - { - if (!SSTemplateEngine::$cacheblock_cache_flushed || $force) { - $cache = Injector::inst()->get(CacheInterface::class . '.cacheblock'); - $cache->clear(); - SSTemplateEngine::$cacheblock_cache_flushed = true; - } - } - - public function hasTemplate(array|string $templateCandidates): bool - { - return (bool) $this->findTemplate($templateCandidates); - } - - public function renderString(string $template, ViewLayerData $model, array $overlay = [], bool $cache = true): string - { - $hash = sha1($template); - $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . ".cache.$hash"; - - // Generate a file whether we're caching or not. - // This is an inefficiency that's required due to the way rendered templates get processed. - if (!file_exists($cacheFile) || Injector::inst()->get(Kernel::class)->isFlushed()) { - $content = $this->parseTemplateContent($template, "string sha1=$hash"); - $fh = fopen($cacheFile, 'w'); - fwrite($fh, $content); - fclose($fh); - } - - $output = $this->includeGeneratedTemplate($cacheFile, $model, $overlay, []); - - if (!$cache) { - unlink($cacheFile); - } - - return $output; - } - - public function render(ViewLayerData $model, array $overlay = [], ?SSViewer_Scope $scope = null): string - { - SSTemplateEngine::$topLevel[] = $model; - $template = $this->chosen; - - // If there's no template, throw an exception - if (!$template) { - if (empty($this->templateCandidates)) { - throw new MissingTemplateException( - 'No template to render. ' - . 'Try calling setTemplate() or passing template candidates into the constructor.' - ); - } - $message = 'None of the following templates could be found: '; - $message .= print_r($this->templateCandidates, true); - $themes = SSViewer::get_themes(); - if (!$themes) { - $message .= ' (no theme in use)'; - } else { - $message .= ' in themes "' . print_r($themes, true) . '"'; - } - throw new MissingTemplateException($message); - } - - $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache' - . str_replace(['\\','/',':'], '.', Director::makeRelative(realpath($template ?? '')) ?? ''); - $lastEdited = filemtime($template ?? ''); - - if (!file_exists($cacheFile ?? '') || filemtime($cacheFile ?? '') < $lastEdited) { - $content = file_get_contents($template ?? ''); - $content = $this->parseTemplateContent($content, $template); - - $fh = fopen($cacheFile ?? '', 'w'); - fwrite($fh, $content ?? ''); - fclose($fh); - } - - $underlay = ['I18NNamespace' => basename($template ?? '')]; - - // Makes the rendered sub-templates available on the parent model, - // through $Content and $Layout placeholders. - foreach (['Content', 'Layout'] as $subtemplate) { - // Detect sub-template to use - $sub = $this->getSubtemplateFor($subtemplate); - if (!$sub) { - continue; - } - - // Create lazy-evaluated underlay for this subtemplate - $underlay[$subtemplate] = function () use ($model, $overlay, $sub) { - $subtemplateViewer = clone $this; - // Select the right template and render if the template exists - $subtemplateViewer->setTemplate($sub); - // If there's no template for that underlay, just don't render anything. - // This mirrors how SSViewer_Scope handles null values. - if (!$subtemplateViewer->chosen) { - return null; - } - // Render and wrap in DBHTMLText so it doesn't get escaped - return DBHTMLText::create()->setValue($subtemplateViewer->render($model, $overlay)); - }; - } - - $output = $this->includeGeneratedTemplate($cacheFile, $model, $overlay, $underlay, $scope); - - array_pop(SSTemplateEngine::$topLevel); - - return $output; - } - - public function setTemplate(string|array $templateCandidates): static - { - $this->templateCandidates = $templateCandidates; - $this->chosen = $this->findTemplate($templateCandidates); - $this->subTemplates = []; - return $this; - } - - /** - * Set the template parser that will be used in template generation - */ - public function setParser(TemplateParser $parser): static - { - $this->parser = $parser; - return $this; - } - - /** - * Returns the parser that is set for template generation - */ - public function getParser(): TemplateParser - { - if (!$this->parser) { - $this->setParser(Injector::inst()->get(SSTemplateParser::class)); - } - return $this->parser; - } - - /** - * Set the cache object to use when storing / retrieving partial cache blocks. - */ - public function setPartialCacheStore(CacheInterface $cache): static - { - $this->partialCacheStore = $cache; - return $this; - } - - /** - * Get the cache object to use when storing / retrieving partial cache blocks. - */ - public function getPartialCacheStore(): CacheInterface - { - if (!$this->partialCacheStore) { - $this->partialCacheStore = Injector::inst()->get(CacheInterface::class . '.cacheblock'); - } - return $this->partialCacheStore; - } - - /** - * An internal utility function to set up variables in preparation for including a compiled - * template, then do the include - * - * @param string $cacheFile The path to the file that contains the template compiled to PHP - * @param ViewLayerData $model The model to use as the root scope for the template - * @param array $overlay Any variables to layer on top of the scope - * @param array $underlay Any variables to layer underneath the scope - * @param SSViewer_Scope|null $inheritedScope The current scope of a parent template including a sub-template - */ - protected function includeGeneratedTemplate( - string $cacheFile, - ViewLayerData $model, - array $overlay, - array $underlay, - ?SSViewer_Scope $inheritedScope = null - ): string { - if (isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) { - $lines = file($cacheFile ?? ''); - echo "

Template: $cacheFile

"; - echo '
';
-            foreach ($lines as $num => $line) {
-                echo str_pad($num+1, 5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
-            }
-            echo '
'; - } - - $cache = $this->getPartialCacheStore(); - $scope = new SSViewer_Scope($model, $overlay, $underlay, $inheritedScope); - $val = ''; - - // Placeholder for values exposed to $cacheFile - [$cache, $scope, $val]; - include($cacheFile); - - return $val; - } - - /** - * Get the appropriate template to use for the named sub-template, or null if none are appropriate - */ - protected function getSubtemplateFor(string $subtemplate): ?array - { - // Get explicit subtemplate name - if (isset($this->subTemplates[$subtemplate])) { - return $this->subTemplates[$subtemplate]; - } - - // Don't apply sub-templates if type is already specified (e.g. 'Includes') - if (isset($this->templateCandidates['type'])) { - return null; - } - - // Filter out any other typed templates as we can only add, not change type - $templates = array_filter( - (array) $this->templateCandidates, - function ($template) { - return !isset($template['type']); - } - ); - if (empty($templates)) { - return null; - } - - // Set type to subtemplate - $templates['type'] = $subtemplate; - return $templates; - } - - /** - * Parse given template contents - * - * @param string $content The template contents - * @param string $template The template file name - */ - protected function parseTemplateContent(string $content, string $template = ""): string - { - return $this->getParser()->compileString( - $content, - $template, - Director::isDev() && SSViewer::config()->uninherited('source_file_comments') - ); - } - - /** - * Attempts to find possible candidate templates from a set of template - * names from modules, current theme directory and finally the application - * folder. - * - * The template names can be passed in as plain strings, or be in the - * format "type/name", where type is the type of template to search for - * (e.g. Includes, Layout). - * - * The results of this method will be cached for future use. - * - * @param string|array $template Template name, or template spec in array format with the keys - * 'type' (type string) and 'templates' (template hierarchy in order of precedence). - * If 'templates' is omitted then any other item in the array will be treated as the template - * list, or list of templates each in the array spec given. - * Templates with an .ss extension will be treated as file paths, and will bypass - * theme-coupled resolution. - * @param array $themes List of themes to use to resolve themes. Defaults to {@see SSViewer::get_themes()} - * @return string Absolute path to resolved template file, or null if not resolved. - * File location will be in the format themes//templates///.ss - * Note that type (e.g. 'Layout') is not the root level directory under 'templates'. - * Returns null if no template was found. - */ - private function findTemplate(string|array $template, array $themes = []): ?string - { - if (empty($themes)) { - $themes = SSViewer::get_themes(); - } - - $cacheAdapter = ThemeResourceLoader::inst()->getCache(); - $cacheKey = 'findTemplate_' . md5(json_encode($template) . json_encode($themes)); - - // Look for a cached result for this data set - if ($cacheAdapter->has($cacheKey)) { - return $cacheAdapter->get($cacheKey); - } - - $type = ''; - if (is_array($template)) { - // Check if templates has type specified - if (array_key_exists('type', $template ?? [])) { - $type = $template['type']; - unset($template['type']); - } - // Templates are either nested in 'templates' or just the rest of the list - $templateList = array_key_exists('templates', $template ?? []) ? $template['templates'] : $template; - } else { - $templateList = [$template]; - } - - $themePaths = ThemeResourceLoader::inst()->getThemePaths($themes); - $baseDir = ThemeResourceLoader::inst()->getBase(); - foreach ($templateList as $i => $template) { - // Check if passed list of templates in array format - if (is_array($template)) { - $path = $this->findTemplate($template, $themes); - if ($path) { - $cacheAdapter->set($cacheKey, $path); - return $path; - } - continue; - } - - // If we have an .ss extension, this is a path, not a template name. We should - // pass in templates without extensions in order for template manifest to find - // files dynamically. - if (substr($template ?? '', -3) == '.ss' && file_exists($template ?? '')) { - $cacheAdapter->set($cacheKey, $template); - return $template; - } - - // Check string template identifier - $template = str_replace('\\', '/', $template ?? ''); - $parts = explode('/', $template ?? ''); - - $tail = array_pop($parts); - $head = implode('/', $parts); - foreach ($themePaths as $themePath) { - // Join path - $pathParts = [ $baseDir, $themePath, 'templates', $head, $type, $tail ]; - try { - $path = Path::join($pathParts) . '.ss'; - if (file_exists($path ?? '')) { - $cacheAdapter->set($cacheKey, $path); - return $path; - } - } catch (InvalidArgumentException $e) { - // No-op - } - } - } - - // No template found - $cacheAdapter->set($cacheKey, null); - return null; - } -} diff --git a/src/View/SSTemplateParseException.php b/src/View/SSTemplateParseException.php deleted file mode 100644 index b3665abef6c..00000000000 --- a/src/View/SSTemplateParseException.php +++ /dev/null @@ -1,29 +0,0 @@ -string ?? '', 0, $parser->pos); - - preg_match_all('/\r\n|\r|\n/', $prior ?? '', $matches); - $line = count($matches[0] ?? []) + 1; - - parent::__construct("Parse error in template on line $line. Error was: $message"); - } -} diff --git a/src/View/SSTemplateParser.peg b/src/View/SSTemplateParser.peg deleted file mode 100644 index f53d64b6097..00000000000 --- a/src/View/SSTemplateParser.peg +++ /dev/null @@ -1,1353 +0,0 @@ - SSTemplateParser.php - -See the php-peg docs for more information on the parser format, and how to convert this file into SSTemplateParser.php - -This comment will not appear in the output -*/ - -namespace SilverStripe\View; - -use SilverStripe\Core\Injector\Injector; -use Parser; -use InvalidArgumentException; - -// We want this to work when run by hand too -if (defined('THIRDPARTY_PATH')) { - require_once(THIRDPARTY_PATH . '/php-peg/Parser.php'); -} else { - $base = dirname(__FILE__); - require_once($base.'/../thirdparty/php-peg/Parser.php'); -} - -/** - * This is the parser for the SilverStripe template language. It gets called on a string and uses a php-peg parser - * to match that string against the language structure, building up the PHP code to execute that structure as it - * parses - * - * The $result array that is built up as part of the parsing (see thirdparty/php-peg/README.md for more on how - * parsers build results) has one special member, 'php', which contains the php equivalent of that part of the - * template tree. - * - * Some match rules generate alternate php, or other variations, so check the per-match documentation too. - * - * Terms used: - * - * Marked: A string or lookup in the template that has been explicitly marked as such - lookups by prepending with - * "$" (like $Foo.Bar), strings by wrapping with single or double quotes ('Foo' or "Foo") - * - * Bare: The opposite of marked. An argument that has to has it's type inferred by usage and 2.4 defaults. - * - * Example of using a bare argument for a loop block: <% loop Foo %> - * - * Block: One of two SS template structures. The special characters "<%" and "%>" are used to wrap the opening and - * (required or forbidden depending on which block exactly) closing block marks. - * - * Open Block: An SS template block that doesn't wrap any content or have a closing end tag (in fact, a closing end - * tag is forbidden) - * - * Closed Block: An SS template block that wraps content, and requires a counterpart <% end_blockname %> tag - * - * Angle Bracket: angle brackets "<" and ">" are used to eat whitespace between template elements - * N: eats white space including newlines (using in legacy _t support) - */ -class SSTemplateParser extends Parser implements TemplateParser -{ - - /** - * @var bool - Set true by SSTemplateParser::compileString if the template should include comments intended - * for debugging (template source, included files, etc) - */ - protected $includeDebuggingComments = false; - - /** - * Stores the user-supplied closed block extension rules in the form: - * [ - * 'name' => function (&$res) {} - * ] - * See SSTemplateParser::ClosedBlock_Handle_Loop for an example of what the callable should look like - * @var array - */ - protected $closedBlocks = []; - - /** - * Stores the user-supplied open block extension rules in the form: - * [ - * 'name' => function (&$res) {} - * ] - * See SSTemplateParser::OpenBlock_Handle_Base_tag for an example of what the callable should look like - * @var array - */ - protected $openBlocks = []; - - /** - * Allow the injection of new closed & open block callables - * @param array $closedBlocks - * @param array $openBlocks - */ - public function __construct($closedBlocks = [], $openBlocks = []) - { - parent::__construct(null); - $this->setClosedBlocks($closedBlocks); - $this->setOpenBlocks($openBlocks); - } - - /** - * Override the function that constructs the result arrays to also prepare a 'php' item in the array - */ - function construct($matchrule, $name, $arguments = null) - { - $res = parent::construct($matchrule, $name, $arguments); - if (!isset($res['php'])) { - $res['php'] = ''; - } - return $res; - } - - /** - * Set the closed blocks that the template parser should use - * - * This method will delete any existing closed blocks, please use addClosedBlock if you don't - * want to overwrite - * @param array $closedBlocks - * @throws InvalidArgumentException - */ - public function setClosedBlocks($closedBlocks) - { - $this->closedBlocks = []; - foreach ((array) $closedBlocks as $name => $callable) { - $this->addClosedBlock($name, $callable); - } - } - - /** - * Set the open blocks that the template parser should use - * - * This method will delete any existing open blocks, please use addOpenBlock if you don't - * want to overwrite - * @param array $openBlocks - * @throws InvalidArgumentException - */ - public function setOpenBlocks($openBlocks) - { - $this->openBlocks = []; - foreach ((array) $openBlocks as $name => $callable) { - $this->addOpenBlock($name, $callable); - } - } - - /** - * Add a closed block callable to allow <% name %><% end_name %> syntax - * @param string $name The name of the token to be used in the syntax <% name %><% end_name %> - * @param callable $callable The function that modifies the generation of template code - * @throws InvalidArgumentException - */ - public function addClosedBlock($name, $callable) - { - $this->validateExtensionBlock($name, $callable, 'Closed block'); - $this->closedBlocks[$name] = $callable; - } - - /** - * Add a closed block callable to allow <% name %> syntax - * @param string $name The name of the token to be used in the syntax <% name %> - * @param callable $callable The function that modifies the generation of template code - * @throws InvalidArgumentException - */ - public function addOpenBlock($name, $callable) - { - $this->validateExtensionBlock($name, $callable, 'Open block'); - $this->openBlocks[$name] = $callable; - } - - /** - * Ensures that the arguments to addOpenBlock and addClosedBlock are valid - * @param $name - * @param $callable - * @param $type - * @throws InvalidArgumentException - */ - protected function validateExtensionBlock($name, $callable, $type) - { - if (!is_string($name)) { - throw new InvalidArgumentException( - sprintf( - "Name argument for %s must be a string", - $type - ) - ); - } elseif (!is_callable($callable)) { - throw new InvalidArgumentException( - sprintf( - "Callable %s argument named '%s' is not callable", - $type, - $name - ) - ); - } - } - - /*!* SSTemplateParser - - # Template is any structurally-complete portion of template (a full nested level in other words). It's the - # primary matcher, and is used by all enclosing blocks, as well as a base for the top level. - # Any new template elements need to be included in this list, if they are to work. - - Template: (Comment | Translate | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | - OpenBlock | MalformedBlock | MalformedBracketInjection | Injection | Text)+ - */ - function Template_STR(&$res, $sub) - { - $res['php'] .= $sub['php'] . PHP_EOL ; - } - - /*!* - - Word: / [A-Za-z_] [A-Za-z0-9_]* / - NamespacedWord: / [A-Za-z_\/\\] [A-Za-z0-9_\/\\]* / - Number: / [0-9]+ / - Value: / [A-Za-z0-9_]+ / - - # CallArguments is a list of one or more comma separated "arguments" (lookups or strings, either bare or marked) - # as passed to a Call within brackets - - CallArguments: :Argument ( < "," < :Argument )* - */ - - /** - * Values are bare words in templates, but strings in PHP. We rely on PHP's type conversion to back-convert - * strings to numbers when needed. - */ - function CallArguments_Argument(&$res, $sub) - { - if ($res['php'] !== '') { - $res['php'] .= ', '; - } - - $res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : - str_replace('$$FINAL', 'getValueAsArgument', $sub['php'] ?? ''); - } - - /*!* - - # Call is a php-style function call, e.g. Method(Argument, ...). Unlike PHP, the brackets are optional if no - # arguments are passed - - Call: Method:Word ( "(" < :CallArguments? > ")" )? - - # A lookup is a lookup of a value on the current scope object. It's a sequence of calls separated by "." - # characters. This final call in the sequence needs handling specially, as different structures need different - # sorts of values, which require a different final method to be called to get the right return value - - LookupStep: :Call &"." - LastLookupStep: :Call - - Lookup: LookupStep ("." LookupStep)* "." LastLookupStep | LastLookupStep - */ - - function Lookup__construct(&$res) - { - $res['php'] = '$scope->locally()'; - $res['LookupSteps'] = []; - } - - /** - * The basic generated PHP of LookupStep and LastLookupStep is the same, except that LookupStep calls 'scopeToIntermediateValue' to - * get the next ModelData in the sequence, and LastLookupStep calls different methods (getOutputValue, hasValue, scopeToIntermediateValue) - * depending on the context the lookup is used in. - */ - function Lookup_AddLookupStep(&$res, $sub, $method) - { - $res['LookupSteps'][] = $sub; - - $property = $sub['Call']['Method']['text']; - - $arguments = ''; - if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) { - $arguments = $sub['Call']['CallArguments']['php']; - } - $res['php'] .= "->$method('$property', [$arguments])"; - } - - function Lookup_LookupStep(&$res, $sub) - { - $this->Lookup_AddLookupStep($res, $sub, 'scopeToIntermediateValue'); - } - - function Lookup_LastLookupStep(&$res, $sub) - { - $this->Lookup_AddLookupStep($res, $sub, '$$FINAL'); - } - - - /*!* - - # New Translatable Syntax - # <%t Entity DefaultString is Context name1=string name2=$functionCall - # (This is a new way to call translatable strings. The parser transforms this into a call to the _t() method) - - Translate: "<%t" < Entity < (Default:QuotedString)? < (!("is" "=") < "is" < Context:QuotedString)? < - (InjectionVariables)? > "%>" - InjectionVariables: (< InjectionName:Word "=" Argument)+ - Entity: / [A-Za-z_\\] [\w\.\\]* / - */ - - function Translate__construct(&$res) - { - $res['php'] = '$val .= _t('; - } - - function Translate_Entity(&$res, $sub) - { - $res['php'] .= "'$sub[text]'"; - } - - function Translate_Default(&$res, $sub) - { - $res['php'] .= ",$sub[text]"; - } - - function Translate_Context(&$res, $sub) - { - $res['php'] .= ",$sub[text]"; - } - - function Translate_InjectionVariables(&$res, $sub) - { - $res['php'] .= ",$sub[php]"; - } - - function Translate__finalise(&$res) - { - $res['php'] .= ');'; - } - - function InjectionVariables__construct(&$res) - { - $res['php'] = "["; - } - - function InjectionVariables_InjectionName(&$res, $sub) - { - $res['php'] .= "'$sub[text]'=>"; - } - - function InjectionVariables_Argument(&$res, $sub) - { - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? '') . ','; - } - - function InjectionVariables__finalise(&$res) - { - if (substr($res['php'] ?? '', -1) == ',') { - $res['php'] = substr($res['php'] ?? '', 0, -1); //remove last comma in the array - } - $res['php'] .= ']'; - } - - /*!* - - # This is used to detect a malformed bracket injection - where the closing '}' is missing - - MalformedBracketInjection: "{$" :Lookup !( "}" ) - */ - function MalformedBracketInjection__finalise(&$res) - { - $lookup = $res['text']; - throw new SSTemplateParseException("Malformed bracket injection $lookup. Perhaps you have forgotten the " . - "closing bracket (})?", $this); - } - - /*!* - - # Injections are where, outside of a block, a value needs to be inserted into the output. You can either - # just do $Foo, or {$Foo} if the surrounding text would cause a problem (e.g. {$Foo}Bar) - - SimpleInjection: '$' :Lookup - BracketInjection: '{$' :Lookup "}" - Injection: BracketInjection | SimpleInjection - */ - function Injection_STR(&$res, $sub) - { - $res['php'] = '$val .= '. str_replace('$$FINAL', 'getOutputValue', $sub['Lookup']['php'] ?? '') . ';'; - } - - /*!* - - # Inside a block's arguments you can still use the same format as a simple injection ($Foo). In this case - # it marks the argument as being a lookup, not a string (if it was bare it might still be used as a lookup, - # but that depends on where it's used, a la 2.4) - - DollarMarkedLookup: SimpleInjection - */ - function DollarMarkedLookup_STR(&$res, $sub) - { - $res['Lookup'] = $sub['Lookup']; - } - - /*!* - - # Inside a block's arguments you can explicitly mark a string by surrounding it with quotes (single or double, - # but they must be matching). If you do, inside the quote you can escape any character, but the only character - # that _needs_ escaping is the matching closing quote - - QuotedString: q:/['"]/ String:/ (\\\\ | \\. | [^$q\\])* / '$q' - - # Null - - Null: / (null)\b /i - - # Booleans - - Boolean: / (true|false)\b /i - - # Integers and floats - - Sign: / [+-] / - - Float: / [0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? / - Hexadecimal: / 0[xX][0-9a-fA-F]+ / - Octal: / 0[0-7]+ / - Binary: / 0[bB][01]+ / - Decimal: / 0 | [1-9][0-9]* / - - # The order of the below statements is important. E.g. with Float first, the '0' in a '0x1A' hexadecimal would - # match the first part of the float regexp, so would stop matching there - IntegerOrFloat: ( Sign )? ( Hexadecimal | Binary | Float | Octal | Decimal ) - - # In order to support 2.4's base syntax, we also need to detect free strings - strings not surrounded by - # quotes, and containing spaces or punctuation, but supported as a single string. We support almost as flexible - # a string as 2.4 - we don't attempt to determine the closing character by context, but just break on any - # character which, in some context, would indicate the end of a free string, regardless of if we're actually in - # that context or not - - FreeString: /[^,)%!=><|&]+/ - - # An argument - either a marked value, or a bare value, preferring lookup matching on the bare value over - # freestring matching as long as that would give a successful parse - - Argument: - :DollarMarkedLookup | - :QuotedString | - :Null | - :Boolean | - :IntegerOrFloat | - :Lookup !(< FreeString)| - :FreeString - */ - - /** - * If we get a bare value, we don't know enough to determine exactly what php would be the translation, because - * we don't know if the position of use indicates a lookup or a string argument. - * - * Instead, we record 'ArgumentMode' as a member of this matches results node, which can be: - * - lookup if this argument was unambiguously a lookup (marked as such) - * - string is this argument was unambiguously a string (marked as such, or impossible to parse as lookup) - * - default if this argument needs to be handled as per 2.4 - * - * In the case of 'default', there is no php member of the results node, but instead 'lookup_php', which - * should be used by the parent if the context indicates a lookup, and 'string_php' which should be used - * if the context indicates a string - */ - - function Argument_DollarMarkedLookup(&$res, $sub) - { - $res['ArgumentMode'] = 'lookup'; - $res['php'] = $sub['Lookup']['php']; - } - - function Argument_QuotedString(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text'] ?? '') . "'"; - } - - function Argument_Null(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_Boolean(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_IntegerOrFloat(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_Lookup(&$res, $sub) - { - if (count($sub['LookupSteps'] ?? []) == 1 && !isset($sub['LookupSteps'][0]['Call']['Arguments'])) { - $res['ArgumentMode'] = 'default'; - $res['lookup_php'] = $sub['php']; - $res['string_php'] = "'".$sub['LookupSteps'][0]['Call']['Method']['text']."'"; - } else { - $res['ArgumentMode'] = 'lookup'; - $res['php'] = $sub['php']; - } - } - - function Argument_FreeString(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = "'" . str_replace("'", "\\'", trim($sub['text'] ?? '')) . "'"; - } - - /*!* - - # if and else_if blocks allow basic comparisons between arguments - - ComparisonOperator: "!=" | "==" | ">=" | ">" | "<=" | "<" | "=" - - Comparison: Argument < ComparisonOperator > Argument - */ - function Comparison_Argument(&$res, $sub) - { - if ($sub['ArgumentMode'] == 'default') { - if (!empty($res['php'])) { - $res['php'] .= $sub['string_php']; - } else { - $res['php'] = str_replace('$$FINAL', 'getOutputValue', $sub['lookup_php'] ?? ''); - } - } else { - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? ''); - } - } - - function Comparison_ComparisonOperator(&$res, $sub) - { - $res['php'] .= ($sub['text'] == '=' ? '==' : $sub['text']); - } - - /*!* - - # If a comparison operator is not used in an if or else_if block, then the statement is a 'presence check', - # which checks if the argument given is present or not. For explicit strings (which were not allowed in 2.4) - # this falls back to simple truthiness check - - PresenceCheck: (Not:'not' <)? Argument - */ - function PresenceCheck_Not(&$res, $sub) - { - $res['php'] = '!'; - } - - function PresenceCheck_Argument(&$res, $sub) - { - if ($sub['ArgumentMode'] == 'string') { - $res['php'] .= '((bool)'.$sub['php'].')'; - } else { - $php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']); - $res['php'] .= str_replace('$$FINAL', 'hasValue', $php ?? ''); - } - } - - /*!* - - # if and else_if arguments are a series of presence checks and comparisons, optionally separated by boolean - # operators - - IfArgumentPortion: Comparison | PresenceCheck - */ - function IfArgumentPortion_STR(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /*!* - - # if and else_if arguments can be combined via these two boolean operators. No precedence overriding is - # supported - - BooleanOperator: "||" | "&&" - - # This is the combination of the previous if and else_if argument portions - - IfArgument: :IfArgumentPortion ( < :BooleanOperator < :IfArgumentPortion )* - */ - function IfArgument_IfArgumentPortion(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function IfArgument_BooleanOperator(&$res, $sub) - { - $res['php'] .= $sub['text']; - } - - /*!* - - # ifs are handled separately from other closed block tags, because (A) their structure is different - they - # can have else_if and else tags in between the if tag and the end_if tag, and (B) they have a different - # argument structure to every other block - - IfPart: '<%' < 'if' [ :IfArgument > '%>' Template:$TemplateMatcher? - ElseIfPart: '<%' < 'else_if' [ :IfArgument > '%>' Template:$TemplateMatcher? - ElsePart: '<%' < 'else' > '%>' Template:$TemplateMatcher? - - If: IfPart ElseIfPart* ElsePart? '<%' < 'end_if' > '%>' - */ - function If_IfPart(&$res, $sub) - { - $res['php'] = - 'if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - function If_ElseIfPart(&$res, $sub) - { - $res['php'] .= - 'else if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - function If_ElsePart(&$res, $sub) - { - $res['php'] .= - 'else { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - /*!* - - # The require block is handled separately to the other open blocks as the argument syntax is different - # - must have one call style argument, must pass arguments to that call style argument - - Require: '<%' < 'require' [ Call:(Method:Word "(" < :CallArguments > ")") > '%>' - */ - function Require_Call(&$res, $sub) - { - $requirements = '\\SilverStripe\\View\\Requirements'; - $res['php'] = "{$requirements}::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');'; - } - - - /*!* - - # Cache block arguments don't support free strings - - CacheBlockArgument: - !( "if " | "unless " ) - ( - :DollarMarkedLookup | - :QuotedString | - :Lookup - ) - */ - function CacheBlockArgument_DollarMarkedLookup(&$res, $sub) - { - $res['php'] = $sub['Lookup']['php']; - } - - function CacheBlockArgument_QuotedString(&$res, $sub) - { - $res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text'] ?? '') . "'"; - } - - function CacheBlockArgument_Lookup(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /*!* - - # Collects the arguments passed in to be part of the key of a cacheblock - - CacheBlockArguments: CacheBlockArgument ( < "," < CacheBlockArgument )* - - */ - function CacheBlockArguments_CacheBlockArgument(&$res, $sub) - { - if (!empty($res['php'])) { - $res['php'] .= ".'_'."; - } else { - $res['php'] = ''; - } - - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? ''); - } - - /*!* - # CacheBlockTemplate is the same as Template, but doesn't include cache blocks (because they're handled separately) - - CacheBlockTemplate extends Template (TemplateMatcher = CacheRestrictedTemplate); CacheBlock | UncachedBlock | => '' - */ - - /*!* - - UncachedBlock: - '<%' < "uncached" < CacheBlockArguments? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>' - Template:$TemplateMatcher? - '<%' < 'end_' ("uncached"|"cached"|"cacheblock") > '%>' - */ - function UncachedBlock_Template(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /*!* - - # CacheRestrictedTemplate is the same as Template, but doesn't allow cache blocks - - CacheRestrictedTemplate extends Template - */ - function CacheRestrictedTemplate_CacheBlock(&$res, $sub) - { - throw new SSTemplateParseException('You cant have cache blocks nested within with, loop or control blocks ' . - 'that are within cache blocks', $this); - } - - function CacheRestrictedTemplate_UncachedBlock(&$res, $sub) - { - throw new SSTemplateParseException('You cant have uncache blocks nested within with, loop or control blocks ' . - 'that are within cache blocks', $this); - } - - /*!* - # The partial caching block - - CacheBlock: - '<%' < CacheTag:("cached"|"cacheblock") < (CacheBlockArguments)? ( < Conditional:("if"|"unless") > - Condition:IfArgument )? > '%>' - (CacheBlock | UncachedBlock | CacheBlockTemplate)* - '<%' < 'end_' ("cached"|"uncached"|"cacheblock") > '%>' - - */ - function CacheBlock__construct(&$res) - { - $res['subblocks'] = 0; - } - - function CacheBlock_CacheBlockArguments(&$res, $sub) - { - $res['key'] = !empty($sub['php']) ? $sub['php'] : ''; - } - - function CacheBlock_Condition(&$res, $sub) - { - $res['condition'] = ($res['Conditional']['text'] == 'if' ? '(' : '!(') . $sub['php'] . ') && '; - } - - function CacheBlock_CacheBlock(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function CacheBlock_UncachedBlock(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function CacheBlock_CacheBlockTemplate(&$res, $sub) - { - // Get the block counter - $block = ++$res['subblocks']; - // Build the key for this block from the global key (evaluated in a closure within the template), - // the passed cache key, the block index, and the sha hash of the template. - $res['php'] .= '$keyExpression = function() use ($scope, $cache) {' . PHP_EOL; - $res['php'] .= '$val = \'\';' . PHP_EOL; - if ($globalKey = SSTemplateEngine::config()->get('global_key')) { - // Embed the code necessary to evaluate the globalKey directly into the template, - // so that SSTemplateParser only needs to be called during template regeneration. - // Warning: If the global key is changed, it's necessary to flush the template cache. - $parser = Injector::inst()->get(__CLASS__, false); - $result = $parser->compileString($globalKey, '', false, false); - if (!$result) { - throw new SSTemplateParseException('Unexpected problem parsing template', $parser); - } - $res['php'] .= $result . PHP_EOL; - } - $res['php'] .= 'return $val;' . PHP_EOL; - $res['php'] .= '};' . PHP_EOL; - $key = 'sha1($keyExpression())' // Global key - . '.\'_' . sha1($sub['php'] ?? '') // sha of template - . (isset($res['key']) && $res['key'] ? "_'.sha1(".$res['key'].")" : "'") // Passed key - . ".'_$block'"; // block index - // Get any condition - $condition = isset($res['condition']) ? $res['condition'] : ''; - - $res['php'] .= 'if ('.$condition.'($partial = $cache->get('.$key.'))) $val .= $partial;' . PHP_EOL; - $res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL; - $res['php'] .= $sub['php'] . PHP_EOL; - $res['php'] .= $condition . ' $cache->set('.$key.', $val); $val = $oldval . $val;' . PHP_EOL; - $res['php'] .= '}'; - } - - /*!* - - # Deprecated old-style i18n _t and sprintf(_t block tags. We support a slightly more flexible version than we used - # to, but just because it's easier to do so. It's strongly recommended to use the new syntax - - # This is the core used by both syntaxes, without the block start & end tags - - OldTPart: "_t" N "(" N QuotedString (N "," N CallArguments)? N ")" N (";")? - - # whitespace with a newline - N: / [\s\n]* / - */ - function OldTPart__construct(&$res) - { - $res['php'] = "_t("; - } - - function OldTPart_QuotedString(&$res, $sub) - { - $entity = $sub['String']['text']; - if (strpos($entity ?? '', '.') === false) { - $res['php'] .= "\$scope->getOutputValue('I18NNamespace').'.$entity'"; - } else { - $res['php'] .= "'$entity'"; - } - } - - function OldTPart_CallArguments(&$res, $sub) - { - $res['php'] .= ',' . $sub['php']; - } - - function OldTPart__finalise(&$res) - { - $res['php'] .= ')'; - } - - /*!* - - # This is the old <% _t() %> tag - - OldTTag: "<%" < OldTPart > "%>" - - */ - function OldTTag_OldTPart(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /*!* - - # This is the old <% sprintf(_t()) %> tag - - OldSprintfTag: "<%" < "sprintf" < "(" < OldTPart < "," < CallArguments > ")" > "%>" - - */ - function OldSprintfTag__construct(&$res) - { - $res['php'] = "sprintf("; - } - - function OldSprintfTag_OldTPart(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function OldSprintfTag_CallArguments(&$res, $sub) - { - $res['php'] .= ',' . $sub['php'] . ')'; - } - - /*!* - - # This matches either the old style sprintf(_t()) or _t() tags. As well as including the output portion of the - # php, this rule combines all the old i18n stuff into a single match rule to make it easy to not support these - # tags later - - OldI18NTag: OldSprintfTag | OldTTag - - */ - function OldI18NTag_STR(&$res, $sub) - { - $res['php'] = '$val .= ' . $sub['php'] . ';'; - } - - /*!* - - # An argument that can be passed through to an included template - - NamedArgument: Name:Word "=" Value:Argument - - */ - function NamedArgument_Name(&$res, $sub) - { - $res['php'] = "'" . $sub['text'] . "' => "; - } - - function NamedArgument_Value(&$res, $sub) - { - switch ($sub['ArgumentMode']) { - case 'string': - $res['php'] .= $sub['php']; - break; - - case 'default': - $res['php'] .= $sub['string_php']; - break; - - default: - $res['php'] .= str_replace('$$FINAL', 'scopeToIntermediateValue', $sub['php'] ?? '') . '->self()'; - break; - } - } - - /*!* - - # The include tag - - Include: "<%" < "include" < Template:NamespacedWord < (NamedArgument ( < "," < NamedArgument )*)? > "%>" - - */ - function Include__construct(&$res) - { - $res['arguments'] = []; - } - - function Include_Template(&$res, $sub) - { - $res['template'] = "'" . $sub['text'] . "'"; - } - - function Include_NamedArgument(&$res, $sub) - { - $res['arguments'][] = $sub['php']; - } - - function Include__finalise(&$res) - { - $template = $res['template']; - $arguments = $res['arguments']; - - // Note: 'type' here is important to disable subTemplates in SSTemplateEngine::getSubtemplateFor() - $res['php'] = '$val .= \\SilverStripe\\View\\SSTemplateEngine::execute_template([["type" => "Includes", '.$template.'], '.$template.'], $scope->getCurrentItem(), [' . - implode(',', $arguments)."], \$scope, true);\n"; - - if ($this->includeDebuggingComments) { // Add include filename comments on dev sites - $res['php'] = - '$val .= \'\';'. "\n". - $res['php']. - '$val .= \'\';'. "\n"; - } - } - - /*!* - - # To make the block support reasonably extendable, we don't explicitly define each closed block and it's structure, - # but instead match against a generic <% block_name argument, ... %> pattern. Each argument is left as per the - # output of the Argument matcher, and the handler (see the PHPDoc block later for more on this) is responsible - # for pulling out the info required - - BlockArguments: :Argument ( < "," < :Argument)* - - # NotBlockTag matches against any word that might come after a "<%" that the generic open and closed block handlers - # shouldn't attempt to match against, because they're handled by more explicit matchers - - NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock" | "include")]) - - # Match against closed blocks - blocks with an opening and a closing tag that surround some internal portion of - # template - - ClosedBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > Zap:'%>' Template:$TemplateMatcher? - '<%' < 'end_' '$BlockName' > '%>' - */ - - /** - * As mentioned in the parser comment, block handling is kept fairly generic for extensibility. The match rule - * builds up two important elements in the match result array: - * 'ArgumentCount' - how many arguments were passed in the opening tag - * 'Arguments' an array of the Argument match rule result arrays - * - * Once a block has successfully been matched against, it will then look for the actual handler, which should - * be on this class (either defined or extended on) as ClosedBlock_Handler_Name(&$res), where Name is the - * tag name, first letter captialized (i.e Control, Loop, With, etc). - * - * This function will be called with the match rule result array as it's first argument. It should return - * the php result of this block as it's return value, or throw an error if incorrect arguments were passed. - */ - - function ClosedBlock__construct(&$res) - { - $res['ArgumentCount'] = 0; - } - - function ClosedBlock_BlockArguments(&$res, $sub) - { - if (isset($sub['Argument']['ArgumentMode'])) { - $res['Arguments'] = [$sub['Argument']]; - $res['ArgumentCount'] = 1; - } else { - $res['Arguments'] = $sub['Argument']; - $res['ArgumentCount'] = count($res['Arguments'] ?? []); - } - } - - function ClosedBlock__finalise(&$res) - { - $blockname = $res['BlockName']['text']; - - $method = 'ClosedBlock_Handle_'.$blockname; - if (method_exists($this, $method ?? '')) { - $res['php'] = $this->$method($res); - } elseif (isset($this->closedBlocks[$blockname])) { - $res['php'] = call_user_func($this->closedBlocks[$blockname], $res); - } else { - throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are ' . - 'not supposed to close this block, or have mis-spelled it?', $this); - } - } - - /** - * This is an example of a block handler function. This one handles the loop tag. - */ - function ClosedBlock_Handle_Loop(&$res) - { - if ($res['ArgumentCount'] > 1) { - throw new SSTemplateParseException('Too many arguments in control block. Must be one or no' . - 'arguments only.', $this); - } - - // loop without arguments loops on the current scope - if ($res['ArgumentCount'] == 0) { - $on = '$scope->locally()->self()'; - } else { //loop in the normal way - $arg = $res['Arguments'][0]; - if ($arg['ArgumentMode'] == 'string') { - throw new SSTemplateParseException('Control block cant take string as argument.', $this); - } - $on = str_replace( - '$$FINAL', - 'scopeToIntermediateValue', - ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php'] - ); - } - - return - $on . '; $scope->pushScope(); while ($scope->next() !== false) {' . PHP_EOL . - $res['Template']['php'] . PHP_EOL . - '}; $scope->popScope(); '; - } - - /** - * The closed block handler for with blocks - */ - function ClosedBlock_Handle_With(&$res) - { - if ($res['ArgumentCount'] != 1) { - throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one ' . - 'argument only.', $this); - } - - $arg = $res['Arguments'][0]; - if ($arg['ArgumentMode'] == 'string') { - throw new SSTemplateParseException('Control block cant take string as argument.', $this); - } - - $on = str_replace('$$FINAL', 'scopeToIntermediateValue', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']); - return - $on . '; $scope->pushScope();' . PHP_EOL . - $res['Template']['php'] . PHP_EOL . - '; $scope->popScope(); '; - } - - /*!* - - # Open blocks are handled in the same generic manner as closed blocks. There is no need to define which blocks - # are which - closed is tried first, and if no matching end tag is found, open is tried next - - OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>' - */ - function OpenBlock__construct(&$res) - { - $res['ArgumentCount'] = 0; - } - - function OpenBlock_BlockArguments(&$res, $sub) - { - if (isset($sub['Argument']['ArgumentMode'])) { - $res['Arguments'] = [$sub['Argument']]; - $res['ArgumentCount'] = 1; - } else { - $res['Arguments'] = $sub['Argument']; - $res['ArgumentCount'] = count($res['Arguments'] ?? []); - } - } - - function OpenBlock__finalise(&$res) - { - $blockname = $res['BlockName']['text']; - - $method = 'OpenBlock_Handle_'.$blockname; - if (method_exists($this, $method ?? '')) { - $res['php'] = $this->$method($res); - } elseif (isset($this->openBlocks[$blockname])) { - $res['php'] = call_user_func($this->openBlocks[$blockname], $res); - } else { - throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed ' . - ' the closing tag or have mis-spelled it?', $this); - } - } - - /** - * This is an open block handler, for the <% base_tag %> tag - */ - function OpenBlock_Handle_Base_tag(&$res) - { - if ($res['ArgumentCount'] != 0) { - throw new SSTemplateParseException('Base_tag takes no arguments', $this); - } - $code = '$isXhtml = preg_match(\'/]+xhtml/i\', $val);'; - $code .= PHP_EOL . '$val .= \\SilverStripe\\View\\SSViewer::getBaseTag($isXhtml);'; - return $code; - } - - /** - * This is an open block handler, for the <% current_page %> tag - */ - function OpenBlock_Handle_Current_page(&$res) - { - if ($res['ArgumentCount'] != 0) { - throw new SSTemplateParseException('Current_page takes no arguments', $this); - } - return '$val .= $_SERVER[SCRIPT_URL];'; - } - - /*!* - - # This is used to detect when we have a mismatched closing tag (i.e., one with no equivalent opening tag) - # Because of parser limitations, this can only be used at the top nesting level of a template. Other mismatched - # closing tags are detected as an invalid open tag - - MismatchedEndBlock: '<%' < 'end_' :Word > '%>' - */ - function MismatchedEndBlock__finalise(&$res) - { - $blockname = $res['Word']['text']; - throw new SSTemplateParseException('Unexpected close tag end_' . $blockname . - ' encountered. Perhaps you have mis-nested blocks, or have mis-spelled a tag?', $this); - } - - /*!* - - # This is used to detect a malformed opening tag - one where the tag is opened with the "<%" characters, but - # the tag is not structured properly - - MalformedOpenTag: '<%' < !NotBlockTag Tag:Word !( ( [ :BlockArguments ] )? > '%>' ) - */ - function MalformedOpenTag__finalise(&$res) - { - $tag = $res['Tag']['text']; - throw new SSTemplateParseException("Malformed opening block tag $tag. Perhaps you have tried to use operators?", $this); - } - - /*!* - - # This is used to detect a malformed end tag - one where the tag is opened with the "<%" characters, but - # the tag is not structured properly - - MalformedCloseTag: '<%' < Tag:('end_' :Word ) !( > '%>' ) - */ - function MalformedCloseTag__finalise(&$res) - { - $tag = $res['Tag']['text']; - throw new SSTemplateParseException("Malformed closing block tag $tag. Perhaps you have tried to pass an " . - "argument to one?", $this); - } - - /*!* - - # This is used to detect a malformed tag. It's mostly to keep the Template match rule a bit shorter - - MalformedBlock: MalformedOpenTag | MalformedCloseTag - */ - - /*!* - - # This is used to remove template comments - - CommentWithContent: '<%--' ( !"--%>" /(?s)./ )+ '--%>' - EmptyComment: '<%----%>' - Comment: :EmptyComment | :CommentWithContent - */ - function Comment__construct(&$res) - { - $res['php'] = ''; - } - - /*!* - - # TopTemplate is the same as Template, but should only be used at the top level (not nested), as it includes - # MismatchedEndBlock detection, which only works at the top level - - TopTemplate extends Template (TemplateMatcher = Template); MalformedBlock => MalformedBlock | MismatchedEndBlock - */ - - /** - * The TopTemplate also includes the opening stanza to start off the template - */ - function TopTemplate__construct(&$res) - { - $res['php'] = "]+href *= *)"#/i', - '\\1"\' . ' . addcslashes($code ?? '', '\\') . ' . \'#', - $text ?? '' - ); - - $res['php'] .= '$val .= \'' . $text . '\';' . PHP_EOL; - } - - /****************** - * Here ends the parser itself. Below are utility methods to use the parser - */ - - /** - * Compiles some passed template source code into the php code that will execute as per the template source. - * - * @throws SSTemplateParseException - * @param string $string The source of the template - * @param string $templateName The name of the template, normally the filename the template source was loaded from - * @param bool $includeDebuggingComments True is debugging comments should be included in the output - * @param bool $topTemplate True if this is a top template, false if it's just a template - * @return string The php that, when executed (via include or exec) will behave as per the template source - */ - public function compileString(string $string, string $templateName = "", bool $includeDebuggingComments = false, bool $topTemplate = true): string - { - if (!trim($string ?? '')) { - $code = ''; - } else { - parent::__construct($string); - - $this->includeDebuggingComments = $includeDebuggingComments; - - // Ignore UTF8 BOM at beginning of string. - if (substr($string ?? '', 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) { - $this->pos = 3; - } - - // Match the source against the parser - if ($topTemplate) { - $result = $this->match_TopTemplate(); - } else { - $result = $this->match_Template(); - } - if (!$result) { - throw new SSTemplateParseException('Unexpected problem parsing template', $this); - } - - // Get the result - $code = $result['php']; - } - - // Include top level debugging comments if desired - if ($includeDebuggingComments && $templateName && stripos($code ?? '', "includeDebuggingComments($code, $templateName); - } - - return $code; - } - - /** - * @param string $code - * @param string $templateName - * @return string $code - */ - protected function includeDebuggingComments(string $code, string $templateName): string - { - // If this template contains a doctype, put it right after it, - // if not, put it after the tag to avoid IE glitches - if (stripos($code ?? '', "]*("[^"]")*[^>]*>)/im', "$1\r\n", $code ?? ''); - $code .= "\r\n" . '$val .= \'\';'; - } elseif (stripos($code ?? '', "]*>)(.*)/i', function ($matches) use ($templateName) { - if (stripos($matches[3] ?? '', '') !== false) { - // after this tag there is a comment close but no comment has been opened - // this most likely means that this tag is inside a comment - // we should not add a comment inside a comment (invalid html) - // lets append it at the end of the comment - // an example case for this is the html5boilerplate: - return $matches[0]; - } else { - // all other cases, add the comment and return it - return "{$matches[1]}{$matches[2]}{$matches[3]}"; - } - }, $code ?? ''); - $code = preg_replace('/(<\/html[^>]*>)/i', "$1", $code ?? ''); - } else { - $code = str_replace('\';' . "\r\n", $code ?? ''); - $code .= "\r\n" . '$val .= \'\';'; - } - return $code; - } - - /** - * Compiles some file that contains template source code, and returns the php code that will execute as per that - * source - * - * @param string $template - A file path that contains template source code - * @return string - The php that, when executed (via include or exec) will behave as per the template source - */ - public function compileFile(string $template): string - { - return $this->compileString(file_get_contents($template ?? ''), $template); - } -} diff --git a/src/View/SSTemplateParser.php b/src/View/SSTemplateParser.php deleted file mode 100644 index d391af043e9..00000000000 --- a/src/View/SSTemplateParser.php +++ /dev/null @@ -1,5386 +0,0 @@ - - * - * Block: One of two SS template structures. The special characters "<%" and "%>" are used to wrap the opening and - * (required or forbidden depending on which block exactly) closing block marks. - * - * Open Block: An SS template block that doesn't wrap any content or have a closing end tag (in fact, a closing end - * tag is forbidden) - * - * Closed Block: An SS template block that wraps content, and requires a counterpart <% end_blockname %> tag - * - * Angle Bracket: angle brackets "<" and ">" are used to eat whitespace between template elements - * N: eats white space including newlines (using in legacy _t support) - */ -class SSTemplateParser extends Parser implements TemplateParser -{ - - /** - * @var bool - Set true by SSTemplateParser::compileString if the template should include comments intended - * for debugging (template source, included files, etc) - */ - protected $includeDebuggingComments = false; - - /** - * Stores the user-supplied closed block extension rules in the form: - * [ - * 'name' => function (&$res) {} - * ] - * See SSTemplateParser::ClosedBlock_Handle_Loop for an example of what the callable should look like - * @var array - */ - protected $closedBlocks = []; - - /** - * Stores the user-supplied open block extension rules in the form: - * [ - * 'name' => function (&$res) {} - * ] - * See SSTemplateParser::OpenBlock_Handle_Base_tag for an example of what the callable should look like - * @var array - */ - protected $openBlocks = []; - - /** - * Allow the injection of new closed & open block callables - * @param array $closedBlocks - * @param array $openBlocks - */ - public function __construct($closedBlocks = [], $openBlocks = []) - { - parent::__construct(null); - $this->setClosedBlocks($closedBlocks); - $this->setOpenBlocks($openBlocks); - } - - /** - * Override the function that constructs the result arrays to also prepare a 'php' item in the array - */ - function construct($matchrule, $name, $arguments = null) - { - $res = parent::construct($matchrule, $name, $arguments); - if (!isset($res['php'])) { - $res['php'] = ''; - } - return $res; - } - - /** - * Set the closed blocks that the template parser should use - * - * This method will delete any existing closed blocks, please use addClosedBlock if you don't - * want to overwrite - * @param array $closedBlocks - * @throws InvalidArgumentException - */ - public function setClosedBlocks($closedBlocks) - { - $this->closedBlocks = []; - foreach ((array) $closedBlocks as $name => $callable) { - $this->addClosedBlock($name, $callable); - } - } - - /** - * Set the open blocks that the template parser should use - * - * This method will delete any existing open blocks, please use addOpenBlock if you don't - * want to overwrite - * @param array $openBlocks - * @throws InvalidArgumentException - */ - public function setOpenBlocks($openBlocks) - { - $this->openBlocks = []; - foreach ((array) $openBlocks as $name => $callable) { - $this->addOpenBlock($name, $callable); - } - } - - /** - * Add a closed block callable to allow <% name %><% end_name %> syntax - * @param string $name The name of the token to be used in the syntax <% name %><% end_name %> - * @param callable $callable The function that modifies the generation of template code - * @throws InvalidArgumentException - */ - public function addClosedBlock($name, $callable) - { - $this->validateExtensionBlock($name, $callable, 'Closed block'); - $this->closedBlocks[$name] = $callable; - } - - /** - * Add a closed block callable to allow <% name %> syntax - * @param string $name The name of the token to be used in the syntax <% name %> - * @param callable $callable The function that modifies the generation of template code - * @throws InvalidArgumentException - */ - public function addOpenBlock($name, $callable) - { - $this->validateExtensionBlock($name, $callable, 'Open block'); - $this->openBlocks[$name] = $callable; - } - - /** - * Ensures that the arguments to addOpenBlock and addClosedBlock are valid - * @param $name - * @param $callable - * @param $type - * @throws InvalidArgumentException - */ - protected function validateExtensionBlock($name, $callable, $type) - { - if (!is_string($name)) { - throw new InvalidArgumentException( - sprintf( - "Name argument for %s must be a string", - $type - ) - ); - } elseif (!is_callable($callable)) { - throw new InvalidArgumentException( - sprintf( - "Callable %s argument named '%s' is not callable", - $type, - $name - ) - ); - } - } - - /* Template: (Comment | Translate | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | - OpenBlock | MalformedBlock | MalformedBracketInjection | Injection | Text)+ */ - protected $match_Template_typestack = array('Template'); - function match_Template ($stack = array()) { - $matchrule = "Template"; $result = $this->construct($matchrule, $matchrule, null); - $count = 0; - while (true) { - $res_54 = $result; - $pos_54 = $this->pos; - $_53 = NULL; - do { - $_51 = NULL; - do { - $res_0 = $result; - $pos_0 = $this->pos; - $matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_51 = TRUE; break; - } - $result = $res_0; - $this->pos = $pos_0; - $_49 = NULL; - do { - $res_2 = $result; - $pos_2 = $this->pos; - $matcher = 'match_'.'Translate'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_49 = TRUE; break; - } - $result = $res_2; - $this->pos = $pos_2; - $_47 = NULL; - do { - $res_4 = $result; - $pos_4 = $this->pos; - $matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_47 = TRUE; break; - } - $result = $res_4; - $this->pos = $pos_4; - $_45 = NULL; - do { - $res_6 = $result; - $pos_6 = $this->pos; - $matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_45 = TRUE; break; - } - $result = $res_6; - $this->pos = $pos_6; - $_43 = NULL; - do { - $res_8 = $result; - $pos_8 = $this->pos; - $matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_43 = TRUE; break; - } - $result = $res_8; - $this->pos = $pos_8; - $_41 = NULL; - do { - $res_10 = $result; - $pos_10 = $this->pos; - $matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_41 = TRUE; break; - } - $result = $res_10; - $this->pos = $pos_10; - $_39 = NULL; - do { - $res_12 = $result; - $pos_12 = $this->pos; - $matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_39 = TRUE; break; - } - $result = $res_12; - $this->pos = $pos_12; - $_37 = NULL; - do { - $res_14 = $result; - $pos_14 = $this->pos; - $matcher = 'match_'.'Include'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_37 = TRUE; break; - } - $result = $res_14; - $this->pos = $pos_14; - $_35 = NULL; - do { - $res_16 = $result; - $pos_16 = $this->pos; - $matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_35 = TRUE; break; - } - $result = $res_16; - $this->pos = $pos_16; - $_33 = NULL; - do { - $res_18 = $result; - $pos_18 = $this->pos; - $matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_33 = TRUE; break; - } - $result = $res_18; - $this->pos = $pos_18; - $_31 = NULL; - do { - $res_20 = $result; - $pos_20 = $this->pos; - $matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_31 = TRUE; break; - } - $result = $res_20; - $this->pos = $pos_20; - $_29 = NULL; - do { - $res_22 = $result; - $pos_22 = $this->pos; - $matcher = 'match_'.'MalformedBracketInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_29 = TRUE; break; - } - $result = $res_22; - $this->pos = $pos_22; - $_27 = NULL; - do { - $res_24 = $result; - $pos_24 = $this->pos; - $matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_27 = TRUE; break; - } - $result = $res_24; - $this->pos = $pos_24; - $matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_27 = TRUE; break; - } - $result = $res_24; - $this->pos = $pos_24; - $_27 = FALSE; break; - } - while(0); - if( $_27 === TRUE ) { $_29 = TRUE; break; } - $result = $res_22; - $this->pos = $pos_22; - $_29 = FALSE; break; - } - while(0); - if( $_29 === TRUE ) { $_31 = TRUE; break; } - $result = $res_20; - $this->pos = $pos_20; - $_31 = FALSE; break; - } - while(0); - if( $_31 === TRUE ) { $_33 = TRUE; break; } - $result = $res_18; - $this->pos = $pos_18; - $_33 = FALSE; break; - } - while(0); - if( $_33 === TRUE ) { $_35 = TRUE; break; } - $result = $res_16; - $this->pos = $pos_16; - $_35 = FALSE; break; - } - while(0); - if( $_35 === TRUE ) { $_37 = TRUE; break; } - $result = $res_14; - $this->pos = $pos_14; - $_37 = FALSE; break; - } - while(0); - if( $_37 === TRUE ) { $_39 = TRUE; break; } - $result = $res_12; - $this->pos = $pos_12; - $_39 = FALSE; break; - } - while(0); - if( $_39 === TRUE ) { $_41 = TRUE; break; } - $result = $res_10; - $this->pos = $pos_10; - $_41 = FALSE; break; - } - while(0); - if( $_41 === TRUE ) { $_43 = TRUE; break; } - $result = $res_8; - $this->pos = $pos_8; - $_43 = FALSE; break; - } - while(0); - if( $_43 === TRUE ) { $_45 = TRUE; break; } - $result = $res_6; - $this->pos = $pos_6; - $_45 = FALSE; break; - } - while(0); - if( $_45 === TRUE ) { $_47 = TRUE; break; } - $result = $res_4; - $this->pos = $pos_4; - $_47 = FALSE; break; - } - while(0); - if( $_47 === TRUE ) { $_49 = TRUE; break; } - $result = $res_2; - $this->pos = $pos_2; - $_49 = FALSE; break; - } - while(0); - if( $_49 === TRUE ) { $_51 = TRUE; break; } - $result = $res_0; - $this->pos = $pos_0; - $_51 = FALSE; break; - } - while(0); - if( $_51 === FALSE) { $_53 = FALSE; break; } - $_53 = TRUE; break; - } - while(0); - if( $_53 === FALSE) { - $result = $res_54; - $this->pos = $pos_54; - unset( $res_54 ); - unset( $pos_54 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - - function Template_STR(&$res, $sub) - { - $res['php'] .= $sub['php'] . PHP_EOL ; - } - - /* Word: / [A-Za-z_] [A-Za-z0-9_]* / */ - protected $match_Word_typestack = array('Word'); - function match_Word ($stack = array()) { - $matchrule = "Word"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [A-Za-z_] [A-Za-z0-9_]* /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* NamespacedWord: / [A-Za-z_\/\\] [A-Za-z0-9_\/\\]* / */ - protected $match_NamespacedWord_typestack = array('NamespacedWord'); - function match_NamespacedWord ($stack = array()) { - $matchrule = "NamespacedWord"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [A-Za-z_\/\\\\] [A-Za-z0-9_\/\\\\]* /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Number: / [0-9]+ / */ - protected $match_Number_typestack = array('Number'); - function match_Number ($stack = array()) { - $matchrule = "Number"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [0-9]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Value: / [A-Za-z0-9_]+ / */ - protected $match_Value_typestack = array('Value'); - function match_Value ($stack = array()) { - $matchrule = "Value"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [A-Za-z0-9_]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* CallArguments: :Argument ( < "," < :Argument )* */ - protected $match_CallArguments_typestack = array('CallArguments'); - function match_CallArguments ($stack = array()) { - $matchrule = "CallArguments"; $result = $this->construct($matchrule, $matchrule, null); - $_66 = NULL; - do { - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Argument" ); - } - else { $_66 = FALSE; break; } - while (true) { - $res_65 = $result; - $pos_65 = $this->pos; - $_64 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_64 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Argument" ); - } - else { $_64 = FALSE; break; } - $_64 = TRUE; break; - } - while(0); - if( $_64 === FALSE) { - $result = $res_65; - $this->pos = $pos_65; - unset( $res_65 ); - unset( $pos_65 ); - break; - } - } - $_66 = TRUE; break; - } - while(0); - if( $_66 === TRUE ) { return $this->finalise($result); } - if( $_66 === FALSE) { return FALSE; } - } - - - - - /** - * Values are bare words in templates, but strings in PHP. We rely on PHP's type conversion to back-convert - * strings to numbers when needed. - */ - function CallArguments_Argument(&$res, $sub) - { - if ($res['php'] !== '') { - $res['php'] .= ', '; - } - - $res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : - str_replace('$$FINAL', 'getValueAsArgument', $sub['php'] ?? ''); - } - - /* Call: Method:Word ( "(" < :CallArguments? > ")" )? */ - protected $match_Call_typestack = array('Call'); - function match_Call ($stack = array()) { - $matchrule = "Call"; $result = $this->construct($matchrule, $matchrule, null); - $_76 = NULL; - do { - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Method" ); - } - else { $_76 = FALSE; break; } - $res_75 = $result; - $pos_75 = $this->pos; - $_74 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '(') { - $this->pos += 1; - $result["text"] .= '('; - } - else { $_74 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_71 = $result; - $pos_71 = $this->pos; - $matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "CallArguments" ); - } - else { - $result = $res_71; - $this->pos = $pos_71; - unset( $res_71 ); - unset( $pos_71 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ')') { - $this->pos += 1; - $result["text"] .= ')'; - } - else { $_74 = FALSE; break; } - $_74 = TRUE; break; - } - while(0); - if( $_74 === FALSE) { - $result = $res_75; - $this->pos = $pos_75; - unset( $res_75 ); - unset( $pos_75 ); - } - $_76 = TRUE; break; - } - while(0); - if( $_76 === TRUE ) { return $this->finalise($result); } - if( $_76 === FALSE) { return FALSE; } - } - - - /* LookupStep: :Call &"." */ - protected $match_LookupStep_typestack = array('LookupStep'); - function match_LookupStep ($stack = array()) { - $matchrule = "LookupStep"; $result = $this->construct($matchrule, $matchrule, null); - $_80 = NULL; - do { - $matcher = 'match_'.'Call'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Call" ); - } - else { $_80 = FALSE; break; } - $res_79 = $result; - $pos_79 = $this->pos; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '.') { - $this->pos += 1; - $result["text"] .= '.'; - $result = $res_79; - $this->pos = $pos_79; - } - else { - $result = $res_79; - $this->pos = $pos_79; - $_80 = FALSE; break; - } - $_80 = TRUE; break; - } - while(0); - if( $_80 === TRUE ) { return $this->finalise($result); } - if( $_80 === FALSE) { return FALSE; } - } - - - /* LastLookupStep: :Call */ - protected $match_LastLookupStep_typestack = array('LastLookupStep'); - function match_LastLookupStep ($stack = array()) { - $matchrule = "LastLookupStep"; $result = $this->construct($matchrule, $matchrule, null); - $matcher = 'match_'.'Call'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Call" ); - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Lookup: LookupStep ("." LookupStep)* "." LastLookupStep | LastLookupStep */ - protected $match_Lookup_typestack = array('Lookup'); - function match_Lookup ($stack = array()) { - $matchrule = "Lookup"; $result = $this->construct($matchrule, $matchrule, null); - $_94 = NULL; - do { - $res_83 = $result; - $pos_83 = $this->pos; - $_91 = NULL; - do { - $matcher = 'match_'.'LookupStep'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_91 = FALSE; break; } - while (true) { - $res_88 = $result; - $pos_88 = $this->pos; - $_87 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '.') { - $this->pos += 1; - $result["text"] .= '.'; - } - else { $_87 = FALSE; break; } - $matcher = 'match_'.'LookupStep'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_87 = FALSE; break; } - $_87 = TRUE; break; - } - while(0); - if( $_87 === FALSE) { - $result = $res_88; - $this->pos = $pos_88; - unset( $res_88 ); - unset( $pos_88 ); - break; - } - } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '.') { - $this->pos += 1; - $result["text"] .= '.'; - } - else { $_91 = FALSE; break; } - $matcher = 'match_'.'LastLookupStep'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_91 = FALSE; break; } - $_91 = TRUE; break; - } - while(0); - if( $_91 === TRUE ) { $_94 = TRUE; break; } - $result = $res_83; - $this->pos = $pos_83; - $matcher = 'match_'.'LastLookupStep'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_94 = TRUE; break; - } - $result = $res_83; - $this->pos = $pos_83; - $_94 = FALSE; break; - } - while(0); - if( $_94 === TRUE ) { return $this->finalise($result); } - if( $_94 === FALSE) { return FALSE; } - } - - - - - function Lookup__construct(&$res) - { - $res['php'] = '$scope->locally()'; - $res['LookupSteps'] = []; - } - - /** - * The basic generated PHP of LookupStep and LastLookupStep is the same, except that LookupStep calls 'scopeToIntermediateValue' to - * get the next ModelData in the sequence, and LastLookupStep calls different methods (getOutputValue, hasValue, scopeToIntermediateValue) - * depending on the context the lookup is used in. - */ - function Lookup_AddLookupStep(&$res, $sub, $method) - { - $res['LookupSteps'][] = $sub; - - $property = $sub['Call']['Method']['text']; - - $arguments = ''; - if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) { - $arguments = $sub['Call']['CallArguments']['php']; - } - $res['php'] .= "->$method('$property', [$arguments])"; - } - - function Lookup_LookupStep(&$res, $sub) - { - $this->Lookup_AddLookupStep($res, $sub, 'scopeToIntermediateValue'); - } - - function Lookup_LastLookupStep(&$res, $sub) - { - $this->Lookup_AddLookupStep($res, $sub, '$$FINAL'); - } - - - /* Translate: "<%t" < Entity < (Default:QuotedString)? < (!("is" "=") < "is" < Context:QuotedString)? < - (InjectionVariables)? > "%>" */ - protected $match_Translate_typestack = array('Translate'); - function match_Translate ($stack = array()) { - $matchrule = "Translate"; $result = $this->construct($matchrule, $matchrule, null); - $_120 = NULL; - do { - if (( $subres = $this->literal( '<%t' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_120 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'Entity'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_120 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_102 = $result; - $pos_102 = $this->pos; - $_101 = NULL; - do { - $matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Default" ); - } - else { $_101 = FALSE; break; } - $_101 = TRUE; break; - } - while(0); - if( $_101 === FALSE) { - $result = $res_102; - $this->pos = $pos_102; - unset( $res_102 ); - unset( $pos_102 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_113 = $result; - $pos_113 = $this->pos; - $_112 = NULL; - do { - $res_107 = $result; - $pos_107 = $this->pos; - $_106 = NULL; - do { - if (( $subres = $this->literal( 'is' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_106 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '=') { - $this->pos += 1; - $result["text"] .= '='; - } - else { $_106 = FALSE; break; } - $_106 = TRUE; break; - } - while(0); - if( $_106 === TRUE ) { - $result = $res_107; - $this->pos = $pos_107; - $_112 = FALSE; break; - } - if( $_106 === FALSE) { - $result = $res_107; - $this->pos = $pos_107; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'is' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_112 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Context" ); - } - else { $_112 = FALSE; break; } - $_112 = TRUE; break; - } - while(0); - if( $_112 === FALSE) { - $result = $res_113; - $this->pos = $pos_113; - unset( $res_113 ); - unset( $pos_113 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_117 = $result; - $pos_117 = $this->pos; - $_116 = NULL; - do { - $matcher = 'match_'.'InjectionVariables'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_116 = FALSE; break; } - $_116 = TRUE; break; - } - while(0); - if( $_116 === FALSE) { - $result = $res_117; - $this->pos = $pos_117; - unset( $res_117 ); - unset( $pos_117 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_120 = FALSE; break; } - $_120 = TRUE; break; - } - while(0); - if( $_120 === TRUE ) { return $this->finalise($result); } - if( $_120 === FALSE) { return FALSE; } - } - - - /* InjectionVariables: (< InjectionName:Word "=" Argument)+ */ - protected $match_InjectionVariables_typestack = array('InjectionVariables'); - function match_InjectionVariables ($stack = array()) { - $matchrule = "InjectionVariables"; $result = $this->construct($matchrule, $matchrule, null); - $count = 0; - while (true) { - $res_127 = $result; - $pos_127 = $this->pos; - $_126 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "InjectionName" ); - } - else { $_126 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '=') { - $this->pos += 1; - $result["text"] .= '='; - } - else { $_126 = FALSE; break; } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_126 = FALSE; break; } - $_126 = TRUE; break; - } - while(0); - if( $_126 === FALSE) { - $result = $res_127; - $this->pos = $pos_127; - unset( $res_127 ); - unset( $pos_127 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - /* Entity: / [A-Za-z_\\] [\w\.\\]* / */ - protected $match_Entity_typestack = array('Entity'); - function match_Entity ($stack = array()) { - $matchrule = "Entity"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [A-Za-z_\\\\] [\w\.\\\\]* /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - - - function Translate__construct(&$res) - { - $res['php'] = '$val .= _t('; - } - - function Translate_Entity(&$res, $sub) - { - $res['php'] .= "'$sub[text]'"; - } - - function Translate_Default(&$res, $sub) - { - $res['php'] .= ",$sub[text]"; - } - - function Translate_Context(&$res, $sub) - { - $res['php'] .= ",$sub[text]"; - } - - function Translate_InjectionVariables(&$res, $sub) - { - $res['php'] .= ",$sub[php]"; - } - - function Translate__finalise(&$res) - { - $res['php'] .= ');'; - } - - function InjectionVariables__construct(&$res) - { - $res['php'] = "["; - } - - function InjectionVariables_InjectionName(&$res, $sub) - { - $res['php'] .= "'$sub[text]'=>"; - } - - function InjectionVariables_Argument(&$res, $sub) - { - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? '') . ','; - } - - function InjectionVariables__finalise(&$res) - { - if (substr($res['php'] ?? '', -1) == ',') { - $res['php'] = substr($res['php'] ?? '', 0, -1); //remove last comma in the array - } - $res['php'] .= ']'; - } - - /* MalformedBracketInjection: "{$" :Lookup !( "}" ) */ - protected $match_MalformedBracketInjection_typestack = array('MalformedBracketInjection'); - function match_MalformedBracketInjection ($stack = array()) { - $matchrule = "MalformedBracketInjection"; $result = $this->construct($matchrule, $matchrule, null); - $_134 = NULL; - do { - if (( $subres = $this->literal( '{$' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_134 = FALSE; break; } - $matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Lookup" ); - } - else { $_134 = FALSE; break; } - $res_133 = $result; - $pos_133 = $this->pos; - $_132 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '}') { - $this->pos += 1; - $result["text"] .= '}'; - } - else { $_132 = FALSE; break; } - $_132 = TRUE; break; - } - while(0); - if( $_132 === TRUE ) { - $result = $res_133; - $this->pos = $pos_133; - $_134 = FALSE; break; - } - if( $_132 === FALSE) { - $result = $res_133; - $this->pos = $pos_133; - } - $_134 = TRUE; break; - } - while(0); - if( $_134 === TRUE ) { return $this->finalise($result); } - if( $_134 === FALSE) { return FALSE; } - } - - - - function MalformedBracketInjection__finalise(&$res) - { - $lookup = $res['text']; - throw new SSTemplateParseException("Malformed bracket injection $lookup. Perhaps you have forgotten the " . - "closing bracket (})?", $this); - } - - /* SimpleInjection: '$' :Lookup */ - protected $match_SimpleInjection_typestack = array('SimpleInjection'); - function match_SimpleInjection ($stack = array()) { - $matchrule = "SimpleInjection"; $result = $this->construct($matchrule, $matchrule, null); - $_138 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '$') { - $this->pos += 1; - $result["text"] .= '$'; - } - else { $_138 = FALSE; break; } - $matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Lookup" ); - } - else { $_138 = FALSE; break; } - $_138 = TRUE; break; - } - while(0); - if( $_138 === TRUE ) { return $this->finalise($result); } - if( $_138 === FALSE) { return FALSE; } - } - - - /* BracketInjection: '{$' :Lookup "}" */ - protected $match_BracketInjection_typestack = array('BracketInjection'); - function match_BracketInjection ($stack = array()) { - $matchrule = "BracketInjection"; $result = $this->construct($matchrule, $matchrule, null); - $_143 = NULL; - do { - if (( $subres = $this->literal( '{$' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_143 = FALSE; break; } - $matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Lookup" ); - } - else { $_143 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '}') { - $this->pos += 1; - $result["text"] .= '}'; - } - else { $_143 = FALSE; break; } - $_143 = TRUE; break; - } - while(0); - if( $_143 === TRUE ) { return $this->finalise($result); } - if( $_143 === FALSE) { return FALSE; } - } - - - /* Injection: BracketInjection | SimpleInjection */ - protected $match_Injection_typestack = array('Injection'); - function match_Injection ($stack = array()) { - $matchrule = "Injection"; $result = $this->construct($matchrule, $matchrule, null); - $_148 = NULL; - do { - $res_145 = $result; - $pos_145 = $this->pos; - $matcher = 'match_'.'BracketInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_148 = TRUE; break; - } - $result = $res_145; - $this->pos = $pos_145; - $matcher = 'match_'.'SimpleInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_148 = TRUE; break; - } - $result = $res_145; - $this->pos = $pos_145; - $_148 = FALSE; break; - } - while(0); - if( $_148 === TRUE ) { return $this->finalise($result); } - if( $_148 === FALSE) { return FALSE; } - } - - - - function Injection_STR(&$res, $sub) - { - $res['php'] = '$val .= '. str_replace('$$FINAL', 'getOutputValue', $sub['Lookup']['php'] ?? '') . ';'; - } - - /* DollarMarkedLookup: SimpleInjection */ - protected $match_DollarMarkedLookup_typestack = array('DollarMarkedLookup'); - function match_DollarMarkedLookup ($stack = array()) { - $matchrule = "DollarMarkedLookup"; $result = $this->construct($matchrule, $matchrule, null); - $matcher = 'match_'.'SimpleInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - return $this->finalise($result); - } - else { return FALSE; } - } - - - - function DollarMarkedLookup_STR(&$res, $sub) - { - $res['Lookup'] = $sub['Lookup']; - } - - /* QuotedString: q:/['"]/ String:/ (\\\\ | \\. | [^$q\\])* / '$q' */ - protected $match_QuotedString_typestack = array('QuotedString'); - function match_QuotedString ($stack = array()) { - $matchrule = "QuotedString"; $result = $this->construct($matchrule, $matchrule, null); - $_154 = NULL; - do { - $stack[] = $result; $result = $this->construct( $matchrule, "q" ); - if (( $subres = $this->rx( '/[\'"]/' ) ) !== FALSE) { - $result["text"] .= $subres; - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'q' ); - } - else { - $result = array_pop($stack); - $_154 = FALSE; break; - } - $stack[] = $result; $result = $this->construct( $matchrule, "String" ); - if (( $subres = $this->rx( '/ (\\\\\\\\ | \\\\. | [^'.$this->expression($result, $stack, 'q').'\\\\])* /' ) ) !== FALSE) { - $result["text"] .= $subres; - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'String' ); - } - else { - $result = array_pop($stack); - $_154 = FALSE; break; - } - if (( $subres = $this->literal( ''.$this->expression($result, $stack, 'q').'' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_154 = FALSE; break; } - $_154 = TRUE; break; - } - while(0); - if( $_154 === TRUE ) { return $this->finalise($result); } - if( $_154 === FALSE) { return FALSE; } - } - - - /* Null: / (null)\b /i */ - protected $match_Null_typestack = array('Null'); - function match_Null ($stack = array()) { - $matchrule = "Null"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ (null)\b /i' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Boolean: / (true|false)\b /i */ - protected $match_Boolean_typestack = array('Boolean'); - function match_Boolean ($stack = array()) { - $matchrule = "Boolean"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ (true|false)\b /i' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Sign: / [+-] / */ - protected $match_Sign_typestack = array('Sign'); - function match_Sign ($stack = array()) { - $matchrule = "Sign"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [+-] /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Float: / [0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? / */ - protected $match_Float_typestack = array('Float'); - function match_Float ($stack = array()) { - $matchrule = "Float"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Hexadecimal: / 0[xX][0-9a-fA-F]+ / */ - protected $match_Hexadecimal_typestack = array('Hexadecimal'); - function match_Hexadecimal ($stack = array()) { - $matchrule = "Hexadecimal"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ 0[xX][0-9a-fA-F]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Octal: / 0[0-7]+ / */ - protected $match_Octal_typestack = array('Octal'); - function match_Octal ($stack = array()) { - $matchrule = "Octal"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ 0[0-7]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Binary: / 0[bB][01]+ / */ - protected $match_Binary_typestack = array('Binary'); - function match_Binary ($stack = array()) { - $matchrule = "Binary"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ 0[bB][01]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Decimal: / 0 | [1-9][0-9]* / */ - protected $match_Decimal_typestack = array('Decimal'); - function match_Decimal ($stack = array()) { - $matchrule = "Decimal"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ 0 | [1-9][0-9]* /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* IntegerOrFloat: ( Sign )? ( Hexadecimal | Binary | Float | Octal | Decimal ) */ - protected $match_IntegerOrFloat_typestack = array('IntegerOrFloat'); - function match_IntegerOrFloat ($stack = array()) { - $matchrule = "IntegerOrFloat"; $result = $this->construct($matchrule, $matchrule, null); - $_186 = NULL; - do { - $res_166 = $result; - $pos_166 = $this->pos; - $_165 = NULL; - do { - $matcher = 'match_'.'Sign'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_165 = FALSE; break; } - $_165 = TRUE; break; - } - while(0); - if( $_165 === FALSE) { - $result = $res_166; - $this->pos = $pos_166; - unset( $res_166 ); - unset( $pos_166 ); - } - $_184 = NULL; - do { - $_182 = NULL; - do { - $res_167 = $result; - $pos_167 = $this->pos; - $matcher = 'match_'.'Hexadecimal'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_182 = TRUE; break; - } - $result = $res_167; - $this->pos = $pos_167; - $_180 = NULL; - do { - $res_169 = $result; - $pos_169 = $this->pos; - $matcher = 'match_'.'Binary'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_180 = TRUE; break; - } - $result = $res_169; - $this->pos = $pos_169; - $_178 = NULL; - do { - $res_171 = $result; - $pos_171 = $this->pos; - $matcher = 'match_'.'Float'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_178 = TRUE; break; - } - $result = $res_171; - $this->pos = $pos_171; - $_176 = NULL; - do { - $res_173 = $result; - $pos_173 = $this->pos; - $matcher = 'match_'.'Octal'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_176 = TRUE; break; - } - $result = $res_173; - $this->pos = $pos_173; - $matcher = 'match_'.'Decimal'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_176 = TRUE; break; - } - $result = $res_173; - $this->pos = $pos_173; - $_176 = FALSE; break; - } - while(0); - if( $_176 === TRUE ) { $_178 = TRUE; break; } - $result = $res_171; - $this->pos = $pos_171; - $_178 = FALSE; break; - } - while(0); - if( $_178 === TRUE ) { $_180 = TRUE; break; } - $result = $res_169; - $this->pos = $pos_169; - $_180 = FALSE; break; - } - while(0); - if( $_180 === TRUE ) { $_182 = TRUE; break; } - $result = $res_167; - $this->pos = $pos_167; - $_182 = FALSE; break; - } - while(0); - if( $_182 === FALSE) { $_184 = FALSE; break; } - $_184 = TRUE; break; - } - while(0); - if( $_184 === FALSE) { $_186 = FALSE; break; } - $_186 = TRUE; break; - } - while(0); - if( $_186 === TRUE ) { return $this->finalise($result); } - if( $_186 === FALSE) { return FALSE; } - } - - - /* FreeString: /[^,)%!=><|&]+/ */ - protected $match_FreeString_typestack = array('FreeString'); - function match_FreeString ($stack = array()) { - $matchrule = "FreeString"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/[^,)%!=><|&]+/' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Argument: - :DollarMarkedLookup | - :QuotedString | - :Null | - :Boolean | - :IntegerOrFloat | - :Lookup !(< FreeString)| - :FreeString */ - protected $match_Argument_typestack = array('Argument'); - function match_Argument ($stack = array()) { - $matchrule = "Argument"; $result = $this->construct($matchrule, $matchrule, null); - $_218 = NULL; - do { - $res_189 = $result; - $pos_189 = $this->pos; - $matcher = 'match_'.'DollarMarkedLookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "DollarMarkedLookup" ); - $_218 = TRUE; break; - } - $result = $res_189; - $this->pos = $pos_189; - $_216 = NULL; - do { - $res_191 = $result; - $pos_191 = $this->pos; - $matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "QuotedString" ); - $_216 = TRUE; break; - } - $result = $res_191; - $this->pos = $pos_191; - $_214 = NULL; - do { - $res_193 = $result; - $pos_193 = $this->pos; - $matcher = 'match_'.'Null'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Null" ); - $_214 = TRUE; break; - } - $result = $res_193; - $this->pos = $pos_193; - $_212 = NULL; - do { - $res_195 = $result; - $pos_195 = $this->pos; - $matcher = 'match_'.'Boolean'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Boolean" ); - $_212 = TRUE; break; - } - $result = $res_195; - $this->pos = $pos_195; - $_210 = NULL; - do { - $res_197 = $result; - $pos_197 = $this->pos; - $matcher = 'match_'.'IntegerOrFloat'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "IntegerOrFloat" ); - $_210 = TRUE; break; - } - $result = $res_197; - $this->pos = $pos_197; - $_208 = NULL; - do { - $res_199 = $result; - $pos_199 = $this->pos; - $_205 = NULL; - do { - $matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Lookup" ); - } - else { $_205 = FALSE; break; } - $res_204 = $result; - $pos_204 = $this->pos; - $_203 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { - $result["text"] .= $subres; - } - $matcher = 'match_'.'FreeString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_203 = FALSE; break; } - $_203 = TRUE; break; - } - while(0); - if( $_203 === TRUE ) { - $result = $res_204; - $this->pos = $pos_204; - $_205 = FALSE; break; - } - if( $_203 === FALSE) { - $result = $res_204; - $this->pos = $pos_204; - } - $_205 = TRUE; break; - } - while(0); - if( $_205 === TRUE ) { $_208 = TRUE; break; } - $result = $res_199; - $this->pos = $pos_199; - $matcher = 'match_'.'FreeString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "FreeString" ); - $_208 = TRUE; break; - } - $result = $res_199; - $this->pos = $pos_199; - $_208 = FALSE; break; - } - while(0); - if( $_208 === TRUE ) { $_210 = TRUE; break; } - $result = $res_197; - $this->pos = $pos_197; - $_210 = FALSE; break; - } - while(0); - if( $_210 === TRUE ) { $_212 = TRUE; break; } - $result = $res_195; - $this->pos = $pos_195; - $_212 = FALSE; break; - } - while(0); - if( $_212 === TRUE ) { $_214 = TRUE; break; } - $result = $res_193; - $this->pos = $pos_193; - $_214 = FALSE; break; - } - while(0); - if( $_214 === TRUE ) { $_216 = TRUE; break; } - $result = $res_191; - $this->pos = $pos_191; - $_216 = FALSE; break; - } - while(0); - if( $_216 === TRUE ) { $_218 = TRUE; break; } - $result = $res_189; - $this->pos = $pos_189; - $_218 = FALSE; break; - } - while(0); - if( $_218 === TRUE ) { return $this->finalise($result); } - if( $_218 === FALSE) { return FALSE; } - } - - - - - /** - * If we get a bare value, we don't know enough to determine exactly what php would be the translation, because - * we don't know if the position of use indicates a lookup or a string argument. - * - * Instead, we record 'ArgumentMode' as a member of this matches results node, which can be: - * - lookup if this argument was unambiguously a lookup (marked as such) - * - string is this argument was unambiguously a string (marked as such, or impossible to parse as lookup) - * - default if this argument needs to be handled as per 2.4 - * - * In the case of 'default', there is no php member of the results node, but instead 'lookup_php', which - * should be used by the parent if the context indicates a lookup, and 'string_php' which should be used - * if the context indicates a string - */ - - function Argument_DollarMarkedLookup(&$res, $sub) - { - $res['ArgumentMode'] = 'lookup'; - $res['php'] = $sub['Lookup']['php']; - } - - function Argument_QuotedString(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text'] ?? '') . "'"; - } - - function Argument_Null(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_Boolean(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_IntegerOrFloat(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = $sub['text']; - } - - function Argument_Lookup(&$res, $sub) - { - if (count($sub['LookupSteps'] ?? []) == 1 && !isset($sub['LookupSteps'][0]['Call']['Arguments'])) { - $res['ArgumentMode'] = 'default'; - $res['lookup_php'] = $sub['php']; - $res['string_php'] = "'".$sub['LookupSteps'][0]['Call']['Method']['text']."'"; - } else { - $res['ArgumentMode'] = 'lookup'; - $res['php'] = $sub['php']; - } - } - - function Argument_FreeString(&$res, $sub) - { - $res['ArgumentMode'] = 'string'; - $res['php'] = "'" . str_replace("'", "\\'", trim($sub['text'] ?? '')) . "'"; - } - - /* ComparisonOperator: "!=" | "==" | ">=" | ">" | "<=" | "<" | "=" */ - protected $match_ComparisonOperator_typestack = array('ComparisonOperator'); - function match_ComparisonOperator ($stack = array()) { - $matchrule = "ComparisonOperator"; $result = $this->construct($matchrule, $matchrule, null); - $_243 = NULL; - do { - $res_220 = $result; - $pos_220 = $this->pos; - if (( $subres = $this->literal( '!=' ) ) !== FALSE) { - $result["text"] .= $subres; - $_243 = TRUE; break; - } - $result = $res_220; - $this->pos = $pos_220; - $_241 = NULL; - do { - $res_222 = $result; - $pos_222 = $this->pos; - if (( $subres = $this->literal( '==' ) ) !== FALSE) { - $result["text"] .= $subres; - $_241 = TRUE; break; - } - $result = $res_222; - $this->pos = $pos_222; - $_239 = NULL; - do { - $res_224 = $result; - $pos_224 = $this->pos; - if (( $subres = $this->literal( '>=' ) ) !== FALSE) { - $result["text"] .= $subres; - $_239 = TRUE; break; - } - $result = $res_224; - $this->pos = $pos_224; - $_237 = NULL; - do { - $res_226 = $result; - $pos_226 = $this->pos; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '>') { - $this->pos += 1; - $result["text"] .= '>'; - $_237 = TRUE; break; - } - $result = $res_226; - $this->pos = $pos_226; - $_235 = NULL; - do { - $res_228 = $result; - $pos_228 = $this->pos; - if (( $subres = $this->literal( '<=' ) ) !== FALSE) { - $result["text"] .= $subres; - $_235 = TRUE; break; - } - $result = $res_228; - $this->pos = $pos_228; - $_233 = NULL; - do { - $res_230 = $result; - $pos_230 = $this->pos; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '<') { - $this->pos += 1; - $result["text"] .= '<'; - $_233 = TRUE; break; - } - $result = $res_230; - $this->pos = $pos_230; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '=') { - $this->pos += 1; - $result["text"] .= '='; - $_233 = TRUE; break; - } - $result = $res_230; - $this->pos = $pos_230; - $_233 = FALSE; break; - } - while(0); - if( $_233 === TRUE ) { $_235 = TRUE; break; } - $result = $res_228; - $this->pos = $pos_228; - $_235 = FALSE; break; - } - while(0); - if( $_235 === TRUE ) { $_237 = TRUE; break; } - $result = $res_226; - $this->pos = $pos_226; - $_237 = FALSE; break; - } - while(0); - if( $_237 === TRUE ) { $_239 = TRUE; break; } - $result = $res_224; - $this->pos = $pos_224; - $_239 = FALSE; break; - } - while(0); - if( $_239 === TRUE ) { $_241 = TRUE; break; } - $result = $res_222; - $this->pos = $pos_222; - $_241 = FALSE; break; - } - while(0); - if( $_241 === TRUE ) { $_243 = TRUE; break; } - $result = $res_220; - $this->pos = $pos_220; - $_243 = FALSE; break; - } - while(0); - if( $_243 === TRUE ) { return $this->finalise($result); } - if( $_243 === FALSE) { return FALSE; } - } - - - /* Comparison: Argument < ComparisonOperator > Argument */ - protected $match_Comparison_typestack = array('Comparison'); - function match_Comparison ($stack = array()) { - $matchrule = "Comparison"; $result = $this->construct($matchrule, $matchrule, null); - $_250 = NULL; - do { - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_250 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'ComparisonOperator'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_250 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_250 = FALSE; break; } - $_250 = TRUE; break; - } - while(0); - if( $_250 === TRUE ) { return $this->finalise($result); } - if( $_250 === FALSE) { return FALSE; } - } - - - - function Comparison_Argument(&$res, $sub) - { - if ($sub['ArgumentMode'] == 'default') { - if (!empty($res['php'])) { - $res['php'] .= $sub['string_php']; - } else { - $res['php'] = str_replace('$$FINAL', 'getOutputValue', $sub['lookup_php'] ?? ''); - } - } else { - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? ''); - } - } - - function Comparison_ComparisonOperator(&$res, $sub) - { - $res['php'] .= ($sub['text'] == '=' ? '==' : $sub['text']); - } - - /* PresenceCheck: (Not:'not' <)? Argument */ - protected $match_PresenceCheck_typestack = array('PresenceCheck'); - function match_PresenceCheck ($stack = array()) { - $matchrule = "PresenceCheck"; $result = $this->construct($matchrule, $matchrule, null); - $_257 = NULL; - do { - $res_255 = $result; - $pos_255 = $this->pos; - $_254 = NULL; - do { - $stack[] = $result; $result = $this->construct( $matchrule, "Not" ); - if (( $subres = $this->literal( 'not' ) ) !== FALSE) { - $result["text"] .= $subres; - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Not' ); - } - else { - $result = array_pop($stack); - $_254 = FALSE; break; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $_254 = TRUE; break; - } - while(0); - if( $_254 === FALSE) { - $result = $res_255; - $this->pos = $pos_255; - unset( $res_255 ); - unset( $pos_255 ); - } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_257 = FALSE; break; } - $_257 = TRUE; break; - } - while(0); - if( $_257 === TRUE ) { return $this->finalise($result); } - if( $_257 === FALSE) { return FALSE; } - } - - - - function PresenceCheck_Not(&$res, $sub) - { - $res['php'] = '!'; - } - - function PresenceCheck_Argument(&$res, $sub) - { - if ($sub['ArgumentMode'] == 'string') { - $res['php'] .= '((bool)'.$sub['php'].')'; - } else { - $php = ($sub['ArgumentMode'] == 'default' ? $sub['lookup_php'] : $sub['php']); - $res['php'] .= str_replace('$$FINAL', 'hasValue', $php ?? ''); - } - } - - /* IfArgumentPortion: Comparison | PresenceCheck */ - protected $match_IfArgumentPortion_typestack = array('IfArgumentPortion'); - function match_IfArgumentPortion ($stack = array()) { - $matchrule = "IfArgumentPortion"; $result = $this->construct($matchrule, $matchrule, null); - $_262 = NULL; - do { - $res_259 = $result; - $pos_259 = $this->pos; - $matcher = 'match_'.'Comparison'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_262 = TRUE; break; - } - $result = $res_259; - $this->pos = $pos_259; - $matcher = 'match_'.'PresenceCheck'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_262 = TRUE; break; - } - $result = $res_259; - $this->pos = $pos_259; - $_262 = FALSE; break; - } - while(0); - if( $_262 === TRUE ) { return $this->finalise($result); } - if( $_262 === FALSE) { return FALSE; } - } - - - - function IfArgumentPortion_STR(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /* BooleanOperator: "||" | "&&" */ - protected $match_BooleanOperator_typestack = array('BooleanOperator'); - function match_BooleanOperator ($stack = array()) { - $matchrule = "BooleanOperator"; $result = $this->construct($matchrule, $matchrule, null); - $_267 = NULL; - do { - $res_264 = $result; - $pos_264 = $this->pos; - if (( $subres = $this->literal( '||' ) ) !== FALSE) { - $result["text"] .= $subres; - $_267 = TRUE; break; - } - $result = $res_264; - $this->pos = $pos_264; - if (( $subres = $this->literal( '&&' ) ) !== FALSE) { - $result["text"] .= $subres; - $_267 = TRUE; break; - } - $result = $res_264; - $this->pos = $pos_264; - $_267 = FALSE; break; - } - while(0); - if( $_267 === TRUE ) { return $this->finalise($result); } - if( $_267 === FALSE) { return FALSE; } - } - - - /* IfArgument: :IfArgumentPortion ( < :BooleanOperator < :IfArgumentPortion )* */ - protected $match_IfArgument_typestack = array('IfArgument'); - function match_IfArgument ($stack = array()) { - $matchrule = "IfArgument"; $result = $this->construct($matchrule, $matchrule, null); - $_276 = NULL; - do { - $matcher = 'match_'.'IfArgumentPortion'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "IfArgumentPortion" ); - } - else { $_276 = FALSE; break; } - while (true) { - $res_275 = $result; - $pos_275 = $this->pos; - $_274 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'BooleanOperator'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BooleanOperator" ); - } - else { $_274 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'IfArgumentPortion'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "IfArgumentPortion" ); - } - else { $_274 = FALSE; break; } - $_274 = TRUE; break; - } - while(0); - if( $_274 === FALSE) { - $result = $res_275; - $this->pos = $pos_275; - unset( $res_275 ); - unset( $pos_275 ); - break; - } - } - $_276 = TRUE; break; - } - while(0); - if( $_276 === TRUE ) { return $this->finalise($result); } - if( $_276 === FALSE) { return FALSE; } - } - - - - function IfArgument_IfArgumentPortion(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function IfArgument_BooleanOperator(&$res, $sub) - { - $res['php'] .= $sub['text']; - } - - /* IfPart: '<%' < 'if' [ :IfArgument > '%>' Template:$TemplateMatcher? */ - protected $match_IfPart_typestack = array('IfPart'); - function match_IfPart ($stack = array()) { - $matchrule = "IfPart"; $result = $this->construct($matchrule, $matchrule, null); - $_286 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_286 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'if' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_286 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_286 = FALSE; break; } - $matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "IfArgument" ); - } - else { $_286 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_286 = FALSE; break; } - $res_285 = $result; - $pos_285 = $this->pos; - $matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { - $result = $res_285; - $this->pos = $pos_285; - unset( $res_285 ); - unset( $pos_285 ); - } - $_286 = TRUE; break; - } - while(0); - if( $_286 === TRUE ) { return $this->finalise($result); } - if( $_286 === FALSE) { return FALSE; } - } - - - /* ElseIfPart: '<%' < 'else_if' [ :IfArgument > '%>' Template:$TemplateMatcher? */ - protected $match_ElseIfPart_typestack = array('ElseIfPart'); - function match_ElseIfPart ($stack = array()) { - $matchrule = "ElseIfPart"; $result = $this->construct($matchrule, $matchrule, null); - $_296 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_296 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'else_if' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_296 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_296 = FALSE; break; } - $matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "IfArgument" ); - } - else { $_296 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_296 = FALSE; break; } - $res_295 = $result; - $pos_295 = $this->pos; - $matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { - $result = $res_295; - $this->pos = $pos_295; - unset( $res_295 ); - unset( $pos_295 ); - } - $_296 = TRUE; break; - } - while(0); - if( $_296 === TRUE ) { return $this->finalise($result); } - if( $_296 === FALSE) { return FALSE; } - } - - - /* ElsePart: '<%' < 'else' > '%>' Template:$TemplateMatcher? */ - protected $match_ElsePart_typestack = array('ElsePart'); - function match_ElsePart ($stack = array()) { - $matchrule = "ElsePart"; $result = $this->construct($matchrule, $matchrule, null); - $_304 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_304 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'else' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_304 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_304 = FALSE; break; } - $res_303 = $result; - $pos_303 = $this->pos; - $matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { - $result = $res_303; - $this->pos = $pos_303; - unset( $res_303 ); - unset( $pos_303 ); - } - $_304 = TRUE; break; - } - while(0); - if( $_304 === TRUE ) { return $this->finalise($result); } - if( $_304 === FALSE) { return FALSE; } - } - - - /* If: IfPart ElseIfPart* ElsePart? '<%' < 'end_if' > '%>' */ - protected $match_If_typestack = array('If'); - function match_If ($stack = array()) { - $matchrule = "If"; $result = $this->construct($matchrule, $matchrule, null); - $_314 = NULL; - do { - $matcher = 'match_'.'IfPart'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_314 = FALSE; break; } - while (true) { - $res_307 = $result; - $pos_307 = $this->pos; - $matcher = 'match_'.'ElseIfPart'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { - $result = $res_307; - $this->pos = $pos_307; - unset( $res_307 ); - unset( $pos_307 ); - break; - } - } - $res_308 = $result; - $pos_308 = $this->pos; - $matcher = 'match_'.'ElsePart'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { - $result = $res_308; - $this->pos = $pos_308; - unset( $res_308 ); - unset( $pos_308 ); - } - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_314 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'end_if' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_314 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_314 = FALSE; break; } - $_314 = TRUE; break; - } - while(0); - if( $_314 === TRUE ) { return $this->finalise($result); } - if( $_314 === FALSE) { return FALSE; } - } - - - - function If_IfPart(&$res, $sub) - { - $res['php'] = - 'if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - function If_ElseIfPart(&$res, $sub) - { - $res['php'] .= - 'else if (' . $sub['IfArgument']['php'] . ') { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - function If_ElsePart(&$res, $sub) - { - $res['php'] .= - 'else { ' . PHP_EOL . - (isset($sub['Template']) ? $sub['Template']['php'] : '') . PHP_EOL . - '}'; - } - - /* Require: '<%' < 'require' [ Call:(Method:Word "(" < :CallArguments > ")") > '%>' */ - protected $match_Require_typestack = array('Require'); - function match_Require ($stack = array()) { - $matchrule = "Require"; $result = $this->construct($matchrule, $matchrule, null); - $_330 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_330 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'require' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_330 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_330 = FALSE; break; } - $stack[] = $result; $result = $this->construct( $matchrule, "Call" ); - $_326 = NULL; - do { - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Method" ); - } - else { $_326 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '(') { - $this->pos += 1; - $result["text"] .= '('; - } - else { $_326 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "CallArguments" ); - } - else { $_326 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ')') { - $this->pos += 1; - $result["text"] .= ')'; - } - else { $_326 = FALSE; break; } - $_326 = TRUE; break; - } - while(0); - if( $_326 === TRUE ) { - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Call' ); - } - if( $_326 === FALSE) { - $result = array_pop($stack); - $_330 = FALSE; break; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_330 = FALSE; break; } - $_330 = TRUE; break; - } - while(0); - if( $_330 === TRUE ) { return $this->finalise($result); } - if( $_330 === FALSE) { return FALSE; } - } - - - - function Require_Call(&$res, $sub) - { - $requirements = '\\SilverStripe\\View\\Requirements'; - $res['php'] = "{$requirements}::".$sub['Method']['text'].'('.$sub['CallArguments']['php'].');'; - } - - - /* CacheBlockArgument: - !( "if " | "unless " ) - ( - :DollarMarkedLookup | - :QuotedString | - :Lookup - ) */ - protected $match_CacheBlockArgument_typestack = array('CacheBlockArgument'); - function match_CacheBlockArgument ($stack = array()) { - $matchrule = "CacheBlockArgument"; $result = $this->construct($matchrule, $matchrule, null); - $_350 = NULL; - do { - $res_338 = $result; - $pos_338 = $this->pos; - $_337 = NULL; - do { - $_335 = NULL; - do { - $res_332 = $result; - $pos_332 = $this->pos; - if (( $subres = $this->literal( 'if ' ) ) !== FALSE) { - $result["text"] .= $subres; - $_335 = TRUE; break; - } - $result = $res_332; - $this->pos = $pos_332; - if (( $subres = $this->literal( 'unless ' ) ) !== FALSE) { - $result["text"] .= $subres; - $_335 = TRUE; break; - } - $result = $res_332; - $this->pos = $pos_332; - $_335 = FALSE; break; - } - while(0); - if( $_335 === FALSE) { $_337 = FALSE; break; } - $_337 = TRUE; break; - } - while(0); - if( $_337 === TRUE ) { - $result = $res_338; - $this->pos = $pos_338; - $_350 = FALSE; break; - } - if( $_337 === FALSE) { - $result = $res_338; - $this->pos = $pos_338; - } - $_348 = NULL; - do { - $_346 = NULL; - do { - $res_339 = $result; - $pos_339 = $this->pos; - $matcher = 'match_'.'DollarMarkedLookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "DollarMarkedLookup" ); - $_346 = TRUE; break; - } - $result = $res_339; - $this->pos = $pos_339; - $_344 = NULL; - do { - $res_341 = $result; - $pos_341 = $this->pos; - $matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "QuotedString" ); - $_344 = TRUE; break; - } - $result = $res_341; - $this->pos = $pos_341; - $matcher = 'match_'.'Lookup'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Lookup" ); - $_344 = TRUE; break; - } - $result = $res_341; - $this->pos = $pos_341; - $_344 = FALSE; break; - } - while(0); - if( $_344 === TRUE ) { $_346 = TRUE; break; } - $result = $res_339; - $this->pos = $pos_339; - $_346 = FALSE; break; - } - while(0); - if( $_346 === FALSE) { $_348 = FALSE; break; } - $_348 = TRUE; break; - } - while(0); - if( $_348 === FALSE) { $_350 = FALSE; break; } - $_350 = TRUE; break; - } - while(0); - if( $_350 === TRUE ) { return $this->finalise($result); } - if( $_350 === FALSE) { return FALSE; } - } - - - - function CacheBlockArgument_DollarMarkedLookup(&$res, $sub) - { - $res['php'] = $sub['Lookup']['php']; - } - - function CacheBlockArgument_QuotedString(&$res, $sub) - { - $res['php'] = "'" . str_replace("'", "\\'", $sub['String']['text'] ?? '') . "'"; - } - - function CacheBlockArgument_Lookup(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /* CacheBlockArguments: CacheBlockArgument ( < "," < CacheBlockArgument )* */ - protected $match_CacheBlockArguments_typestack = array('CacheBlockArguments'); - function match_CacheBlockArguments ($stack = array()) { - $matchrule = "CacheBlockArguments"; $result = $this->construct($matchrule, $matchrule, null); - $_359 = NULL; - do { - $matcher = 'match_'.'CacheBlockArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_359 = FALSE; break; } - while (true) { - $res_358 = $result; - $pos_358 = $this->pos; - $_357 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_357 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'CacheBlockArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_357 = FALSE; break; } - $_357 = TRUE; break; - } - while(0); - if( $_357 === FALSE) { - $result = $res_358; - $this->pos = $pos_358; - unset( $res_358 ); - unset( $pos_358 ); - break; - } - } - $_359 = TRUE; break; - } - while(0); - if( $_359 === TRUE ) { return $this->finalise($result); } - if( $_359 === FALSE) { return FALSE; } - } - - - - function CacheBlockArguments_CacheBlockArgument(&$res, $sub) - { - if (!empty($res['php'])) { - $res['php'] .= ".'_'."; - } else { - $res['php'] = ''; - } - - $res['php'] .= str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? ''); - } - - /* CacheBlockTemplate: (Comment | Translate | If | Require | OldI18NTag | Include | ClosedBlock | - OpenBlock | MalformedBlock | MalformedBracketInjection | Injection | Text)+ */ - protected $match_CacheBlockTemplate_typestack = array('CacheBlockTemplate','Template'); - function match_CacheBlockTemplate ($stack = array()) { - $matchrule = "CacheBlockTemplate"; $result = $this->construct($matchrule, $matchrule, array('TemplateMatcher' => 'CacheRestrictedTemplate')); - $count = 0; - while (true) { - $res_407 = $result; - $pos_407 = $this->pos; - $_406 = NULL; - do { - $_404 = NULL; - do { - $res_361 = $result; - $pos_361 = $this->pos; - $matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_404 = TRUE; break; - } - $result = $res_361; - $this->pos = $pos_361; - $_402 = NULL; - do { - $res_363 = $result; - $pos_363 = $this->pos; - $matcher = 'match_'.'Translate'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_402 = TRUE; break; - } - $result = $res_363; - $this->pos = $pos_363; - $_400 = NULL; - do { - $res_365 = $result; - $pos_365 = $this->pos; - $matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_400 = TRUE; break; - } - $result = $res_365; - $this->pos = $pos_365; - $_398 = NULL; - do { - $res_367 = $result; - $pos_367 = $this->pos; - $matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_398 = TRUE; break; - } - $result = $res_367; - $this->pos = $pos_367; - $_396 = NULL; - do { - $res_369 = $result; - $pos_369 = $this->pos; - $matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_396 = TRUE; break; - } - $result = $res_369; - $this->pos = $pos_369; - $_394 = NULL; - do { - $res_371 = $result; - $pos_371 = $this->pos; - $matcher = 'match_'.'Include'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_394 = TRUE; break; - } - $result = $res_371; - $this->pos = $pos_371; - $_392 = NULL; - do { - $res_373 = $result; - $pos_373 = $this->pos; - $matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_392 = TRUE; break; - } - $result = $res_373; - $this->pos = $pos_373; - $_390 = NULL; - do { - $res_375 = $result; - $pos_375 = $this->pos; - $matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_390 = TRUE; break; - } - $result = $res_375; - $this->pos = $pos_375; - $_388 = NULL; - do { - $res_377 = $result; - $pos_377 = $this->pos; - $matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_388 = TRUE; break; - } - $result = $res_377; - $this->pos = $pos_377; - $_386 = NULL; - do { - $res_379 = $result; - $pos_379 = $this->pos; - $matcher = 'match_'.'MalformedBracketInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_386 = TRUE; break; - } - $result = $res_379; - $this->pos = $pos_379; - $_384 = NULL; - do { - $res_381 = $result; - $pos_381 = $this->pos; - $matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_384 = TRUE; break; - } - $result = $res_381; - $this->pos = $pos_381; - $matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_384 = TRUE; break; - } - $result = $res_381; - $this->pos = $pos_381; - $_384 = FALSE; break; - } - while(0); - if( $_384 === TRUE ) { $_386 = TRUE; break; } - $result = $res_379; - $this->pos = $pos_379; - $_386 = FALSE; break; - } - while(0); - if( $_386 === TRUE ) { $_388 = TRUE; break; } - $result = $res_377; - $this->pos = $pos_377; - $_388 = FALSE; break; - } - while(0); - if( $_388 === TRUE ) { $_390 = TRUE; break; } - $result = $res_375; - $this->pos = $pos_375; - $_390 = FALSE; break; - } - while(0); - if( $_390 === TRUE ) { $_392 = TRUE; break; } - $result = $res_373; - $this->pos = $pos_373; - $_392 = FALSE; break; - } - while(0); - if( $_392 === TRUE ) { $_394 = TRUE; break; } - $result = $res_371; - $this->pos = $pos_371; - $_394 = FALSE; break; - } - while(0); - if( $_394 === TRUE ) { $_396 = TRUE; break; } - $result = $res_369; - $this->pos = $pos_369; - $_396 = FALSE; break; - } - while(0); - if( $_396 === TRUE ) { $_398 = TRUE; break; } - $result = $res_367; - $this->pos = $pos_367; - $_398 = FALSE; break; - } - while(0); - if( $_398 === TRUE ) { $_400 = TRUE; break; } - $result = $res_365; - $this->pos = $pos_365; - $_400 = FALSE; break; - } - while(0); - if( $_400 === TRUE ) { $_402 = TRUE; break; } - $result = $res_363; - $this->pos = $pos_363; - $_402 = FALSE; break; - } - while(0); - if( $_402 === TRUE ) { $_404 = TRUE; break; } - $result = $res_361; - $this->pos = $pos_361; - $_404 = FALSE; break; - } - while(0); - if( $_404 === FALSE) { $_406 = FALSE; break; } - $_406 = TRUE; break; - } - while(0); - if( $_406 === FALSE) { - $result = $res_407; - $this->pos = $pos_407; - unset( $res_407 ); - unset( $pos_407 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - - - /* UncachedBlock: - '<%' < "uncached" < CacheBlockArguments? ( < Conditional:("if"|"unless") > Condition:IfArgument )? > '%>' - Template:$TemplateMatcher? - '<%' < 'end_' ("uncached"|"cached"|"cacheblock") > '%>' */ - protected $match_UncachedBlock_typestack = array('UncachedBlock'); - function match_UncachedBlock ($stack = array()) { - $matchrule = "UncachedBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_444 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_412 = $result; - $pos_412 = $this->pos; - $matcher = 'match_'.'CacheBlockArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { - $result = $res_412; - $this->pos = $pos_412; - unset( $res_412 ); - unset( $pos_412 ); - } - $res_424 = $result; - $pos_424 = $this->pos; - $_423 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $stack[] = $result; $result = $this->construct( $matchrule, "Conditional" ); - $_419 = NULL; - do { - $_417 = NULL; - do { - $res_414 = $result; - $pos_414 = $this->pos; - if (( $subres = $this->literal( 'if' ) ) !== FALSE) { - $result["text"] .= $subres; - $_417 = TRUE; break; - } - $result = $res_414; - $this->pos = $pos_414; - if (( $subres = $this->literal( 'unless' ) ) !== FALSE) { - $result["text"] .= $subres; - $_417 = TRUE; break; - } - $result = $res_414; - $this->pos = $pos_414; - $_417 = FALSE; break; - } - while(0); - if( $_417 === FALSE) { $_419 = FALSE; break; } - $_419 = TRUE; break; - } - while(0); - if( $_419 === TRUE ) { - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Conditional' ); - } - if( $_419 === FALSE) { - $result = array_pop($stack); - $_423 = FALSE; break; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Condition" ); - } - else { $_423 = FALSE; break; } - $_423 = TRUE; break; - } - while(0); - if( $_423 === FALSE) { - $result = $res_424; - $this->pos = $pos_424; - unset( $res_424 ); - unset( $pos_424 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - $res_427 = $result; - $pos_427 = $this->pos; - $matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { - $result = $res_427; - $this->pos = $pos_427; - unset( $res_427 ); - unset( $pos_427 ); - } - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - $_440 = NULL; - do { - $_438 = NULL; - do { - $res_431 = $result; - $pos_431 = $this->pos; - if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_438 = TRUE; break; - } - $result = $res_431; - $this->pos = $pos_431; - $_436 = NULL; - do { - $res_433 = $result; - $pos_433 = $this->pos; - if (( $subres = $this->literal( 'cached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_436 = TRUE; break; - } - $result = $res_433; - $this->pos = $pos_433; - if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) { - $result["text"] .= $subres; - $_436 = TRUE; break; - } - $result = $res_433; - $this->pos = $pos_433; - $_436 = FALSE; break; - } - while(0); - if( $_436 === TRUE ) { $_438 = TRUE; break; } - $result = $res_431; - $this->pos = $pos_431; - $_438 = FALSE; break; - } - while(0); - if( $_438 === FALSE) { $_440 = FALSE; break; } - $_440 = TRUE; break; - } - while(0); - if( $_440 === FALSE) { $_444 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_444 = FALSE; break; } - $_444 = TRUE; break; - } - while(0); - if( $_444 === TRUE ) { return $this->finalise($result); } - if( $_444 === FALSE) { return FALSE; } - } - - - - function UncachedBlock_Template(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /* CacheRestrictedTemplate: (Comment | Translate | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | - OpenBlock | MalformedBlock | MalformedBracketInjection | Injection | Text)+ */ - protected $match_CacheRestrictedTemplate_typestack = array('CacheRestrictedTemplate','Template'); - function match_CacheRestrictedTemplate ($stack = array()) { - $matchrule = "CacheRestrictedTemplate"; $result = $this->construct($matchrule, $matchrule, null); - $count = 0; - while (true) { - $res_500 = $result; - $pos_500 = $this->pos; - $_499 = NULL; - do { - $_497 = NULL; - do { - $res_446 = $result; - $pos_446 = $this->pos; - $matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_497 = TRUE; break; - } - $result = $res_446; - $this->pos = $pos_446; - $_495 = NULL; - do { - $res_448 = $result; - $pos_448 = $this->pos; - $matcher = 'match_'.'Translate'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_495 = TRUE; break; - } - $result = $res_448; - $this->pos = $pos_448; - $_493 = NULL; - do { - $res_450 = $result; - $pos_450 = $this->pos; - $matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_493 = TRUE; break; - } - $result = $res_450; - $this->pos = $pos_450; - $_491 = NULL; - do { - $res_452 = $result; - $pos_452 = $this->pos; - $matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_491 = TRUE; break; - } - $result = $res_452; - $this->pos = $pos_452; - $_489 = NULL; - do { - $res_454 = $result; - $pos_454 = $this->pos; - $matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_489 = TRUE; break; - } - $result = $res_454; - $this->pos = $pos_454; - $_487 = NULL; - do { - $res_456 = $result; - $pos_456 = $this->pos; - $matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_487 = TRUE; break; - } - $result = $res_456; - $this->pos = $pos_456; - $_485 = NULL; - do { - $res_458 = $result; - $pos_458 = $this->pos; - $matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_485 = TRUE; break; - } - $result = $res_458; - $this->pos = $pos_458; - $_483 = NULL; - do { - $res_460 = $result; - $pos_460 = $this->pos; - $matcher = 'match_'.'Include'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_483 = TRUE; break; - } - $result = $res_460; - $this->pos = $pos_460; - $_481 = NULL; - do { - $res_462 = $result; - $pos_462 = $this->pos; - $matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_481 = TRUE; break; - } - $result = $res_462; - $this->pos = $pos_462; - $_479 = NULL; - do { - $res_464 = $result; - $pos_464 = $this->pos; - $matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_479 = TRUE; break; - } - $result = $res_464; - $this->pos = $pos_464; - $_477 = NULL; - do { - $res_466 = $result; - $pos_466 = $this->pos; - $matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_477 = TRUE; break; - } - $result = $res_466; - $this->pos = $pos_466; - $_475 = NULL; - do { - $res_468 = $result; - $pos_468 = $this->pos; - $matcher = 'match_'.'MalformedBracketInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_475 = TRUE; break; - } - $result = $res_468; - $this->pos = $pos_468; - $_473 = NULL; - do { - $res_470 = $result; - $pos_470 = $this->pos; - $matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_473 = TRUE; break; - } - $result = $res_470; - $this->pos = $pos_470; - $matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_473 = TRUE; break; - } - $result = $res_470; - $this->pos = $pos_470; - $_473 = FALSE; break; - } - while(0); - if( $_473 === TRUE ) { - $_475 = TRUE; break; - } - $result = $res_468; - $this->pos = $pos_468; - $_475 = FALSE; break; - } - while(0); - if( $_475 === TRUE ) { $_477 = TRUE; break; } - $result = $res_466; - $this->pos = $pos_466; - $_477 = FALSE; break; - } - while(0); - if( $_477 === TRUE ) { $_479 = TRUE; break; } - $result = $res_464; - $this->pos = $pos_464; - $_479 = FALSE; break; - } - while(0); - if( $_479 === TRUE ) { $_481 = TRUE; break; } - $result = $res_462; - $this->pos = $pos_462; - $_481 = FALSE; break; - } - while(0); - if( $_481 === TRUE ) { $_483 = TRUE; break; } - $result = $res_460; - $this->pos = $pos_460; - $_483 = FALSE; break; - } - while(0); - if( $_483 === TRUE ) { $_485 = TRUE; break; } - $result = $res_458; - $this->pos = $pos_458; - $_485 = FALSE; break; - } - while(0); - if( $_485 === TRUE ) { $_487 = TRUE; break; } - $result = $res_456; - $this->pos = $pos_456; - $_487 = FALSE; break; - } - while(0); - if( $_487 === TRUE ) { $_489 = TRUE; break; } - $result = $res_454; - $this->pos = $pos_454; - $_489 = FALSE; break; - } - while(0); - if( $_489 === TRUE ) { $_491 = TRUE; break; } - $result = $res_452; - $this->pos = $pos_452; - $_491 = FALSE; break; - } - while(0); - if( $_491 === TRUE ) { $_493 = TRUE; break; } - $result = $res_450; - $this->pos = $pos_450; - $_493 = FALSE; break; - } - while(0); - if( $_493 === TRUE ) { $_495 = TRUE; break; } - $result = $res_448; - $this->pos = $pos_448; - $_495 = FALSE; break; - } - while(0); - if( $_495 === TRUE ) { $_497 = TRUE; break; } - $result = $res_446; - $this->pos = $pos_446; - $_497 = FALSE; break; - } - while(0); - if( $_497 === FALSE) { $_499 = FALSE; break; } - $_499 = TRUE; break; - } - while(0); - if( $_499 === FALSE) { - $result = $res_500; - $this->pos = $pos_500; - unset( $res_500 ); - unset( $pos_500 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - - function CacheRestrictedTemplate_CacheBlock(&$res, $sub) - { - throw new SSTemplateParseException('You cant have cache blocks nested within with, loop or control blocks ' . - 'that are within cache blocks', $this); - } - - function CacheRestrictedTemplate_UncachedBlock(&$res, $sub) - { - throw new SSTemplateParseException('You cant have uncache blocks nested within with, loop or control blocks ' . - 'that are within cache blocks', $this); - } - - /* CacheBlock: - '<%' < CacheTag:("cached"|"cacheblock") < (CacheBlockArguments)? ( < Conditional:("if"|"unless") > - Condition:IfArgument )? > '%>' - (CacheBlock | UncachedBlock | CacheBlockTemplate)* - '<%' < 'end_' ("cached"|"uncached"|"cacheblock") > '%>' */ - protected $match_CacheBlock_typestack = array('CacheBlock'); - function match_CacheBlock ($stack = array()) { - $matchrule = "CacheBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_555 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_555 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $stack[] = $result; $result = $this->construct( $matchrule, "CacheTag" ); - $_508 = NULL; - do { - $_506 = NULL; - do { - $res_503 = $result; - $pos_503 = $this->pos; - if (( $subres = $this->literal( 'cached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_506 = TRUE; break; - } - $result = $res_503; - $this->pos = $pos_503; - if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) { - $result["text"] .= $subres; - $_506 = TRUE; break; - } - $result = $res_503; - $this->pos = $pos_503; - $_506 = FALSE; break; - } - while(0); - if( $_506 === FALSE) { $_508 = FALSE; break; } - $_508 = TRUE; break; - } - while(0); - if( $_508 === TRUE ) { - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'CacheTag' ); - } - if( $_508 === FALSE) { - $result = array_pop($stack); - $_555 = FALSE; break; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_513 = $result; - $pos_513 = $this->pos; - $_512 = NULL; - do { - $matcher = 'match_'.'CacheBlockArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_512 = FALSE; break; } - $_512 = TRUE; break; - } - while(0); - if( $_512 === FALSE) { - $result = $res_513; - $this->pos = $pos_513; - unset( $res_513 ); - unset( $pos_513 ); - } - $res_525 = $result; - $pos_525 = $this->pos; - $_524 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $stack[] = $result; $result = $this->construct( $matchrule, "Conditional" ); - $_520 = NULL; - do { - $_518 = NULL; - do { - $res_515 = $result; - $pos_515 = $this->pos; - if (( $subres = $this->literal( 'if' ) ) !== FALSE) { - $result["text"] .= $subres; - $_518 = TRUE; break; - } - $result = $res_515; - $this->pos = $pos_515; - if (( $subres = $this->literal( 'unless' ) ) !== FALSE) { - $result["text"] .= $subres; - $_518 = TRUE; break; - } - $result = $res_515; - $this->pos = $pos_515; - $_518 = FALSE; break; - } - while(0); - if( $_518 === FALSE) { $_520 = FALSE; break; } - $_520 = TRUE; break; - } - while(0); - if( $_520 === TRUE ) { - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Conditional' ); - } - if( $_520 === FALSE) { - $result = array_pop($stack); - $_524 = FALSE; break; - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'IfArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Condition" ); - } - else { $_524 = FALSE; break; } - $_524 = TRUE; break; - } - while(0); - if( $_524 === FALSE) { - $result = $res_525; - $this->pos = $pos_525; - unset( $res_525 ); - unset( $pos_525 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_555 = FALSE; break; } - while (true) { - $res_538 = $result; - $pos_538 = $this->pos; - $_537 = NULL; - do { - $_535 = NULL; - do { - $res_528 = $result; - $pos_528 = $this->pos; - $matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_535 = TRUE; break; - } - $result = $res_528; - $this->pos = $pos_528; - $_533 = NULL; - do { - $res_530 = $result; - $pos_530 = $this->pos; - $matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_533 = TRUE; break; - } - $result = $res_530; - $this->pos = $pos_530; - $matcher = 'match_'.'CacheBlockTemplate'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_533 = TRUE; break; - } - $result = $res_530; - $this->pos = $pos_530; - $_533 = FALSE; break; - } - while(0); - if( $_533 === TRUE ) { $_535 = TRUE; break; } - $result = $res_528; - $this->pos = $pos_528; - $_535 = FALSE; break; - } - while(0); - if( $_535 === FALSE) { $_537 = FALSE; break; } - $_537 = TRUE; break; - } - while(0); - if( $_537 === FALSE) { - $result = $res_538; - $this->pos = $pos_538; - unset( $res_538 ); - unset( $pos_538 ); - break; - } - } - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_555 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_555 = FALSE; break; } - $_551 = NULL; - do { - $_549 = NULL; - do { - $res_542 = $result; - $pos_542 = $this->pos; - if (( $subres = $this->literal( 'cached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_549 = TRUE; break; - } - $result = $res_542; - $this->pos = $pos_542; - $_547 = NULL; - do { - $res_544 = $result; - $pos_544 = $this->pos; - if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_547 = TRUE; break; - } - $result = $res_544; - $this->pos = $pos_544; - if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) { - $result["text"] .= $subres; - $_547 = TRUE; break; - } - $result = $res_544; - $this->pos = $pos_544; - $_547 = FALSE; break; - } - while(0); - if( $_547 === TRUE ) { $_549 = TRUE; break; } - $result = $res_542; - $this->pos = $pos_542; - $_549 = FALSE; break; - } - while(0); - if( $_549 === FALSE) { $_551 = FALSE; break; } - $_551 = TRUE; break; - } - while(0); - if( $_551 === FALSE) { $_555 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_555 = FALSE; break; } - $_555 = TRUE; break; - } - while(0); - if( $_555 === TRUE ) { return $this->finalise($result); } - if( $_555 === FALSE) { return FALSE; } - } - - - - function CacheBlock__construct(&$res) - { - $res['subblocks'] = 0; - } - - function CacheBlock_CacheBlockArguments(&$res, $sub) - { - $res['key'] = !empty($sub['php']) ? $sub['php'] : ''; - } - - function CacheBlock_Condition(&$res, $sub) - { - $res['condition'] = ($res['Conditional']['text'] == 'if' ? '(' : '!(') . $sub['php'] . ') && '; - } - - function CacheBlock_CacheBlock(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function CacheBlock_UncachedBlock(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function CacheBlock_CacheBlockTemplate(&$res, $sub) - { - // Get the block counter - $block = ++$res['subblocks']; - // Build the key for this block from the global key (evaluated in a closure within the template), - // the passed cache key, the block index, and the sha hash of the template. - $res['php'] .= '$keyExpression = function() use ($scope, $cache) {' . PHP_EOL; - $res['php'] .= '$val = \'\';' . PHP_EOL; - if ($globalKey = SSTemplateEngine::config()->get('global_key')) { - // Embed the code necessary to evaluate the globalKey directly into the template, - // so that SSTemplateParser only needs to be called during template regeneration. - // Warning: If the global key is changed, it's necessary to flush the template cache. - $parser = Injector::inst()->get(__CLASS__, false); - $result = $parser->compileString($globalKey, '', false, false); - if (!$result) { - throw new SSTemplateParseException('Unexpected problem parsing template', $parser); - } - $res['php'] .= $result . PHP_EOL; - } - $res['php'] .= 'return $val;' . PHP_EOL; - $res['php'] .= '};' . PHP_EOL; - $key = 'sha1($keyExpression())' // Global key - . '.\'_' . sha1($sub['php'] ?? '') // sha of template - . (isset($res['key']) && $res['key'] ? "_'.sha1(".$res['key'].")" : "'") // Passed key - . ".'_$block'"; // block index - // Get any condition - $condition = isset($res['condition']) ? $res['condition'] : ''; - - $res['php'] .= 'if ('.$condition.'($partial = $cache->get('.$key.'))) $val .= $partial;' . PHP_EOL; - $res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL; - $res['php'] .= $sub['php'] . PHP_EOL; - $res['php'] .= $condition . ' $cache->set('.$key.', $val); $val = $oldval . $val;' . PHP_EOL; - $res['php'] .= '}'; - } - - /* OldTPart: "_t" N "(" N QuotedString (N "," N CallArguments)? N ")" N (";")? */ - protected $match_OldTPart_typestack = array('OldTPart'); - function match_OldTPart ($stack = array()) { - $matchrule = "OldTPart"; $result = $this->construct($matchrule, $matchrule, null); - $_574 = NULL; - do { - if (( $subres = $this->literal( '_t' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_574 = FALSE; break; } - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_574 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '(') { - $this->pos += 1; - $result["text"] .= '('; - } - else { $_574 = FALSE; break; } - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_574 = FALSE; break; } - $matcher = 'match_'.'QuotedString'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_574 = FALSE; break; } - $res_567 = $result; - $pos_567 = $this->pos; - $_566 = NULL; - do { - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_566 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_566 = FALSE; break; } - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_566 = FALSE; break; } - $matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_566 = FALSE; break; } - $_566 = TRUE; break; - } - while(0); - if( $_566 === FALSE) { - $result = $res_567; - $this->pos = $pos_567; - unset( $res_567 ); - unset( $pos_567 ); - } - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_574 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ')') { - $this->pos += 1; - $result["text"] .= ')'; - } - else { $_574 = FALSE; break; } - $matcher = 'match_'.'N'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_574 = FALSE; break; } - $res_573 = $result; - $pos_573 = $this->pos; - $_572 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == ';') { - $this->pos += 1; - $result["text"] .= ';'; - } - else { $_572 = FALSE; break; } - $_572 = TRUE; break; - } - while(0); - if( $_572 === FALSE) { - $result = $res_573; - $this->pos = $pos_573; - unset( $res_573 ); - unset( $pos_573 ); - } - $_574 = TRUE; break; - } - while(0); - if( $_574 === TRUE ) { return $this->finalise($result); } - if( $_574 === FALSE) { return FALSE; } - } - - - /* N: / [\s\n]* / */ - protected $match_N_typestack = array('N'); - function match_N ($stack = array()) { - $matchrule = "N"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->rx( '/ [\s\n]* /' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - - function OldTPart__construct(&$res) - { - $res['php'] = "_t("; - } - - function OldTPart_QuotedString(&$res, $sub) - { - $entity = $sub['String']['text']; - if (strpos($entity ?? '', '.') === false) { - $res['php'] .= "\$scope->getOutputValue('I18NNamespace').'.$entity'"; - } else { - $res['php'] .= "'$entity'"; - } - } - - function OldTPart_CallArguments(&$res, $sub) - { - $res['php'] .= ',' . $sub['php']; - } - - function OldTPart__finalise(&$res) - { - $res['php'] .= ')'; - } - - /* OldTTag: "<%" < OldTPart > "%>" */ - protected $match_OldTTag_typestack = array('OldTTag'); - function match_OldTTag ($stack = array()) { - $matchrule = "OldTTag"; $result = $this->construct($matchrule, $matchrule, null); - $_582 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_582 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'OldTPart'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_582 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_582 = FALSE; break; } - $_582 = TRUE; break; - } - while(0); - if( $_582 === TRUE ) { return $this->finalise($result); } - if( $_582 === FALSE) { return FALSE; } - } - - - - function OldTTag_OldTPart(&$res, $sub) - { - $res['php'] = $sub['php']; - } - - /* OldSprintfTag: "<%" < "sprintf" < "(" < OldTPart < "," < CallArguments > ")" > "%>" */ - protected $match_OldSprintfTag_typestack = array('OldSprintfTag'); - function match_OldSprintfTag ($stack = array()) { - $matchrule = "OldSprintfTag"; $result = $this->construct($matchrule, $matchrule, null); - $_599 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'sprintf' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '(') { - $this->pos += 1; - $result["text"] .= '('; - } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'OldTPart'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'CallArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ')') { - $this->pos += 1; - $result["text"] .= ')'; - } - else { $_599 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_599 = FALSE; break; } - $_599 = TRUE; break; - } - while(0); - if( $_599 === TRUE ) { return $this->finalise($result); } - if( $_599 === FALSE) { return FALSE; } - } - - - - function OldSprintfTag__construct(&$res) - { - $res['php'] = "sprintf("; - } - - function OldSprintfTag_OldTPart(&$res, $sub) - { - $res['php'] .= $sub['php']; - } - - function OldSprintfTag_CallArguments(&$res, $sub) - { - $res['php'] .= ',' . $sub['php'] . ')'; - } - - /* OldI18NTag: OldSprintfTag | OldTTag */ - protected $match_OldI18NTag_typestack = array('OldI18NTag'); - function match_OldI18NTag ($stack = array()) { - $matchrule = "OldI18NTag"; $result = $this->construct($matchrule, $matchrule, null); - $_604 = NULL; - do { - $res_601 = $result; - $pos_601 = $this->pos; - $matcher = 'match_'.'OldSprintfTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_604 = TRUE; break; - } - $result = $res_601; - $this->pos = $pos_601; - $matcher = 'match_'.'OldTTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_604 = TRUE; break; - } - $result = $res_601; - $this->pos = $pos_601; - $_604 = FALSE; break; - } - while(0); - if( $_604 === TRUE ) { return $this->finalise($result); } - if( $_604 === FALSE) { return FALSE; } - } - - - - function OldI18NTag_STR(&$res, $sub) - { - $res['php'] = '$val .= ' . $sub['php'] . ';'; - } - - /* NamedArgument: Name:Word "=" Value:Argument */ - protected $match_NamedArgument_typestack = array('NamedArgument'); - function match_NamedArgument ($stack = array()) { - $matchrule = "NamedArgument"; $result = $this->construct($matchrule, $matchrule, null); - $_609 = NULL; - do { - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Name" ); - } - else { $_609 = FALSE; break; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == '=') { - $this->pos += 1; - $result["text"] .= '='; - } - else { $_609 = FALSE; break; } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Value" ); - } - else { $_609 = FALSE; break; } - $_609 = TRUE; break; - } - while(0); - if( $_609 === TRUE ) { return $this->finalise($result); } - if( $_609 === FALSE) { return FALSE; } - } - - - - function NamedArgument_Name(&$res, $sub) - { - $res['php'] = "'" . $sub['text'] . "' => "; - } - - function NamedArgument_Value(&$res, $sub) - { - switch ($sub['ArgumentMode']) { - case 'string': - $res['php'] .= $sub['php']; - break; - - case 'default': - $res['php'] .= $sub['string_php']; - break; - - default: - $res['php'] .= str_replace('$$FINAL', 'scopeToIntermediateValue', $sub['php'] ?? '') . '->self()'; - break; - } - } - - /* Include: "<%" < "include" < Template:NamespacedWord < (NamedArgument ( < "," < NamedArgument )*)? > "%>" */ - protected $match_Include_typestack = array('Include'); - function match_Include ($stack = array()) { - $matchrule = "Include"; $result = $this->construct($matchrule, $matchrule, null); - $_628 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_628 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'include' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_628 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'NamespacedWord'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { $_628 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_625 = $result; - $pos_625 = $this->pos; - $_624 = NULL; - do { - $matcher = 'match_'.'NamedArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_624 = FALSE; break; } - while (true) { - $res_623 = $result; - $pos_623 = $this->pos; - $_622 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_622 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'NamedArgument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - } - else { $_622 = FALSE; break; } - $_622 = TRUE; break; - } - while(0); - if( $_622 === FALSE) { - $result = $res_623; - $this->pos = $pos_623; - unset( $res_623 ); - unset( $pos_623 ); - break; - } - } - $_624 = TRUE; break; - } - while(0); - if( $_624 === FALSE) { - $result = $res_625; - $this->pos = $pos_625; - unset( $res_625 ); - unset( $pos_625 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_628 = FALSE; break; } - $_628 = TRUE; break; - } - while(0); - if( $_628 === TRUE ) { return $this->finalise($result); } - if( $_628 === FALSE) { return FALSE; } - } - - - - function Include__construct(&$res) - { - $res['arguments'] = []; - } - - function Include_Template(&$res, $sub) - { - $res['template'] = "'" . $sub['text'] . "'"; - } - - function Include_NamedArgument(&$res, $sub) - { - $res['arguments'][] = $sub['php']; - } - - function Include__finalise(&$res) - { - $template = $res['template']; - $arguments = $res['arguments']; - - // Note: 'type' here is important to disable subTemplates in SSTemplateEngine::getSubtemplateFor() - $res['php'] = '$val .= \\SilverStripe\\View\\SSTemplateEngine::execute_template([["type" => "Includes", '.$template.'], '.$template.'], $scope->getCurrentItem(), [' . - implode(',', $arguments)."], \$scope, true);\n"; - - if ($this->includeDebuggingComments) { // Add include filename comments on dev sites - $res['php'] = - '$val .= \'\';'. "\n". - $res['php']. - '$val .= \'\';'. "\n"; - } - } - - /* BlockArguments: :Argument ( < "," < :Argument)* */ - protected $match_BlockArguments_typestack = array('BlockArguments'); - function match_BlockArguments ($stack = array()) { - $matchrule = "BlockArguments"; $result = $this->construct($matchrule, $matchrule, null); - $_637 = NULL; - do { - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Argument" ); - } - else { $_637 = FALSE; break; } - while (true) { - $res_636 = $result; - $pos_636 = $this->pos; - $_635 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (substr($this->string ?? '',$this->pos ?? 0,1) == ',') { - $this->pos += 1; - $result["text"] .= ','; - } - else { $_635 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $matcher = 'match_'.'Argument'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Argument" ); - } - else { $_635 = FALSE; break; } - $_635 = TRUE; break; - } - while(0); - if( $_635 === FALSE) { - $result = $res_636; - $this->pos = $pos_636; - unset( $res_636 ); - unset( $pos_636 ); - break; - } - } - $_637 = TRUE; break; - } - while(0); - if( $_637 === TRUE ) { return $this->finalise($result); } - if( $_637 === FALSE) { return FALSE; } - } - - - /* NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock" | "include")]) */ - protected $match_NotBlockTag_typestack = array('NotBlockTag'); - function match_NotBlockTag ($stack = array()) { - $matchrule = "NotBlockTag"; $result = $this->construct($matchrule, $matchrule, null); - $_675 = NULL; - do { - $res_639 = $result; - $pos_639 = $this->pos; - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { - $result["text"] .= $subres; - $_675 = TRUE; break; - } - $result = $res_639; - $this->pos = $pos_639; - $_673 = NULL; - do { - $_670 = NULL; - do { - $_668 = NULL; - do { - $res_641 = $result; - $pos_641 = $this->pos; - if (( $subres = $this->literal( 'if' ) ) !== FALSE) { - $result["text"] .= $subres; - $_668 = TRUE; break; - } - $result = $res_641; - $this->pos = $pos_641; - $_666 = NULL; - do { - $res_643 = $result; - $pos_643 = $this->pos; - if (( $subres = $this->literal( 'else_if' ) ) !== FALSE) { - $result["text"] .= $subres; - $_666 = TRUE; break; - } - $result = $res_643; - $this->pos = $pos_643; - $_664 = NULL; - do { - $res_645 = $result; - $pos_645 = $this->pos; - if (( $subres = $this->literal( 'else' ) ) !== FALSE) { - $result["text"] .= $subres; - $_664 = TRUE; break; - } - $result = $res_645; - $this->pos = $pos_645; - $_662 = NULL; - do { - $res_647 = $result; - $pos_647 = $this->pos; - if (( $subres = $this->literal( 'require' ) ) !== FALSE) { - $result["text"] .= $subres; - $_662 = TRUE; break; - } - $result = $res_647; - $this->pos = $pos_647; - $_660 = NULL; - do { - $res_649 = $result; - $pos_649 = $this->pos; - if (( $subres = $this->literal( 'cached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_660 = TRUE; break; - } - $result = $res_649; - $this->pos = $pos_649; - $_658 = NULL; - do { - $res_651 = $result; - $pos_651 = $this->pos; - if (( $subres = $this->literal( 'uncached' ) ) !== FALSE) { - $result["text"] .= $subres; - $_658 = TRUE; break; - } - $result = $res_651; - $this->pos = $pos_651; - $_656 = NULL; - do { - $res_653 = $result; - $pos_653 = $this->pos; - if (( $subres = $this->literal( 'cacheblock' ) ) !== FALSE) { - $result["text"] .= $subres; - $_656 = TRUE; break; - } - $result = $res_653; - $this->pos = $pos_653; - if (( $subres = $this->literal( 'include' ) ) !== FALSE) { - $result["text"] .= $subres; - $_656 = TRUE; break; - } - $result = $res_653; - $this->pos = $pos_653; - $_656 = FALSE; break; - } - while(0); - if( $_656 === TRUE ) { $_658 = TRUE; break; } - $result = $res_651; - $this->pos = $pos_651; - $_658 = FALSE; break; - } - while(0); - if( $_658 === TRUE ) { $_660 = TRUE; break; } - $result = $res_649; - $this->pos = $pos_649; - $_660 = FALSE; break; - } - while(0); - if( $_660 === TRUE ) { $_662 = TRUE; break; } - $result = $res_647; - $this->pos = $pos_647; - $_662 = FALSE; break; - } - while(0); - if( $_662 === TRUE ) { $_664 = TRUE; break; } - $result = $res_645; - $this->pos = $pos_645; - $_664 = FALSE; break; - } - while(0); - if( $_664 === TRUE ) { $_666 = TRUE; break; } - $result = $res_643; - $this->pos = $pos_643; - $_666 = FALSE; break; - } - while(0); - if( $_666 === TRUE ) { $_668 = TRUE; break; } - $result = $res_641; - $this->pos = $pos_641; - $_668 = FALSE; break; - } - while(0); - if( $_668 === FALSE) { $_670 = FALSE; break; } - $_670 = TRUE; break; - } - while(0); - if( $_670 === FALSE) { $_673 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_673 = FALSE; break; } - $_673 = TRUE; break; - } - while(0); - if( $_673 === TRUE ) { $_675 = TRUE; break; } - $result = $res_639; - $this->pos = $pos_639; - $_675 = FALSE; break; - } - while(0); - if( $_675 === TRUE ) { return $this->finalise($result); } - if( $_675 === FALSE) { return FALSE; } - } - - - /* ClosedBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > Zap:'%>' Template:$TemplateMatcher? - '<%' < 'end_' '$BlockName' > '%>' */ - protected $match_ClosedBlock_typestack = array('ClosedBlock'); - function match_ClosedBlock ($stack = array()) { - $matchrule = "ClosedBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_695 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_695 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_679 = $result; - $pos_679 = $this->pos; - $matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $result = $res_679; - $this->pos = $pos_679; - $_695 = FALSE; break; - } - else { - $result = $res_679; - $this->pos = $pos_679; - } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BlockName" ); - } - else { $_695 = FALSE; break; } - $res_685 = $result; - $pos_685 = $this->pos; - $_684 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_684 = FALSE; break; } - $matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BlockArguments" ); - } - else { $_684 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_684 = FALSE; break; } - $_684 = TRUE; break; - } - while(0); - if( $_684 === FALSE) { - $result = $res_685; - $this->pos = $pos_685; - unset( $res_685 ); - unset( $pos_685 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $stack[] = $result; $result = $this->construct( $matchrule, "Zap" ); - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { - $result["text"] .= $subres; - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Zap' ); - } - else { - $result = array_pop($stack); - $_695 = FALSE; break; - } - $res_688 = $result; - $pos_688 = $this->pos; - $matcher = 'match_'.$this->expression($result, $stack, 'TemplateMatcher'); $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Template" ); - } - else { - $result = $res_688; - $this->pos = $pos_688; - unset( $res_688 ); - unset( $pos_688 ); - } - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_695 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_695 = FALSE; break; } - if (( $subres = $this->literal( ''.$this->expression($result, $stack, 'BlockName').'' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_695 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_695 = FALSE; break; } - $_695 = TRUE; break; - } - while(0); - if( $_695 === TRUE ) { return $this->finalise($result); } - if( $_695 === FALSE) { return FALSE; } - } - - - - - /** - * As mentioned in the parser comment, block handling is kept fairly generic for extensibility. The match rule - * builds up two important elements in the match result array: - * 'ArgumentCount' - how many arguments were passed in the opening tag - * 'Arguments' an array of the Argument match rule result arrays - * - * Once a block has successfully been matched against, it will then look for the actual handler, which should - * be on this class (either defined or extended on) as ClosedBlock_Handler_Name(&$res), where Name is the - * tag name, first letter captialized (i.e Control, Loop, With, etc). - * - * This function will be called with the match rule result array as it's first argument. It should return - * the php result of this block as it's return value, or throw an error if incorrect arguments were passed. - */ - - function ClosedBlock__construct(&$res) - { - $res['ArgumentCount'] = 0; - } - - function ClosedBlock_BlockArguments(&$res, $sub) - { - if (isset($sub['Argument']['ArgumentMode'])) { - $res['Arguments'] = [$sub['Argument']]; - $res['ArgumentCount'] = 1; - } else { - $res['Arguments'] = $sub['Argument']; - $res['ArgumentCount'] = count($res['Arguments'] ?? []); - } - } - - function ClosedBlock__finalise(&$res) - { - $blockname = $res['BlockName']['text']; - - $method = 'ClosedBlock_Handle_'.$blockname; - if (method_exists($this, $method ?? '')) { - $res['php'] = $this->$method($res); - } elseif (isset($this->closedBlocks[$blockname])) { - $res['php'] = call_user_func($this->closedBlocks[$blockname], $res); - } else { - throw new SSTemplateParseException('Unknown closed block "'.$blockname.'" encountered. Perhaps you are ' . - 'not supposed to close this block, or have mis-spelled it?', $this); - } - } - - /** - * This is an example of a block handler function. This one handles the loop tag. - */ - function ClosedBlock_Handle_Loop(&$res) - { - if ($res['ArgumentCount'] > 1) { - throw new SSTemplateParseException('Too many arguments in control block. Must be one or no' . - 'arguments only.', $this); - } - - // loop without arguments loops on the current scope - if ($res['ArgumentCount'] == 0) { - $on = '$scope->locally()->self()'; - } else { //loop in the normal way - $arg = $res['Arguments'][0]; - if ($arg['ArgumentMode'] == 'string') { - throw new SSTemplateParseException('Control block cant take string as argument.', $this); - } - $on = str_replace( - '$$FINAL', - 'scopeToIntermediateValue', - ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php'] - ); - } - - return - $on . '; $scope->pushScope(); while ($scope->next() !== false) {' . PHP_EOL . - $res['Template']['php'] . PHP_EOL . - '}; $scope->popScope(); '; - } - - /** - * The closed block handler for with blocks - */ - function ClosedBlock_Handle_With(&$res) - { - if ($res['ArgumentCount'] != 1) { - throw new SSTemplateParseException('Either no or too many arguments in with block. Must be one ' . - 'argument only.', $this); - } - - $arg = $res['Arguments'][0]; - if ($arg['ArgumentMode'] == 'string') { - throw new SSTemplateParseException('Control block cant take string as argument.', $this); - } - - $on = str_replace('$$FINAL', 'scopeToIntermediateValue', ($arg['ArgumentMode'] == 'default') ? $arg['lookup_php'] : $arg['php']); - return - $on . '; $scope->pushScope();' . PHP_EOL . - $res['Template']['php'] . PHP_EOL . - '; $scope->popScope(); '; - } - - /* OpenBlock: '<%' < !NotBlockTag BlockName:Word ( [ :BlockArguments ] )? > '%>' */ - protected $match_OpenBlock_typestack = array('OpenBlock'); - function match_OpenBlock ($stack = array()) { - $matchrule = "OpenBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_708 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_708 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_699 = $result; - $pos_699 = $this->pos; - $matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $result = $res_699; - $this->pos = $pos_699; - $_708 = FALSE; break; - } - else { - $result = $res_699; - $this->pos = $pos_699; - } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BlockName" ); - } - else { $_708 = FALSE; break; } - $res_705 = $result; - $pos_705 = $this->pos; - $_704 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_704 = FALSE; break; } - $matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BlockArguments" ); - } - else { $_704 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_704 = FALSE; break; } - $_704 = TRUE; break; - } - while(0); - if( $_704 === FALSE) { - $result = $res_705; - $this->pos = $pos_705; - unset( $res_705 ); - unset( $pos_705 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_708 = FALSE; break; } - $_708 = TRUE; break; - } - while(0); - if( $_708 === TRUE ) { return $this->finalise($result); } - if( $_708 === FALSE) { return FALSE; } - } - - - - function OpenBlock__construct(&$res) - { - $res['ArgumentCount'] = 0; - } - - function OpenBlock_BlockArguments(&$res, $sub) - { - if (isset($sub['Argument']['ArgumentMode'])) { - $res['Arguments'] = [$sub['Argument']]; - $res['ArgumentCount'] = 1; - } else { - $res['Arguments'] = $sub['Argument']; - $res['ArgumentCount'] = count($res['Arguments'] ?? []); - } - } - - function OpenBlock__finalise(&$res) - { - $blockname = $res['BlockName']['text']; - - $method = 'OpenBlock_Handle_'.$blockname; - if (method_exists($this, $method ?? '')) { - $res['php'] = $this->$method($res); - } elseif (isset($this->openBlocks[$blockname])) { - $res['php'] = call_user_func($this->openBlocks[$blockname], $res); - } else { - throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed ' . - ' the closing tag or have mis-spelled it?', $this); - } - } - - /** - * This is an open block handler, for the <% base_tag %> tag - */ - function OpenBlock_Handle_Base_tag(&$res) - { - if ($res['ArgumentCount'] != 0) { - throw new SSTemplateParseException('Base_tag takes no arguments', $this); - } - $code = '$isXhtml = preg_match(\'/]+xhtml/i\', $val);'; - $code .= PHP_EOL . '$val .= \\SilverStripe\\View\\SSViewer::getBaseTag($isXhtml);'; - return $code; - } - - /** - * This is an open block handler, for the <% current_page %> tag - */ - function OpenBlock_Handle_Current_page(&$res) - { - if ($res['ArgumentCount'] != 0) { - throw new SSTemplateParseException('Current_page takes no arguments', $this); - } - return '$val .= $_SERVER[SCRIPT_URL];'; - } - - /* MismatchedEndBlock: '<%' < 'end_' :Word > '%>' */ - protected $match_MismatchedEndBlock_typestack = array('MismatchedEndBlock'); - function match_MismatchedEndBlock ($stack = array()) { - $matchrule = "MismatchedEndBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_716 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_716 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_716 = FALSE; break; } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Word" ); - } - else { $_716 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_716 = FALSE; break; } - $_716 = TRUE; break; - } - while(0); - if( $_716 === TRUE ) { return $this->finalise($result); } - if( $_716 === FALSE) { return FALSE; } - } - - - - function MismatchedEndBlock__finalise(&$res) - { - $blockname = $res['Word']['text']; - throw new SSTemplateParseException('Unexpected close tag end_' . $blockname . - ' encountered. Perhaps you have mis-nested blocks, or have mis-spelled a tag?', $this); - } - - /* MalformedOpenTag: '<%' < !NotBlockTag Tag:Word !( ( [ :BlockArguments ] )? > '%>' ) */ - protected $match_MalformedOpenTag_typestack = array('MalformedOpenTag'); - function match_MalformedOpenTag ($stack = array()) { - $matchrule = "MalformedOpenTag"; $result = $this->construct($matchrule, $matchrule, null); - $_731 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_731 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $res_720 = $result; - $pos_720 = $this->pos; - $matcher = 'match_'.'NotBlockTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $result = $res_720; - $this->pos = $pos_720; - $_731 = FALSE; break; - } - else { - $result = $res_720; - $this->pos = $pos_720; - } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Tag" ); - } - else { $_731 = FALSE; break; } - $res_730 = $result; - $pos_730 = $this->pos; - $_729 = NULL; - do { - $res_726 = $result; - $pos_726 = $this->pos; - $_725 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_725 = FALSE; break; } - $matcher = 'match_'.'BlockArguments'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "BlockArguments" ); - } - else { $_725 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_725 = FALSE; break; } - $_725 = TRUE; break; - } - while(0); - if( $_725 === FALSE) { - $result = $res_726; - $this->pos = $pos_726; - unset( $res_726 ); - unset( $pos_726 ); - } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_729 = FALSE; break; } - $_729 = TRUE; break; - } - while(0); - if( $_729 === TRUE ) { - $result = $res_730; - $this->pos = $pos_730; - $_731 = FALSE; break; - } - if( $_729 === FALSE) { - $result = $res_730; - $this->pos = $pos_730; - } - $_731 = TRUE; break; - } - while(0); - if( $_731 === TRUE ) { return $this->finalise($result); } - if( $_731 === FALSE) { return FALSE; } - } - - - - function MalformedOpenTag__finalise(&$res) - { - $tag = $res['Tag']['text']; - throw new SSTemplateParseException("Malformed opening block tag $tag. Perhaps you have tried to use operators?", $this); - } - - /* MalformedCloseTag: '<%' < Tag:('end_' :Word ) !( > '%>' ) */ - protected $match_MalformedCloseTag_typestack = array('MalformedCloseTag'); - function match_MalformedCloseTag ($stack = array()) { - $matchrule = "MalformedCloseTag"; $result = $this->construct($matchrule, $matchrule, null); - $_743 = NULL; - do { - if (( $subres = $this->literal( '<%' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_743 = FALSE; break; } - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - $stack[] = $result; $result = $this->construct( $matchrule, "Tag" ); - $_737 = NULL; - do { - if (( $subres = $this->literal( 'end_' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_737 = FALSE; break; } - $matcher = 'match_'.'Word'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "Word" ); - } - else { $_737 = FALSE; break; } - $_737 = TRUE; break; - } - while(0); - if( $_737 === TRUE ) { - $subres = $result; $result = array_pop($stack); - $this->store( $result, $subres, 'Tag' ); - } - if( $_737 === FALSE) { - $result = array_pop($stack); - $_743 = FALSE; break; - } - $res_742 = $result; - $pos_742 = $this->pos; - $_741 = NULL; - do { - if (( $subres = $this->whitespace( ) ) !== FALSE) { $result["text"] .= $subres; } - if (( $subres = $this->literal( '%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_741 = FALSE; break; } - $_741 = TRUE; break; - } - while(0); - if( $_741 === TRUE ) { - $result = $res_742; - $this->pos = $pos_742; - $_743 = FALSE; break; - } - if( $_741 === FALSE) { - $result = $res_742; - $this->pos = $pos_742; - } - $_743 = TRUE; break; - } - while(0); - if( $_743 === TRUE ) { return $this->finalise($result); } - if( $_743 === FALSE) { return FALSE; } - } - - - - function MalformedCloseTag__finalise(&$res) - { - $tag = $res['Tag']['text']; - throw new SSTemplateParseException("Malformed closing block tag $tag. Perhaps you have tried to pass an " . - "argument to one?", $this); - } - - /* MalformedBlock: MalformedOpenTag | MalformedCloseTag */ - protected $match_MalformedBlock_typestack = array('MalformedBlock'); - function match_MalformedBlock ($stack = array()) { - $matchrule = "MalformedBlock"; $result = $this->construct($matchrule, $matchrule, null); - $_748 = NULL; - do { - $res_745 = $result; - $pos_745 = $this->pos; - $matcher = 'match_'.'MalformedOpenTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_748 = TRUE; break; - } - $result = $res_745; - $this->pos = $pos_745; - $matcher = 'match_'.'MalformedCloseTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_748 = TRUE; break; - } - $result = $res_745; - $this->pos = $pos_745; - $_748 = FALSE; break; - } - while(0); - if( $_748 === TRUE ) { return $this->finalise($result); } - if( $_748 === FALSE) { return FALSE; } - } - - - - - /* CommentWithContent: '<%--' ( !"--%>" /(?s)./ )+ '--%>' */ - protected $match_CommentWithContent_typestack = array('CommentWithContent'); - function match_CommentWithContent ($stack = array()) { - $matchrule = "CommentWithContent"; $result = $this->construct($matchrule, $matchrule, null); - $_756 = NULL; - do { - if (( $subres = $this->literal( '<%--' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_756 = FALSE; break; } - $count = 0; - while (true) { - $res_754 = $result; - $pos_754 = $this->pos; - $_753 = NULL; - do { - $res_751 = $result; - $pos_751 = $this->pos; - if (( $subres = $this->literal( '--%>' ) ) !== FALSE) { - $result["text"] .= $subres; - $result = $res_751; - $this->pos = $pos_751; - $_753 = FALSE; break; - } - else { - $result = $res_751; - $this->pos = $pos_751; - } - if (( $subres = $this->rx( '/(?s)./' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_753 = FALSE; break; } - $_753 = TRUE; break; - } - while(0); - if( $_753 === FALSE) { - $result = $res_754; - $this->pos = $pos_754; - unset( $res_754 ); - unset( $pos_754 ); - break; - } - $count += 1; - } - if ($count > 0) { } - else { $_756 = FALSE; break; } - if (( $subres = $this->literal( '--%>' ) ) !== FALSE) { $result["text"] .= $subres; } - else { $_756 = FALSE; break; } - $_756 = TRUE; break; - } - while(0); - if( $_756 === TRUE ) { return $this->finalise($result); } - if( $_756 === FALSE) { return FALSE; } - } - - - /* EmptyComment: '<%----%>' */ - protected $match_EmptyComment_typestack = array('EmptyComment'); - function match_EmptyComment ($stack = array()) { - $matchrule = "EmptyComment"; $result = $this->construct($matchrule, $matchrule, null); - if (( $subres = $this->literal( '<%----%>' ) ) !== FALSE) { - $result["text"] .= $subres; - return $this->finalise($result); - } - else { return FALSE; } - } - - - /* Comment: :EmptyComment | :CommentWithContent */ - protected $match_Comment_typestack = array('Comment'); - function match_Comment ($stack = array()) { - $matchrule = "Comment"; $result = $this->construct($matchrule, $matchrule, null); - $_762 = NULL; - do { - $res_759 = $result; - $pos_759 = $this->pos; - $matcher = 'match_'.'EmptyComment'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "EmptyComment" ); - $_762 = TRUE; break; - } - $result = $res_759; - $this->pos = $pos_759; - $matcher = 'match_'.'CommentWithContent'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres, "CommentWithContent" ); - $_762 = TRUE; break; - } - $result = $res_759; - $this->pos = $pos_759; - $_762 = FALSE; break; - } - while(0); - if( $_762 === TRUE ) { return $this->finalise($result); } - if( $_762 === FALSE) { return FALSE; } - } - - - - function Comment__construct(&$res) - { - $res['php'] = ''; - } - - /* TopTemplate: (Comment | Translate | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | - OpenBlock | MalformedBlock | MismatchedEndBlock | MalformedBracketInjection | Injection | Text)+ */ - protected $match_TopTemplate_typestack = array('TopTemplate','Template'); - function match_TopTemplate ($stack = array()) { - $matchrule = "TopTemplate"; $result = $this->construct($matchrule, $matchrule, array('TemplateMatcher' => 'Template')); - $count = 0; - while (true) { - $res_822 = $result; - $pos_822 = $this->pos; - $_821 = NULL; - do { - $_819 = NULL; - do { - $res_764 = $result; - $pos_764 = $this->pos; - $matcher = 'match_'.'Comment'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_819 = TRUE; break; - } - $result = $res_764; - $this->pos = $pos_764; - $_817 = NULL; - do { - $res_766 = $result; - $pos_766 = $this->pos; - $matcher = 'match_'.'Translate'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_817 = TRUE; break; - } - $result = $res_766; - $this->pos = $pos_766; - $_815 = NULL; - do { - $res_768 = $result; - $pos_768 = $this->pos; - $matcher = 'match_'.'If'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_815 = TRUE; break; - } - $result = $res_768; - $this->pos = $pos_768; - $_813 = NULL; - do { - $res_770 = $result; - $pos_770 = $this->pos; - $matcher = 'match_'.'Require'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_813 = TRUE; break; - } - $result = $res_770; - $this->pos = $pos_770; - $_811 = NULL; - do { - $res_772 = $result; - $pos_772 = $this->pos; - $matcher = 'match_'.'CacheBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_811 = TRUE; break; - } - $result = $res_772; - $this->pos = $pos_772; - $_809 = NULL; - do { - $res_774 = $result; - $pos_774 = $this->pos; - $matcher = 'match_'.'UncachedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_809 = TRUE; break; - } - $result = $res_774; - $this->pos = $pos_774; - $_807 = NULL; - do { - $res_776 = $result; - $pos_776 = $this->pos; - $matcher = 'match_'.'OldI18NTag'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_807 = TRUE; break; - } - $result = $res_776; - $this->pos = $pos_776; - $_805 = NULL; - do { - $res_778 = $result; - $pos_778 = $this->pos; - $matcher = 'match_'.'Include'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_805 = TRUE; break; - } - $result = $res_778; - $this->pos = $pos_778; - $_803 = NULL; - do { - $res_780 = $result; - $pos_780 = $this->pos; - $matcher = 'match_'.'ClosedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_803 = TRUE; break; - } - $result = $res_780; - $this->pos = $pos_780; - $_801 = NULL; - do { - $res_782 = $result; - $pos_782 = $this->pos; - $matcher = 'match_'.'OpenBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_801 = TRUE; break; - } - $result = $res_782; - $this->pos = $pos_782; - $_799 = NULL; - do { - $res_784 = $result; - $pos_784 = $this->pos; - $matcher = 'match_'.'MalformedBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_799 = TRUE; break; - } - $result = $res_784; - $this->pos = $pos_784; - $_797 = NULL; - do { - $res_786 = $result; - $pos_786 = $this->pos; - $matcher = 'match_'.'MismatchedEndBlock'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_797 = TRUE; break; - } - $result = $res_786; - $this->pos = $pos_786; - $_795 = NULL; - do { - $res_788 = $result; - $pos_788 = $this->pos; - $matcher = 'match_'.'MalformedBracketInjection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_795 = TRUE; break; - } - $result = $res_788; - $this->pos = $pos_788; - $_793 = NULL; - do { - $res_790 = $result; - $pos_790 = $this->pos; - $matcher = 'match_'.'Injection'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_793 = TRUE; break; - } - $result = $res_790; - $this->pos = $pos_790; - $matcher = 'match_'.'Text'; $key = $matcher; $pos = $this->pos; - $subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) ); - if ($subres !== FALSE) { - $this->store( $result, $subres ); - $_793 = TRUE; break; - } - $result = $res_790; - $this->pos = $pos_790; - $_793 = FALSE; break; - } - while(0); - if( $_793 === TRUE ) { - $_795 = TRUE; break; - } - $result = $res_788; - $this->pos = $pos_788; - $_795 = FALSE; break; - } - while(0); - if( $_795 === TRUE ) { - $_797 = TRUE; break; - } - $result = $res_786; - $this->pos = $pos_786; - $_797 = FALSE; break; - } - while(0); - if( $_797 === TRUE ) { $_799 = TRUE; break; } - $result = $res_784; - $this->pos = $pos_784; - $_799 = FALSE; break; - } - while(0); - if( $_799 === TRUE ) { $_801 = TRUE; break; } - $result = $res_782; - $this->pos = $pos_782; - $_801 = FALSE; break; - } - while(0); - if( $_801 === TRUE ) { $_803 = TRUE; break; } - $result = $res_780; - $this->pos = $pos_780; - $_803 = FALSE; break; - } - while(0); - if( $_803 === TRUE ) { $_805 = TRUE; break; } - $result = $res_778; - $this->pos = $pos_778; - $_805 = FALSE; break; - } - while(0); - if( $_805 === TRUE ) { $_807 = TRUE; break; } - $result = $res_776; - $this->pos = $pos_776; - $_807 = FALSE; break; - } - while(0); - if( $_807 === TRUE ) { $_809 = TRUE; break; } - $result = $res_774; - $this->pos = $pos_774; - $_809 = FALSE; break; - } - while(0); - if( $_809 === TRUE ) { $_811 = TRUE; break; } - $result = $res_772; - $this->pos = $pos_772; - $_811 = FALSE; break; - } - while(0); - if( $_811 === TRUE ) { $_813 = TRUE; break; } - $result = $res_770; - $this->pos = $pos_770; - $_813 = FALSE; break; - } - while(0); - if( $_813 === TRUE ) { $_815 = TRUE; break; } - $result = $res_768; - $this->pos = $pos_768; - $_815 = FALSE; break; - } - while(0); - if( $_815 === TRUE ) { $_817 = TRUE; break; } - $result = $res_766; - $this->pos = $pos_766; - $_817 = FALSE; break; - } - while(0); - if( $_817 === TRUE ) { $_819 = TRUE; break; } - $result = $res_764; - $this->pos = $pos_764; - $_819 = FALSE; break; - } - while(0); - if( $_819 === FALSE) { $_821 = FALSE; break; } - $_821 = TRUE; break; - } - while(0); - if( $_821 === FALSE) { - $result = $res_822; - $this->pos = $pos_822; - unset( $res_822 ); - unset( $pos_822 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - - - /** - * The TopTemplate also includes the opening stanza to start off the template - */ - function TopTemplate__construct(&$res) - { - $res['php'] = "construct($matchrule, $matchrule, null); - $count = 0; - while (true) { - $res_861 = $result; - $pos_861 = $this->pos; - $_860 = NULL; - do { - $_858 = NULL; - do { - $res_823 = $result; - $pos_823 = $this->pos; - if (( $subres = $this->rx( '/ [^<${\\\\]+ /' ) ) !== FALSE) { - $result["text"] .= $subres; - $_858 = TRUE; break; - } - $result = $res_823; - $this->pos = $pos_823; - $_856 = NULL; - do { - $res_825 = $result; - $pos_825 = $this->pos; - if (( $subres = $this->rx( '/ (\\\\.) /' ) ) !== FALSE) { - $result["text"] .= $subres; - $_856 = TRUE; break; - } - $result = $res_825; - $this->pos = $pos_825; - $_854 = NULL; - do { - $res_827 = $result; - $pos_827 = $this->pos; - $_830 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '<') { - $this->pos += 1; - $result["text"] .= '<'; - } - else { $_830 = FALSE; break; } - $res_829 = $result; - $pos_829 = $this->pos; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '%') { - $this->pos += 1; - $result["text"] .= '%'; - $result = $res_829; - $this->pos = $pos_829; - $_830 = FALSE; break; - } - else { - $result = $res_829; - $this->pos = $pos_829; - } - $_830 = TRUE; break; - } - while(0); - if( $_830 === TRUE ) { $_854 = TRUE; break; } - $result = $res_827; - $this->pos = $pos_827; - $_852 = NULL; - do { - $res_832 = $result; - $pos_832 = $this->pos; - $_837 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '$') { - $this->pos += 1; - $result["text"] .= '$'; - } - else { $_837 = FALSE; break; } - $res_836 = $result; - $pos_836 = $this->pos; - $_835 = NULL; - do { - if (( $subres = $this->rx( '/[A-Za-z_]/' ) ) !== FALSE) { - $result["text"] .= $subres; - } - else { $_835 = FALSE; break; } - $_835 = TRUE; break; - } - while(0); - if( $_835 === TRUE ) { - $result = $res_836; - $this->pos = $pos_836; - $_837 = FALSE; break; - } - if( $_835 === FALSE) { - $result = $res_836; - $this->pos = $pos_836; - } - $_837 = TRUE; break; - } - while(0); - if( $_837 === TRUE ) { $_852 = TRUE; break; } - $result = $res_832; - $this->pos = $pos_832; - $_850 = NULL; - do { - $res_839 = $result; - $pos_839 = $this->pos; - $_842 = NULL; - do { - if (substr($this->string ?? '',$this->pos ?? 0,1) == '{') { - $this->pos += 1; - $result["text"] .= '{'; - } - else { $_842 = FALSE; break; } - $res_841 = $result; - $pos_841 = $this->pos; - if (substr($this->string ?? '',$this->pos ?? 0,1) == '$') { - $this->pos += 1; - $result["text"] .= '$'; - $result = $res_841; - $this->pos = $pos_841; - $_842 = FALSE; break; - } - else { - $result = $res_841; - $this->pos = $pos_841; - } - $_842 = TRUE; break; - } - while(0); - if( $_842 === TRUE ) { $_850 = TRUE; break; } - $result = $res_839; - $this->pos = $pos_839; - $_848 = NULL; - do { - if (( $subres = $this->literal( '{$' ) ) !== FALSE) { - $result["text"] .= $subres; - } - else { $_848 = FALSE; break; } - $res_847 = $result; - $pos_847 = $this->pos; - $_846 = NULL; - do { - if (( $subres = $this->rx( '/[A-Za-z_]/' ) ) !== FALSE) { - $result["text"] .= $subres; - } - else { $_846 = FALSE; break; } - $_846 = TRUE; break; - } - while(0); - if( $_846 === TRUE ) { - $result = $res_847; - $this->pos = $pos_847; - $_848 = FALSE; break; - } - if( $_846 === FALSE) { - $result = $res_847; - $this->pos = $pos_847; - } - $_848 = TRUE; break; - } - while(0); - if( $_848 === TRUE ) { $_850 = TRUE; break; } - $result = $res_839; - $this->pos = $pos_839; - $_850 = FALSE; break; - } - while(0); - if( $_850 === TRUE ) { $_852 = TRUE; break; } - $result = $res_832; - $this->pos = $pos_832; - $_852 = FALSE; break; - } - while(0); - if( $_852 === TRUE ) { $_854 = TRUE; break; } - $result = $res_827; - $this->pos = $pos_827; - $_854 = FALSE; break; - } - while(0); - if( $_854 === TRUE ) { $_856 = TRUE; break; } - $result = $res_825; - $this->pos = $pos_825; - $_856 = FALSE; break; - } - while(0); - if( $_856 === TRUE ) { $_858 = TRUE; break; } - $result = $res_823; - $this->pos = $pos_823; - $_858 = FALSE; break; - } - while(0); - if( $_858 === FALSE) { $_860 = FALSE; break; } - $_860 = TRUE; break; - } - while(0); - if( $_860 === FALSE) { - $result = $res_861; - $this->pos = $pos_861; - unset( $res_861 ); - unset( $pos_861 ); - break; - } - $count += 1; - } - if ($count > 0) { return $this->finalise($result); } - else { return FALSE; } - } - - - - - /** - * We convert text - */ - function Text__finalise(&$res) - { - $text = $res['text']; - - // Unescape any escaped characters in the text, then put back escapes for any single quotes and backslashes - $text = stripslashes($text ?? ''); - $text = addcslashes($text ?? '', '\'\\'); - - // TODO: This is pretty ugly & gets applied on all files not just html. I wonder if we can make this - // non-dynamically calculated - $code = <<<'EOC' -(\SilverStripe\View\SSViewer::getRewriteHashLinksDefault() - ? \SilverStripe\Core\Convert::raw2att( preg_replace("/^(\\/)+/", "/", $_SERVER['REQUEST_URI'] ) ) - : "") -EOC; - // Because preg_replace replacement requires escaped slashes, addcslashes here - $text = preg_replace( - '/(]+href *= *)"#/i', - '\\1"\' . ' . addcslashes($code ?? '', '\\') . ' . \'#', - $text ?? '' - ); - - $res['php'] .= '$val .= \'' . $text . '\';' . PHP_EOL; - } - - /****************** - * Here ends the parser itself. Below are utility methods to use the parser - */ - - /** - * Compiles some passed template source code into the php code that will execute as per the template source. - * - * @throws SSTemplateParseException - * @param string $string The source of the template - * @param string $templateName The name of the template, normally the filename the template source was loaded from - * @param bool $includeDebuggingComments True is debugging comments should be included in the output - * @param bool $topTemplate True if this is a top template, false if it's just a template - * @return string The php that, when executed (via include or exec) will behave as per the template source - */ - public function compileString(string $string, string $templateName = "", bool $includeDebuggingComments = false, bool $topTemplate = true): string - { - if (!trim($string ?? '')) { - $code = ''; - } else { - parent::__construct($string); - - $this->includeDebuggingComments = $includeDebuggingComments; - - // Ignore UTF8 BOM at beginning of string. - if (substr($string ?? '', 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) { - $this->pos = 3; - } - - // Match the source against the parser - if ($topTemplate) { - $result = $this->match_TopTemplate(); - } else { - $result = $this->match_Template(); - } - if (!$result) { - throw new SSTemplateParseException('Unexpected problem parsing template', $this); - } - - // Get the result - $code = $result['php']; - } - - // Include top level debugging comments if desired - if ($includeDebuggingComments && $templateName && stripos($code ?? '', "includeDebuggingComments($code, $templateName); - } - - return $code; - } - - /** - * @param string $code - * @param string $templateName - * @return string $code - */ - protected function includeDebuggingComments(string $code, string $templateName): string - { - // If this template contains a doctype, put it right after it, - // if not, put it after the tag to avoid IE glitches - if (stripos($code ?? '', "]*("[^"]")*[^>]*>)/im', "$1\r\n", $code ?? ''); - $code .= "\r\n" . '$val .= \'\';'; - } elseif (stripos($code ?? '', "]*>)(.*)/i', function ($matches) use ($templateName) { - if (stripos($matches[3] ?? '', '') !== false) { - // after this tag there is a comment close but no comment has been opened - // this most likely means that this tag is inside a comment - // we should not add a comment inside a comment (invalid html) - // lets append it at the end of the comment - // an example case for this is the html5boilerplate: - return $matches[0]; - } else { - // all other cases, add the comment and return it - return "{$matches[1]}{$matches[2]}{$matches[3]}"; - } - }, $code ?? ''); - $code = preg_replace('/(<\/html[^>]*>)/i', "$1", $code ?? ''); - } else { - $code = str_replace('\';' . "\r\n", $code ?? ''); - $code .= "\r\n" . '$val .= \'\';'; - } - return $code; - } - - /** - * Compiles some file that contains template source code, and returns the php code that will execute as per that - * source - * - * @param string $template - A file path that contains template source code - * @return string - The php that, when executed (via include or exec) will behave as per the template source - */ - public function compileFile(string $template): string - { - return $this->compileString(file_get_contents($template ?? ''), $template); - } -} diff --git a/src/View/SSViewer_BasicIteratorSupport.php b/src/View/SSViewer_BasicIteratorSupport.php deleted file mode 100644 index 99d02106b96..00000000000 --- a/src/View/SSViewer_BasicIteratorSupport.php +++ /dev/null @@ -1,211 +0,0 @@ -iteratorPos = $pos; - $this->iteratorTotalItems = $totalItems; - } - - /** - * Returns true if this object is the first in a set. - * - * @return bool - */ - public function IsFirst() - { - return $this->iteratorPos == 0; - } - - /** - * Returns true if this object is the last in a set. - * - * @return bool - */ - public function IsLast() - { - return $this->iteratorPos == $this->iteratorTotalItems - 1; - } - - /** - * Returns 'first' or 'last' if this is the first or last object in the set. - * - * @return string|null - */ - public function FirstLast() - { - if ($this->IsFirst() && $this->IsLast()) { - return 'first last'; - } - if ($this->IsFirst()) { - return 'first'; - } - if ($this->IsLast()) { - return 'last'; - } - return null; - } - - /** - * Return true if this object is between the first & last objects. - * - * @return bool - */ - public function Middle() - { - return !$this->IsFirst() && !$this->IsLast(); - } - - /** - * Return 'middle' if this object is between the first & last objects. - * - * @return string - */ - public function MiddleString() - { - if ($this->Middle()) { - return 'middle'; - } - return null; - } - - /** - * Return true if this object is an even item in the set. - * The count starts from $startIndex, which defaults to 1. - * - * @param int $startIndex Number to start count from. - * @return bool - */ - public function Even($startIndex = 1) - { - return !$this->Odd($startIndex); - } - - /** - * Return true if this is an odd item in the set. - * - * @param int $startIndex Number to start count from. - * @return bool - */ - public function Odd($startIndex = 1) - { - return (bool)(($this->iteratorPos + $startIndex) % 2); - } - - /** - * Return 'even' or 'odd' if this object is in an even or odd position in the set respectively. - * - * @param int $startIndex Number to start count from. - * @return string - */ - public function EvenOdd($startIndex = 1) - { - return ($this->Even($startIndex)) ? 'even' : 'odd'; - } - - /** - * Return the numerical position of this object in the container set. The count starts at $startIndex. - * The default is the give the position using a 1-based index. - * - * @param int $startIndex Number to start count from. - * @return int - */ - public function Pos($startIndex = 1) - { - return $this->iteratorPos + $startIndex; - } - - /** - * Return the position of this item from the last item in the list. The position of the final - * item is $endIndex, which defaults to 1. - * - * @param int $endIndex Value of the last item - * @return int - */ - public function FromEnd($endIndex = 1) - { - return $this->iteratorTotalItems - $this->iteratorPos + $endIndex - 1; - } - - /** - * Return the total number of "sibling" items in the dataset. - * - * @return int - */ - public function TotalItems() - { - return $this->iteratorTotalItems; - } - - /** - * Returns the modulus of the numerical position of the item in the data set. - * The count starts from $startIndex, which defaults to 1. - * - * @param int $mod The number to perform Mod operation to. - * @param int $startIndex Number to start count from. - * @return int - */ - public function Modulus($mod, $startIndex = 1) - { - return ($this->iteratorPos + $startIndex) % $mod; - } - - /** - * Returns true or false depending on if the pos of the iterator is a multiple of a specific number. - * So, <% if MultipleOf(3) %> would return true on indexes: 3,6,9,12,15, etc. - * The count starts from $offset, which defaults to 1. - * - * @param int $factor The multiple of which to return - * @param int $offset Number to start count from. - * @return bool - */ - public function MultipleOf($factor, $offset = 1) - { - return (bool)($this->Modulus($factor, $offset) == 0); - } -} diff --git a/src/View/SSViewer_Scope.php b/src/View/SSViewer_Scope.php deleted file mode 100644 index 5f7836c8c68..00000000000 --- a/src/View/SSViewer_Scope.php +++ /dev/null @@ -1,620 +0,0 @@ -item = $item; - - $this->itemIterator = ($inheritedScope) ? $inheritedScope->itemIterator : null; - $this->itemIteratorTotal = ($inheritedScope) ? $inheritedScope->itemIteratorTotal : 0; - $this->itemStack[] = [$this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0]; - - $this->overlay = $overlay; - $this->underlay = $underlay; - - $this->cacheGlobalProperties(); - $this->cacheIteratorProperties(); - } - - /** - * Returns the current "current" item in scope - */ - public function getCurrentItem(): ?ViewLayerData - { - return $this->itemIterator ? $this->itemIterator->current() : $this->item; - } - - /** - * Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope - * - * @return SSViewer_Scope - */ - public function locally() - { - list( - $this->item, - $this->itemIterator, - $this->itemIteratorTotal, - $this->popIndex, - $this->upIndex, - $this->currentIndex - ) = $this->itemStack[$this->localIndex]; - - // Remember any un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an - // un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later - $this->localStack[] = array_splice($this->itemStack, $this->localIndex + 1); - - return $this; - } - - /** - * Set scope to an intermediate value, which will be used for getting output later on. - */ - public function scopeToIntermediateValue(string $name, array $arguments): static - { - $overlayIndex = false; - - // $Up and $Top need to restore the overlay from the parent and top-level scope respectively. - switch ($name) { - case 'Up': - $upIndex = $this->getUpIndex(); - if ($upIndex === null) { - throw new \LogicException('Up called when we\'re already at the top of the scope'); - } - $overlayIndex = $upIndex; // Parent scope - $this->preserveOverlay = true; // Preserve overlay - list( - $this->item, - $this->itemIterator, - $this->itemIteratorTotal, - /* dud */, - $this->upIndex, - $this->currentIndex - ) = $this->itemStack[$this->upIndex]; - break; - case 'Top': - $overlayIndex = 0; // Top-level scope - $this->preserveOverlay = true; // Preserve overlay - list( - $this->item, - $this->itemIterator, - $this->itemIteratorTotal, - /* dud */, - $this->upIndex, - $this->currentIndex - ) = $this->itemStack[0]; - break; - default: - $this->preserveOverlay = false; - $this->item = $this->getObj($name, $arguments); - $this->itemIterator = null; - $this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack) - 1; - $this->currentIndex = count($this->itemStack); - break; - } - - if ($overlayIndex !== false) { - $itemStack = $this->getItemStack(); - if (!$this->overlay && isset($itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY])) { - $this->overlay = $itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY]; - } - } - - $this->itemStack[] = [ - $this->item, - $this->itemIterator, - $this->itemIteratorTotal, - null, - $this->upIndex, - $this->currentIndex - ]; - return $this; - } - - /** - * Gets the current object and resets the scope. - */ - public function self(): ?ViewLayerData - { - $result = $this->getCurrentItem(); - $this->resetLocalScope(); - - return $result; - } - - /** - * Jump to the last item in the stack, called when a new item is added before a loop/with - * - * Store the current overlay (as it doesn't directly apply to the new scope - * that's being pushed). We want to store the overlay against the next item - * "up" in the stack (hence upIndex), rather than the current item, because - * SSViewer_Scope::obj() has already been called and pushed the new item to - * the stack by this point - */ - public function pushScope(): static - { - $newLocalIndex = count($this->itemStack ?? []) - 1; - - $this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex; - $this->localIndex = $newLocalIndex; - - // $Up now becomes the parent scope - the parent of the current <% loop %> or <% with %> - $this->upIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::UP_INDEX] = $this->popIndex; - - // We normally keep any previous itemIterator around, so local $Up calls reference the right element. But - // once we enter a new global scope, we need to make sure we use a new one - $this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null; - - $upIndex = $this->getUpIndex() ?: 0; - - $itemStack = $this->getItemStack(); - $itemStack[$upIndex][SSViewer_Scope::ITEM_OVERLAY] = $this->overlay; - $this->setItemStack($itemStack); - - // Remove the overlay when we're changing to a new scope, as values in - // that scope take priority. The exceptions that set this flag are $Up - // and $Top as they require that the new scope inherits the overlay - if (!$this->preserveOverlay) { - $this->overlay = []; - } - - return $this; - } - - /** - * Jump back to "previous" item in the stack, called after a loop/with block - * - * Now that we're going to jump up an item in the item stack, we need to - * restore the overlay that was previously stored against the next item "up" - * in the stack from the current one - */ - public function popScope(): static - { - $upIndex = $this->getUpIndex(); - - if ($upIndex !== null) { - $itemStack = $this->getItemStack(); - $this->overlay = $itemStack[$upIndex][SSViewer_Scope::ITEM_OVERLAY]; - } - - $this->localIndex = $this->popIndex; - $this->resetLocalScope(); - - return $this; - } - - /** - * Fast-forwards the current iterator to the next item. - * @return bool True if there's an item, false if not. - */ - public function next(): bool - { - if (!$this->item) { - return false; - } - - if (!$this->itemIterator) { - // Note: it is important that getIterator() is called before count() as implemenations may rely on - // this to efficiently get both the number of records and an iterator (e.g. DataList does this) - $this->itemIterator = $this->item->getIterator(); - - // This will execute code in a generator up to the first yield. For example, this ensures that - // DataList::getIterator() is called before Datalist::count() which means we only run the query once - // instead of running a separate explicit count() query - $this->itemIterator->rewind(); - - // Get the number of items in the iterator. - // Don't just use iterator_count because that results in running through the list - // which causes some iterators to no longer be iterable for some reason - $this->itemIteratorTotal = $this->item->getIteratorCount(); - - $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator; - $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal; - } else { - $this->itemIterator->next(); - } - - $this->resetLocalScope(); - - if (!$this->itemIterator->valid()) { - return false; - } - - return true; - } - - /** - * Get the value that will be directly rendered in the template. - */ - public function getOutputValue(string $name, array $arguments): string - { - $retval = $this->getObj($name, $arguments); - $this->resetLocalScope(); - return $retval === null ? '' : $retval->__toString(); - } - - /** - * Get the value to pass as an argument to a method. - */ - public function getValueAsArgument(string $name, array $arguments): mixed - { - $retval = null; - - if ($this->hasOverlay($name)) { - $retval = $this->getOverlay($name, $arguments, true); - } else { - $on = $this->getCurrentItem(); - if ($on && isset($on->$name)) { - $retval = $on->getRawDataValue($name, $arguments); - } - - if ($retval === null) { - $retval = $this->getUnderlay($name, $arguments, true); - } - } - - $this->resetLocalScope(); - return $retval; - } - - /** - * Check if the current item in scope has a value for the named field. - */ - public function hasValue(string $name, array $arguments): bool - { - $retval = null; - $overlay = $this->getOverlay($name, $arguments); - if ($overlay && $overlay->hasDataValue()) { - $retval = true; - } - - if ($retval === null) { - $on = $this->getCurrentItem(); - if ($on) { - $retval = $on->hasDataValue($name, $arguments); - } - } - - if (!$retval) { - $underlay = $this->getUnderlay($name, $arguments); - $retval = $underlay && $underlay->hasDataValue(); - } - - $this->resetLocalScope(); - return $retval; - } - - /** - * Reset the local scope - restores saved state to the "global" item stack. Typically called after - * a lookup chain has been completed - */ - protected function resetLocalScope() - { - // Restore previous un-completed lookup chain if set - $previousLocalState = $this->localStack ? array_pop($this->localStack) : null; - array_splice($this->itemStack, $this->localIndex + 1, count($this->itemStack ?? []), $previousLocalState); - - list( - $this->item, - $this->itemIterator, - $this->itemIteratorTotal, - $this->popIndex, - $this->upIndex, - $this->currentIndex - ) = end($this->itemStack); - } - - /** - * @return array - */ - protected function getItemStack() - { - return $this->itemStack; - } - - /** - * @param array $stack - */ - protected function setItemStack(array $stack) - { - $this->itemStack = $stack; - } - - /** - * @return int|null - */ - protected function getUpIndex() - { - return $this->upIndex; - } - - /** - * Evaluate a template override. Returns an array where the presence of - * a 'value' key indiciates whether an override was successfully found, - * as null is a valid override value - * - * @param string $property Name of override requested - * @param array $overrides List of overrides available - * @return array An array with a 'value' key if a value has been found, or empty if not - */ - protected function processTemplateOverride($property, $overrides) - { - if (!array_key_exists($property, $overrides)) { - return []; - } - - // Detect override type - $override = $overrides[$property]; - - // Late-evaluate this value - if (!is_string($override) && is_callable($override)) { - $override = $override(); - - // Late override may yet return null - if (!isset($override)) { - return []; - } - } - - return ['value' => $override]; - } - - /** - * Build cache of global properties - */ - protected function cacheGlobalProperties() - { - if (SSViewer_Scope::$globalProperties !== null) { - return; - } - - SSViewer_Scope::$globalProperties = SSViewer::getMethodsFromProvider( - TemplateGlobalProvider::class, - 'get_template_global_variables' - ); - } - - /** - * Build cache of global iterator properties - */ - protected function cacheIteratorProperties() - { - if (SSViewer_Scope::$iteratorProperties !== null) { - return; - } - - SSViewer_Scope::$iteratorProperties = SSViewer::getMethodsFromProvider( - TemplateIteratorProvider::class, - 'get_template_iterator_variables', - true // Call non-statically - ); - } - - protected function getObj(string $name, array $arguments): ?ViewLayerData - { - if ($this->hasOverlay($name)) { - return $this->getOverlay($name, $arguments); - } - - $on = $this->getCurrentItem(); - if ($on && isset($on->$name)) { - return $on->$name(...$arguments); - } - - return $this->getUnderlay($name, $arguments); - } - - protected function hasOverlay(string $property): bool - { - $result = $this->processTemplateOverride($property, $this->overlay); - return array_key_exists('value', $result); - } - - protected function getOverlay(string $property, array $args, bool $getRaw = false): mixed - { - $result = $this->processTemplateOverride($property, $this->overlay); - if (array_key_exists('value', $result)) { - return $this->getInjectedValue($result, $property, $args, $getRaw); - } - return null; - } - - protected function getUnderlay(string $property, array $args, bool $getRaw = false): mixed - { - // Check for a presenter-specific override - $result = $this->processTemplateOverride($property, $this->underlay); - if (array_key_exists('value', $result)) { - return $this->getInjectedValue($result, $property, $args, $getRaw); - } - - // Then for iterator-specific overrides - if (array_key_exists($property, SSViewer_Scope::$iteratorProperties)) { - $source = SSViewer_Scope::$iteratorProperties[$property]; - /** @var TemplateIteratorProvider $implementor */ - $implementor = $source['implementor']; - if ($this->itemIterator) { - // Set the current iterator position and total (the object instance is the first item in - // the callable array) - $implementor->iteratorProperties( - $this->itemIterator->key(), - $this->itemIteratorTotal - ); - } else { - // If we don't actually have an iterator at the moment, act like a list of length 1 - $implementor->iteratorProperties(0, 1); - } - - return $this->getInjectedValue($source, $property, $args, $getRaw); - } - - // And finally for global overrides - if (array_key_exists($property, SSViewer_Scope::$globalProperties)) { - return $this->getInjectedValue( - SSViewer_Scope::$globalProperties[$property], - $property, - $args, - $getRaw - ); - } - - return null; - } - - protected function getInjectedValue( - array|TemplateGlobalProvider|TemplateIteratorProvider $source, - string $property, - array $params, - bool $getRaw = false - ) { - // Look up the value - either from a callable, or from a directly provided value - $value = null; - if (isset($source['callable'])) { - $value = $source['callable'](...$params); - } elseif (array_key_exists('value', $source)) { - $value = $source['value']; - } else { - throw new InvalidArgumentException( - "Injected property $property doesn't have a value or callable value source provided" - ); - } - - if ($value === null) { - return null; - } - - // TemplateGlobalProviders can provide an explicit service to cast to which works outside of the regular cast flow - if (!$getRaw && isset($source['casting'])) { - $castObject = Injector::inst()->create($source['casting'], $property); - if (!ClassInfo::hasMethod($castObject, 'setValue')) { - throw new LogicException('Explicit cast from template global provider must have a setValue method.'); - } - $castObject->setValue($value); - $value = $castObject; - } - - return $getRaw ? $value : ViewLayerData::create($value); - } -} diff --git a/src/View/TemplateIteratorProvider.php b/src/View/TemplateIteratorProvider.php deleted file mode 100644 index a3169afadd7..00000000000 --- a/src/View/TemplateIteratorProvider.php +++ /dev/null @@ -1,47 +0,0 @@ - value pair is one of three forms: - * - template name (no key) - * - template name => method name - * - template name => array(), where the array can contain these key => value pairs - * - "method" => method name - * - "casting" => casting class to use (i.e., Varchar, HTMLFragment, etc) - */ - public static function get_template_iterator_variables(); - - /** - * Set the current iterator properties - where we are on the iterator. - * - * This is called by SSViewer prior to calling any of the variables exposed to the template (that is, as returned - * from a call to get_template_iterator_variables) - * - * @abstract - * @param int $pos position in iterator - * @param int $totalItems total number of items - */ - public function iteratorProperties($pos, $totalItems); -} diff --git a/src/View/TemplateParser.php b/src/View/TemplateParser.php deleted file mode 100644 index 18245987240..00000000000 --- a/src/View/TemplateParser.php +++ /dev/null @@ -1,20 +0,0 @@ -filters ); diff --git a/tests/php/View/SSTemplateEngineCacheBlockTest.php b/tests/php/View/SSTemplateEngineCacheBlockTest.php deleted file mode 100644 index f1f3d2030d0..00000000000 --- a/tests/php/View/SSTemplateEngineCacheBlockTest.php +++ /dev/null @@ -1,463 +0,0 @@ -data = new SSTemplateEngineCacheBlockTest\TestModel(); - - $cache = null; - if ($cacheOn) { - // cache indefinitely - $cache = new Psr16Cache(new FilesystemAdapter('cacheblock', 0, TempFolder::getTempFolder(BASE_PATH))); - } else { - $cache = new Psr16Cache(new NullAdapter()); - } - - Injector::inst()->registerService($cache, CacheInterface::class . '.cacheblock'); - Injector::inst()->get(CacheInterface::class . '.cacheblock')->clear(); - } - - protected function _runtemplate($template, $data = null) - { - if ($data === null) { - $data = $this->data; - } - if (is_array($data)) { - $data = $this->data->customise($data); - } - - $engine = new SSTemplateEngine(); - return $engine->renderString($template, new ViewLayerData($data)); - } - - public function testParsing() - { - - // ** Trivial checks ** - - // Make sure an empty cached block parses - $this->_reset(); - $this->assertEquals('', $this->_runtemplate('<% cached %><% end_cached %>')); - - // Make sure an empty cacheblock block parses - $this->_reset(); - $this->assertEquals('', $this->_runtemplate('<% cacheblock %><% end_cacheblock %>')); - - // Make sure an empty uncached block parses - $this->_reset(); - $this->assertEquals('', $this->_runtemplate('<% uncached %><% end_uncached %>')); - - // ** Argument checks ** - - // Make sure a simple cacheblock parses - $this->_reset(); - $this->assertEquals('Yay', $this->_runtemplate('<% cached %>Yay<% end_cached %>')); - - // Make sure a moderately complicated cacheblock parses - $this->_reset(); - $this->assertEquals('Yay', $this->_runtemplate('<% cached \'block\', Foo, "jumping" %>Yay<% end_cached %>')); - - // Make sure a complicated cacheblock parses - $this->_reset(); - $this->assertEquals( - 'Yay', - $this->_runtemplate( - '<% cached \'block\', Foo, Test.Test(4).Test(jumping).Foo %>Yay<% end_cached %>' - ) - ); - - // ** Conditional Checks ** - - // Make sure a cacheblock with a simple conditional parses - $this->_reset(); - $this->assertEquals('Yay', $this->_runtemplate('<% cached if true %>Yay<% end_cached %>')); - - // Make sure a cacheblock with a complex conditional parses - $this->_reset(); - $this->assertEquals('Yay', $this->_runtemplate('<% cached if Test.Test(yank).Foo %>Yay<% end_cached %>')); - - // Make sure a cacheblock with a complex conditional and arguments parses - $this->_reset(); - $this->assertEquals( - 'Yay', - $this->_runtemplate( - '<% cached Foo, Test.Test(4).Test(jumping).Foo if Test.Test(yank).Foo %>Yay<% end_cached %>' - ) - ); - } - - /** - * Test that cacheblocks actually cache - */ - public function testBlocksCache() - { - // First, run twice without caching, to prove we get two different values - $this->_reset(false); - - $this->assertEquals('1', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 1])); - $this->assertEquals('2', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 2])); - - // Then twice with caching, should get same result each time - $this->_reset(true); - - $this->assertEquals('1', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 1])); - $this->assertEquals('1', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 2])); - } - - /** - * Test that the cacheblocks invalidate when a flush occurs. - */ - public function testBlocksInvalidateOnFlush() - { - Director::test('/?flush=1'); - $this->_reset(true); - - // Generate cached value for foo = 1 - $this->assertEquals('1', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 1])); - - // Test without flush - Injector::inst()->get(Kernel::class)->boot(); - Director::test('/'); - $this->assertEquals('1', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 3])); - - // Test with flush - Injector::inst()->get(Kernel::class)->boot(true); - Director::test('/?flush=1'); - $this->assertEquals('2', $this->_runtemplate('<% cached %>$Foo<% end_cached %>', ['Foo' => 2])); - } - - public function testVersionedCache() - { - if (!class_exists(Versioned::class)) { - $this->markTestSkipped('testVersionedCache requires Versioned extension'); - } - $origReadingMode = Versioned::get_reading_mode(); - - // Run without caching in stage to prove data is uncached - $this->_reset(false); - Versioned::set_stage(Versioned::DRAFT); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('default'); - $this->assertEquals( - 'default Stage.Stage', - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('first'); - $this->assertEquals( - 'first Stage.Stage', - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - - // Run without caching in live to prove data is uncached - $this->_reset(false); - Versioned::set_stage(Versioned::LIVE); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('default'); - $this->assertEquals( - 'default Stage.Live', - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('first'); - $this->assertEquals( - 'first Stage.Live', - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - - // Then with caching, initially in draft, and then in live, to prove that - // changing the versioned reading mode doesn't cache between modes, but it does - // within them - $this->_reset(true); - Versioned::set_stage(Versioned::DRAFT); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('default'); - $this->assertEquals( - 'default Stage.Stage', - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('first'); - $this->assertEquals( - 'default Stage.Stage', // entropy should be ignored due to caching - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - - Versioned::set_stage(Versioned::LIVE); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('first'); - $this->assertEquals( - 'first Stage.Live', // First hit in live, so display current entropy - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - $data = new SSTemplateEngineCacheBlockTest\VersionedModel(); - $data->setEntropy('second'); - $this->assertEquals( - 'first Stage.Live', // entropy should be ignored due to caching - $this->_runtemplate('<% cached %>$Inspect<% end_cached %>', $data) - ); - - Versioned::set_reading_mode($origReadingMode); - } - - /** - * Test that cacheblocks conditionally cache with if - */ - public function testBlocksConditionallyCacheWithIf() - { - // First, run twice with caching - $this->_reset(true); - - $this->assertEquals('1', $this->_runtemplate('<% cached if True %>$Foo<% end_cached %>', ['Foo' => 1])); - $this->assertEquals('1', $this->_runtemplate('<% cached if True %>$Foo<% end_cached %>', ['Foo' => 2])); - - // Then twice without caching - $this->_reset(true); - - $this->assertEquals('1', $this->_runtemplate('<% cached if False %>$Foo<% end_cached %>', ['Foo' => 1])); - $this->assertEquals('2', $this->_runtemplate('<% cached if False %>$Foo<% end_cached %>', ['Foo' => 2])); - - // Then once cached, once not (and the opposite) - $this->_reset(true); - - $this->assertEquals( - '1', - $this->_runtemplate( - '<% cached if Cache %>$Foo<% end_cached %>', - ['Foo' => 1, 'Cache' => true ] - ) - ); - $this->assertEquals( - '2', - $this->_runtemplate( - '<% cached if Cache %>$Foo<% end_cached %>', - ['Foo' => 2, 'Cache' => false] - ) - ); - - $this->_reset(true); - - $this->assertEquals( - '1', - $this->_runtemplate( - '<% cached if Cache %>$Foo<% end_cached %>', - ['Foo' => 1, 'Cache' => false] - ) - ); - $this->assertEquals( - '2', - $this->_runtemplate( - '<% cached if Cache %>$Foo<% end_cached %>', - ['Foo' => 2, 'Cache' => true ] - ) - ); - } - - /** - * Test that cacheblocks conditionally cache with unless - */ - public function testBlocksConditionallyCacheWithUnless() - { - // First, run twice with caching - $this->_reset(true); - - $this->assertEquals( - $this->_runtemplate( - '<% cached unless False %>$Foo<% end_cached %>', - ['Foo' => 1] - ), - '1' - ); - $this->assertEquals( - $this->_runtemplate( - '<% cached unless False %>$Foo<% end_cached %>', - ['Foo' => 2] - ), - '1' - ); - - // Then twice without caching - $this->_reset(true); - - $this->assertEquals( - $this->_runtemplate( - '<% cached unless True %>$Foo<% end_cached %>', - ['Foo' => 1] - ), - '1' - ); - $this->assertEquals( - $this->_runtemplate( - '<% cached unless True %>$Foo<% end_cached %>', - ['Foo' => 2] - ), - '2' - ); - } - - /** - * Test that nested uncached blocks work - */ - public function testNestedUncachedBlocks() - { - // First, run twice with caching, to prove we get the same result back normally - $this->_reset(true); - - $this->assertEquals( - $this->_runtemplate( - '<% cached %> A $Foo B <% end_cached %>', - ['Foo' => 1] - ), - ' A 1 B ' - ); - $this->assertEquals( - $this->_runtemplate( - '<% cached %> A $Foo B <% end_cached %>', - ['Foo' => 2] - ), - ' A 1 B ' - ); - - // Then add uncached to the nested block - $this->_reset(true); - - $this->assertEquals( - $this->_runtemplate( - '<% cached %> A <% uncached %>$Foo<% end_uncached %> B <% end_cached %>', - ['Foo' => 1] - ), - ' A 1 B ' - ); - $this->assertEquals( - $this->_runtemplate( - '<% cached %> A <% uncached %>$Foo<% end_uncached %> B <% end_cached %>', - ['Foo' => 2] - ), - ' A 2 B ' - ); - } - - /** - * Test that nested blocks with different keys works - */ - public function testNestedBlocks() - { - $this->_reset(true); - - $template = '<% cached Foo %> $Fooa <% cached Bar %>$Bara<% end_cached %> $Foob <% end_cached %>'; - - // Do it the first time to load the cache - $this->assertEquals( - $this->_runtemplate( - $template, - ['Foo' => 1, 'Fooa' => 1, 'Foob' => 3, 'Bar' => 1, 'Bara' => 2] - ), - ' 1 2 3 ' - ); - - // Do it again, the input values are ignored as the cache is hit for both elements - $this->assertEquals( - $this->_runtemplate( - $template, - ['Foo' => 1, 'Fooa' => 9, 'Foob' => 9, 'Bar' => 1, 'Bara' => 9] - ), - ' 1 2 3 ' - ); - - // Do it again with a new key for Bar, Bara is picked up, Fooa and Foob are not - $this->assertEquals( - $this->_runtemplate( - $template, - ['Foo' => 1, 'Fooa' => 9, 'Foob' => 9, 'Bar' => 2, 'Bara' => 9] - ), - ' 1 9 3 ' - ); - - // Do it again with a new key for Foo, Fooa and Foob are picked up, Bara are not - $this->assertEquals( - $this->_runtemplate( - $template, - ['Foo' => 2, 'Fooa' => 9, 'Foob' => 9, 'Bar' => 2, 'Bara' => 1] - ), - ' 9 9 9 ' - ); - } - - public function testNoErrorMessageForControlWithinCached() - { - $this->_reset(true); - $this->assertNotNull($this->_runtemplate('<% cached %><% with Foo %>$Bar<% end_with %><% end_cached %>')); - } - - public function testErrorMessageForCachedWithinControlWithinCached() - { - $this->expectException(SSTemplateParseException::class); - $this->_reset(true); - $this->_runtemplate( - '<% cached %><% with Foo %><% cached %>$Bar<% end_cached %><% end_with %><% end_cached %>' - ); - } - - public function testNoErrorMessageForCachedWithinControlWithinUncached() - { - $this->_reset(true); - $this->assertNotNull( - $this->_runtemplate( - '<% uncached %><% with Foo %><% cached %>$Bar<% end_cached %><% end_with %><% end_uncached %>' - ) - ); - } - - public function testErrorMessageForCachedWithinIf() - { - $this->expectException(SSTemplateParseException::class); - $this->_reset(true); - $this->_runtemplate('<% cached %><% if Foo %><% cached %>$Bar<% end_cached %><% end_if %><% end_cached %>'); - } - - public function testErrorMessageForInvalidConditional() - { - $this->expectException(SSTemplateParseException::class); - $this->_reset(true); - $this->_runtemplate('<% cached Foo if %>$Bar<% end_cached %>'); - } -} diff --git a/tests/php/View/SSTemplateEngineCacheBlockTest/TestModel.php b/tests/php/View/SSTemplateEngineCacheBlockTest/TestModel.php deleted file mode 100644 index 0ee50d59893..00000000000 --- a/tests/php/View/SSTemplateEngineCacheBlockTest/TestModel.php +++ /dev/null @@ -1,31 +0,0 @@ -entropy = $entropy; - } - - public function Inspect() - { - return $this->entropy . ' ' . Versioned::get_reading_mode(); - } -} diff --git a/tests/php/View/SSTemplateEngineFindTemplateTest.php b/tests/php/View/SSTemplateEngineFindTemplateTest.php deleted file mode 100644 index 9fb303f19f7..00000000000 --- a/tests/php/View/SSTemplateEngineFindTemplateTest.php +++ /dev/null @@ -1,320 +0,0 @@ -base = dirname(__FILE__) . '/SSTemplateEngineTest_findTemplate'; - Director::config()->set('alternate_base_folder', $this->base); - ModuleManifest::config()->set('module_priority', ['$project', '$other_modules']); - ModuleManifest::config()->set('project', 'myproject'); - - $moduleManifest = new ModuleManifest($this->base); - $moduleManifest->init(); - $moduleManifest->sort(); - ModuleLoader::inst()->pushManifest($moduleManifest); - - // New ThemeManifest for that root - $themeManifest = new ThemeManifest($this->base); - $themeManifest->setProject('myproject'); - $themeManifest->init(); - // New Loader for that root - $this->origLoader = ThemeResourceLoader::inst(); - $themeResourceLoader = new ThemeResourceLoader($this->base); - $themeResourceLoader->addSet('$default', $themeManifest); - ThemeResourceLoader::set_instance($themeResourceLoader); - - // Ensure the cache is flushed between tests - ThemeResourceLoader::flush(); - } - - protected function tearDown(): void - { - ThemeResourceLoader::set_instance($this->origLoader); - ModuleLoader::inst()->popManifest(); - parent::tearDown(); - } - - /** - * Test that 'main' and 'Layout' templates are loaded from module - */ - public function testFindTemplatesInModule() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $this->assertEquals( - "$base/module/templates/Page.ss", - $reflectionFindTemplate->invoke($engine, 'Page', ['$default']) - ); - - $this->assertEquals( - "$base/module/templates/Layout/Page.ss", - $reflectionFindTemplate->invoke($engine, ['type' => 'Layout', 'Page'], ['$default']) - ); - } - - public function testFindNestedThemeTemplates() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - // Without including the theme this template cannot be found - $this->assertEquals(null, $reflectionFindTemplate->invoke($engine, 'NestedThemePage', ['$default'])); - - // With a nested theme available then it is available - $this->assertEquals( - "{$base}/module/themes/subtheme/templates/NestedThemePage.ss", - $reflectionFindTemplate->invoke( - $engine, - 'NestedThemePage', - [ - 'silverstripe/module:subtheme', - '$default' - ] - ) - ); - - // Can also be found if excluding $default theme - $this->assertEquals( - "{$base}/module/themes/subtheme/templates/NestedThemePage.ss", - $reflectionFindTemplate->invoke( - $engine, - 'NestedThemePage', - [ - 'silverstripe/module:subtheme', - ] - ) - ); - } - - public function testFindTemplateByType() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - // Test that "type" is respected properly - $this->assertEquals( - "{$base}/module/templates/MyNamespace/Layout/MyClass.ss", - $reflectionFindTemplate->invoke( - $engine, - [ - [ - 'type' => 'Layout', - 'MyNamespace/NonExistantTemplate' - ], - [ - 'type' => 'Layout', - 'MyNamespace/MyClass' - ], - 'MyNamespace/MyClass' - ], - [ - 'silverstripe/module:subtheme', - 'theme', - '$default', - ] - ) - ); - - // Non-typed template can be found even if looking for typed theme at a lower priority - $this->assertEquals( - "{$base}/module/templates/MyNamespace/MyClass.ss", - $reflectionFindTemplate->invoke( - $engine, - [ - [ - 'type' => 'Layout', - 'MyNamespace/NonExistantTemplate' - ], - 'MyNamespace/MyClass', - [ - 'type' => 'Layout', - 'MyNamespace/MyClass' - ] - ], - [ - 'silverstripe/module', - 'theme', - '$default', - ] - ) - ); - } - - public function testFindTemplatesByPath() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - // Items given as full paths are returned directly - $this->assertEquals( - "$base/themes/theme/templates/Page.ss", - $reflectionFindTemplate->invoke($engine, "$base/themes/theme/templates/Page.ss", ['theme']) - ); - - $this->assertEquals( - "$base/themes/theme/templates/Page.ss", - $reflectionFindTemplate->invoke( - $engine, - [ - "$base/themes/theme/templates/Page.ss", - "Page" - ], - ['theme'] - ) - ); - - // Ensure checks for file_exists - $this->assertEquals( - "$base/themes/theme/templates/Page.ss", - $reflectionFindTemplate->invoke( - $engine, - [ - "$base/themes/theme/templates/NotAPage.ss", - "$base/themes/theme/templates/Page.ss", - ], - ['theme'] - ) - ); - } - - /** - * Test that 'main' and 'Layout' templates are loaded from set theme - */ - public function testFindTemplatesInTheme() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $this->assertEquals( - "$base/themes/theme/templates/Page.ss", - $reflectionFindTemplate->invoke($engine, 'Page', ['theme']) - ); - - $this->assertEquals( - "$base/themes/theme/templates/Layout/Page.ss", - $reflectionFindTemplate->invoke($engine, ['type' => 'Layout', 'Page'], ['theme']) - ); - } - - /** - * Test that 'main' and 'Layout' templates are loaded from project without a set theme - */ - public function testFindTemplatesInApplication() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $templates = [ - $base . '/myproject/templates/Page.ss', - $base . '/myproject/templates/Layout/Page.ss' - ]; - foreach ($templates as $template) { - file_put_contents($template, ''); - } - - try { - $this->assertEquals( - "$base/myproject/templates/Page.ss", - $reflectionFindTemplate->invoke($engine, 'Page', ['$default']) - ); - - $this->assertEquals( - "$base/myproject/templates/Layout/Page.ss", - $reflectionFindTemplate->invoke($engine, ['type' => 'Layout', 'Page'], ['$default']) - ); - } finally { - foreach ($templates as $template) { - unlink($template); - } - } - } - - /** - * Test that 'main' template is found in theme and 'Layout' is found in module - */ - public function testFindTemplatesMainThemeLayoutModule() - { - $base = ThemeResourceLoader::inst()->getBase(); - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $this->assertEquals( - "$base/themes/theme/templates/CustomThemePage.ss", - $reflectionFindTemplate->invoke($engine, 'CustomThemePage', ['theme', '$default']) - ); - - $this->assertEquals( - "$base/module/templates/Layout/CustomThemePage.ss", - $reflectionFindTemplate->invoke($engine, ['type' => 'Layout', 'CustomThemePage'], ['theme', '$default']) - ); - } - - public function testFindTemplateWithCacheMiss() - { - $mockCache = $this->createMock(CacheInterface::class); - $mockCache->expects($this->once())->method('has')->willReturn(false); - $mockCache->expects($this->never())->method('get'); - $mockCache->expects($this->once())->method('set'); - ThemeResourceLoader::inst()->setCache($mockCache); - - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $reflectionFindTemplate->invoke($engine, 'Page', ['$default']); - } - - public function testFindTemplateWithCacheHit() - { - $mockCache = $this->createMock(CacheInterface::class); - $mockCache->expects($this->once())->method('has')->willReturn(true); - $mockCache->expects($this->never())->method('set'); - $mockCache->expects($this->once())->method('get')->willReturn('mock_template.ss'); - ThemeResourceLoader::inst()->setCache($mockCache); - - $engine = new SSTemplateEngine(); - $reflectionFindTemplate = new ReflectionMethod($engine, 'findTemplate'); - $reflectionFindTemplate->setAccessible(true); - - $result = $reflectionFindTemplate->invoke($engine, 'Page', ['$default']); - $this->assertSame('mock_template.ss', $result); - } -} diff --git a/tests/php/View/SSTemplateEngineTest.php b/tests/php/View/SSTemplateEngineTest.php deleted file mode 100644 index 08554ef31cc..00000000000 --- a/tests/php/View/SSTemplateEngineTest.php +++ /dev/null @@ -1,2279 +0,0 @@ -set('source_file_comments', false); - TestAssetStore::activate('SSTemplateEngineTest'); - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - parent::tearDown(); - } - - /** - * Test that a template without a tag still renders. - */ - public function testTemplateWithoutHeadRenders() - { - $data = new ArrayData([ 'Var' => 'var value' ]); - $engine = new SSTemplateEngine('SSTemplateEngineTestPartialTemplate'); - $result = $engine->render(new ViewLayerData($data)); - $this->assertEquals('Test partial template: var value', trim(preg_replace("//U", '', $result ?? '') ?? '')); - } - - /** - * Ensure global methods aren't executed - */ - public function testTemplateExecution() - { - $data = new ArrayData([ 'Var' => 'phpinfo' ]); - $engine = new SSTemplateEngine('SSTemplateEngineTestPartialTemplate'); - $result = $engine->render(new ViewLayerData($data)); - $this->assertEquals('Test partial template: phpinfo', trim(preg_replace("//U", '', $result ?? '') ?? '')); - } - - public function testIncludeScopeInheritance() - { - $data = $this->getScopeInheritanceTestData(); - $expected = [ - 'Item 1 - First-ODD top:Item 1', - 'Item 2 - EVEN top:Item 2', - 'Item 3 - ODD top:Item 3', - 'Item 4 - EVEN top:Item 4', - 'Item 5 - ODD top:Item 5', - 'Item 6 - Last-EVEN top:Item 6', - ]; - - $engine = new SSTemplateEngine('SSTemplateEngineTestIncludeScopeInheritance'); - $result = $engine->render(new ViewLayerData($data)); - $this->assertExpectedStrings($result, $expected); - - // reset results for the tests that include arguments (the title is passed as an arg) - $expected = [ - 'Item 1 _ Item 1 - First-ODD top:Item 1', - 'Item 2 _ Item 2 - EVEN top:Item 2', - 'Item 3 _ Item 3 - ODD top:Item 3', - 'Item 4 _ Item 4 - EVEN top:Item 4', - 'Item 5 _ Item 5 - ODD top:Item 5', - 'Item 6 _ Item 6 - Last-EVEN top:Item 6', - ]; - - $engine = new SSTemplateEngine('SSTemplateEngineTestIncludeScopeInheritanceWithArgs'); - $result = $engine->render(new ViewLayerData($data)); - $this->assertExpectedStrings($result, $expected); - } - - public function testIncludeTruthyness() - { - $data = new ArrayData([ - 'Title' => 'TruthyTest', - 'Items' => new ArrayList([ - new ArrayData(['Title' => 'Item 1']), - new ArrayData(['Title' => '']), - new ArrayData(['Title' => true]), - new ArrayData(['Title' => false]), - new ArrayData(['Title' => null]), - new ArrayData(['Title' => 0]), - new ArrayData(['Title' => 7]) - ]) - ]); - $engine = new SSTemplateEngine('SSTemplateEngineTestIncludeScopeInheritanceWithArgs'); - $result = $engine->render(new ViewLayerData($data)); - - // We should not end up with empty values appearing as empty - $expected = [ - 'Item 1 _ Item 1 - First-ODD top:Item 1', - 'Untitled - EVEN top:', - '1 _ 1 - ODD top:1', - 'Untitled - EVEN top:', - 'Untitled - ODD top:', - 'Untitled - EVEN top:0', - '7 _ 7 - Last-ODD top:7', - ]; - $this->assertExpectedStrings($result, $expected); - } - - public function testRequirements() - { - /** @var Requirements_Backend|MockObject $requirements */ - $requirements = $this - ->getMockBuilder(Requirements_Backend::class) - ->onlyMethods(['javascript', 'css']) - ->getMock(); - $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js'; - $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js'; - - $requirements->expects($this->once())->method('javascript')->with($jsFile); - $requirements->expects($this->once())->method('css')->with($cssFile); - - $origReq = Requirements::backend(); - Requirements::set_backend($requirements); - $result = $this->render( - "<% require javascript($jsFile) %> - <% require css($cssFile) %>" - ); - Requirements::set_backend($origReq); - - // Injecting the actual requirements is the responsibility of SSViewer, so we shouldn't see it in the result - $this->assertFalse((bool)trim($result), 'Should be no content in this return.'); - } - - public function testRequireCallInTemplateInclude() - { - /** @var Requirements_Backend|MockObject $requirements */ - $requirements = $this - ->getMockBuilder(Requirements_Backend::class) - ->onlyMethods(['themedJavascript', 'css']) - ->getMock(); - - $requirements->expects($this->once())->method('themedJavascript')->with('RequirementsTest_a'); - $requirements->expects($this->never())->method('css'); - - $engine = new SSTemplateEngine('SSTemplateEngineTestProcess'); - $origReq = Requirements::backend(); - Requirements::set_backend($requirements); - Requirements::set_suffix_requirements(false); - $result = $engine->render(new ViewLayerData([])); - Requirements::set_backend($origReq); - - // Injecting the actual requirements is the responsibility of SSViewer, so we shouldn't see it in the result - $this->assertEqualIgnoringWhitespace('', $result); - } - - public function testComments() - { - $input = <<This is some content<%-- this is another comment --%>Final content - <%-- Alone multi - line comment --%> - Some more content - Mixing content and <%-- multi - line comment --%> Final final - content - <%--commentwithoutwhitespace--%>last content - SS; - $actual = $this->render($input); - $expected = <<assertEquals($expected, $actual); - - $input = <<empty comment1 - <%-- --%>empty comment2 - <%----%>empty comment3 - SS; - $actual = $this->render($input); - $expected = <<assertEquals($expected, $actual); - } - - public function testBasicText() - { - $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone'); - $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone'); - $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped'); - $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped'); - } - - public function testBasicInjection() - { - $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection'); - $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection'); - $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection'); - $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection'); - - $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped'); - $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character'); - $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character'); - - $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection'); - $this->assertEquals( - '{\\[out:Test]}', - $this->render('{\\\\$Test}'), - 'Escapes before injections are correctly unescaped' - ); - } - - public function testBasicInjectionMismatchedBrackets() - { - $this->expectException(SSTemplateParseException::class); - $this->expectExceptionMessageMatches('/Malformed bracket injection {\$Value(.*)/'); - $this->render('A {$Value here'); - $this->fail("Parser didn't error when encountering mismatched brackets in an injection"); - } - - public function testGlobalVariableCalls() - { - $this->assertEquals('automatic', $this->render('$SSTemplateEngineTest_GlobalAutomatic')); - $this->assertEquals('reference', $this->render('$SSTemplateEngineTest_GlobalReferencedByString')); - $this->assertEquals('reference', $this->render('$SSTemplateEngineTest_GlobalReferencedInArray')); - } - - public function testGlobalVariableCallsWithArguments() - { - $this->assertEquals('zz', $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments')); - $this->assertEquals('zFooz', $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments("Foo")')); - $this->assertEquals( - 'zFoo:Bar:Bazz', - $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")') - ); - $this->assertEquals( - 'zreferencez', - $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments($SSTemplateEngineTest_GlobalReferencedByString)') - ); - } - - public function testGlobalVariablesAreEscaped() - { - $this->assertEquals('
', $this->render('$SSTemplateEngineTest_GlobalHTMLFragment')); - $this->assertEquals('<div></div>', $this->render('$SSTemplateEngineTest_GlobalHTMLEscaped')); - - $this->assertEquals( - 'z
z', - $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments($SSTemplateEngineTest_GlobalHTMLFragment)') - ); - // Don't escape value when passing into a method call - $this->assertEquals( - 'z
z', - $this->render('$SSTemplateEngineTest_GlobalThatTakesArguments($SSTemplateEngineTest_GlobalHTMLEscaped)') - ); - } - - public function testGlobalVariablesReturnNull() - { - $this->assertEquals('

', $this->render('

$SSTemplateEngineTest_GlobalReturnsNull

')); - $this->assertEquals('

', $this->render('

$SSTemplateEngineTest_GlobalReturnsNull.Chained.Properties

')); - } - - public function testCoreGlobalVariableCalls() - { - $this->assertEquals( - Director::absoluteBaseURL(), - $this->render('{$absoluteBaseURL}'), - 'Director::absoluteBaseURL can be called from within template' - ); - $this->assertEquals( - Director::absoluteBaseURL(), - $this->render('{$AbsoluteBaseURL}'), - 'Upper-case %AbsoluteBaseURL can be called from within template' - ); - - $this->assertEquals( - Director::is_ajax(), - $this->render('{$isAjax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$IsAjax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$is_ajax}'), - 'All variations of is_ajax result in the correct call' - ); - $this->assertEquals( - Director::is_ajax(), - $this->render('{$Is_ajax}'), - 'All variations of is_ajax result in the correct call' - ); - - $this->assertEquals( - i18n::get_locale(), - $this->render('{$i18nLocale}'), - 'i18n template functions result correct result' - ); - $this->assertEquals( - i18n::get_locale(), - $this->render('{$get_locale}'), - 'i18n template functions result correct result' - ); - - $this->assertEquals( - Security::getCurrentUser()->ID, - $this->render('{$CurrentMember.ID}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - Security::getCurrentUser()->ID, - $this->render('{$CurrentUser.ID}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - Security::getCurrentUser()->ID, - $this->render('{$currentMember.ID}'), - 'Member template functions result correct result' - ); - $this->assertEquals( - Security::getCurrentUser()->ID, - $this->render('{$currentUser.ID}'), - 'Member template functions result correct result' - ); - - $this->assertEquals( - SecurityToken::getSecurityID(), - $this->render('{$getSecurityID}'), - 'SecurityToken template functions result correct result' - ); - $this->assertEquals( - SecurityToken::getSecurityID(), - $this->render('{$SecurityID}'), - 'SecurityToken template functions result correct result' - ); - - $this->assertEquals( - Permission::check("ADMIN"), - (bool)$this->render('{$HasPerm(\'ADMIN\')}'), - 'Permissions template functions result correct result' - ); - $this->assertEquals( - Permission::check("ADMIN"), - (bool)$this->render('{$hasPerm(\'ADMIN\')}'), - 'Permissions template functions result correct result' - ); - } - - public function testNonFieldCastingHelpersNotUsedInHasValue() - { - // check if Link without $ in front of variable - $result = $this->render( - 'A<% if Link %>$Link<% end_if %>B', - new SSTemplateEngineTest\TestObject() - ); - $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>'); - - // check if Link with $ in front of variable - $result = $this->render( - 'A<% if $Link %>$Link<% end_if %>B', - new SSTemplateEngineTest\TestObject() - ); - $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>'); - } - - public function testLocalFunctionsTakePriorityOverGlobals() - { - $data = new ArrayData([ - 'Page' => new SSTemplateEngineTest\TestObject() - ]); - - //call method with lots of arguments - $result = $this->render( - '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>', - $data - ); - $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments"); - - //call method that does not exist - $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data); - $this->assertEquals("", $result, "Method does not exist - empty result"); - - //call if that does not exist - $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data); - $this->assertEquals("", $result, "Method does not exist - empty result"); - - //call method with same name as a global method (local call should take priority) - $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data); - $this->assertEquals( - "testLocalFunctionPriorityCalled", - $result, - "Local Object's public function called. Did not return the actual baseURL of the current site" - ); - } - - public function testCurrentScopeLoop(): void - { - $data = new ArrayList([['Val' => 'one'], ['Val' => 'two'], ['Val' => 'three']]); - $this->assertEqualIgnoringWhitespace( - 'one two three', - $this->render('<% loop %>$Val<% end_loop %>', $data) - ); - } - - public function testCurrentScopeLoopWith() - { - // Data to run the loop tests on - one sequence of three items, each with a subitem - $data = new ArrayData([ - 'Foo' => new ArrayList([ - 'Subocean' => new ArrayData([ - 'Name' => 'Higher' - ]), - new ArrayData([ - 'Sub' => new ArrayData([ - 'Name' => 'SubKid1' - ]) - ]), - new ArrayData([ - 'Sub' => new ArrayData([ - 'Name' => 'SubKid2' - ]) - ]), - new SSTemplateEngineTest\TestObject('Number6') - ]) - ]); - - $result = $this->render( - '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); - - $result = $this->render( - '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works"); - - $result = $this->render('<% with Foo %>$Count<% end_with %>', $data); - $this->assertEquals("4", $result, "4 items in the DataObjectSet"); - - $result = $this->render( - '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' - . '<% end_if %><% end_loop %><% end_with %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works"); - - $result = $this->render( - '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>' - . '<% end_if %><% end_loop %><% end_with %>', - $data - ); - $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works"); - } - - public static function provideArgumentTypes() - { - return [ - [ - 'arg0:0,arg1:"string",arg2:true', - '$methodWithTypedArguments(0, "string", true).RAW', - ], - [ - 'arg0:false,arg1:"string",arg2:true', - '$methodWithTypedArguments(false, "string", true).RAW', - ], - [ - 'arg0:null,arg1:"string",arg2:true', - '$methodWithTypedArguments(null, "string", true).RAW', - ], - [ - 'arg0:"",arg1:"string",arg2:true', - '$methodWithTypedArguments("", "string", true).RAW', - ], - [ - 'arg0:0,arg1:1,arg2:2', - '$methodWithTypedArguments(0, 1, 2).RAW', - ], - ]; - } - - #[DataProvider('provideArgumentTypes')] - public function testArgumentTypes(string $expected, string $template) - { - $this->assertEquals($expected, $this->render($template, new TestModelData())); - } - - public static function provideEvaluatedArgumentTypes(): array - { - $stdobj = new stdClass(); - $stdobj->key = 'value'; - $scenarios = [ - 'null value' => [ - 'data' => ['Value' => null], - 'useOverlay' => true, - 'expected' => 'arg0:null', - ], - 'int value' => [ - 'data' => ['Value' => 1], - 'useOverlay' => true, - 'expected' => 'arg0:1', - ], - 'string value' => [ - 'data' => ['Value' => '1'], - 'useOverlay' => true, - 'expected' => 'arg0:"1"', - ], - 'boolean true' => [ - 'data' => ['Value' => true], - 'useOverlay' => true, - 'expected' => 'arg0:true', - ], - 'boolean false' => [ - 'data' => ['Value' => false], - 'useOverlay' => true, - 'expected' => 'arg0:false', - ], - 'object value' => [ - 'data' => ['Value' => $stdobj], - 'useOverlay' => true, - 'expected' => 'arg0:{"key":"value"}', - ], - ]; - foreach ($scenarios as $key => $scenario) { - $scenario['useOverlay'] = false; - $scenarios[$key . ' no overlay'] = $scenario; - } - return $scenarios; - } - - #[DataProvider('provideEvaluatedArgumentTypes')] - public function testEvaluatedArgumentTypes(array $data, bool $useOverlay, string $expected): void - { - $template = '$methodWithTypedArguments($Value).RAW'; - $model = new TestModelData(); - $overlay = $data; - if (!$useOverlay) { - $model = $model->customise($data); - $overlay = []; - } - $this->assertEquals($expected, $this->render($template, $model, $overlay)); - } - - public function testObjectDotArguments() - { - $this->assertEquals( - '[out:TestObject.methodWithOneArgument(one)] - [out:TestObject.methodWithTwoArguments(one,two)] - [out:TestMethod(Arg1,Arg2).Bar.Val] - [out:TestMethod(Arg1,Arg2).Bar] - [out:TestMethod(Arg1,Arg2)] - [out:TestMethod(Arg1).Bar.Val] - [out:TestMethod(Arg1).Bar] - [out:TestMethod(Arg1)]', - $this->render( - '$TestObject.methodWithOneArgument(one) - $TestObject.methodWithTwoArguments(one,two) - $TestMethod(Arg1, Arg2).Bar.Val - $TestMethod(Arg1, Arg2).Bar - $TestMethod(Arg1, Arg2) - $TestMethod(Arg1).Bar.Val - $TestMethod(Arg1).Bar - $TestMethod(Arg1)' - ) - ); - } - - public function testEscapedArguments() - { - $this->assertEquals( - '[out:Foo(Arg1,Arg2).Bar.Val].Suffix - [out:Foo(Arg1,Arg2).Val]_Suffix - [out:Foo(Arg1,Arg2)]/Suffix - [out:Foo(Arg1).Bar.Val]textSuffix - [out:Foo(Arg1).Bar].Suffix - [out:Foo(Arg1)].Suffix - [out:Foo.Bar.Val].Suffix - [out:Foo.Bar].Suffix - [out:Foo].Suffix', - $this->render( - '{$Foo(Arg1, Arg2).Bar.Val}.Suffix - {$Foo(Arg1, Arg2).Val}_Suffix - {$Foo(Arg1, Arg2)}/Suffix - {$Foo(Arg1).Bar.Val}textSuffix - {$Foo(Arg1).Bar}.Suffix - {$Foo(Arg1)}.Suffix - {$Foo.Bar.Val}.Suffix - {$Foo.Bar}.Suffix - {$Foo}.Suffix' - ) - ); - } - - public function testLoopWhitespace() - { - $data = new ArrayList([new SSTemplateEngineTest\TestFixture()]); - $this->assertEquals( - 'before[out:Test]after - beforeTestafter', - $this->render( - 'before<% loop %>$Test<% end_loop %>after - before<% loop %>Test<% end_loop %>after', - $data - ) - ); - - // The control tags are removed from the output, but no whitespace - // This is a quirk that could be changed, but included in the test to make the current - // behaviour explicit - $this->assertEquals( - 'before - -[out:ItemOnItsOwnLine] - -after', - $this->render( - 'before -<% loop %> -$ItemOnItsOwnLine -<% end_loop %> -after', - $data - ) - ); - - // The whitespace within the control tags is preserve in a loop - // This is a quirk that could be changed, but included in the test to make the current - // behaviour explicit - $this->assertEquals( - 'before - -[out:Loop3.ItemOnItsOwnLine] - -[out:Loop3.ItemOnItsOwnLine] - -[out:Loop3.ItemOnItsOwnLine] - -after', - $this->render( - 'before -<% loop Loop3 %> -$ItemOnItsOwnLine -<% end_loop %> -after' - ) - ); - } - - public static function typePreservationDataProvider() - { - return [ - // Null - ['NULL:', 'null'], - ['NULL:', 'NULL'], - // Booleans - ['boolean:1', 'true'], - ['boolean:1', 'TRUE'], - ['boolean:', 'false'], - ['boolean:', 'FALSE'], - // Strings which may look like booleans/null to the parser - ['string:nullish', 'nullish'], - ['string:notnull', 'notnull'], - ['string:truethy', 'truethy'], - ['string:untrue', 'untrue'], - ['string:falsey', 'falsey'], - // Integers - ['integer:0', '0'], - ['integer:1', '1'], - ['integer:15', '15'], - ['integer:-15', '-15'], - // Octal integers - ['integer:83', '0123'], - ['integer:-83', '-0123'], - // Hexadecimal integers - ['integer:26', '0x1A'], - ['integer:-26', '-0x1A'], - // Binary integers - ['integer:255', '0b11111111'], - ['integer:-255', '-0b11111111'], - // Floats (aka doubles) - ['double:0', '0.0'], - ['double:1', '1.0'], - ['double:15.25', '15.25'], - ['double:-15.25', '-15.25'], - ['double:1200', '1.2e3'], - ['double:-1200', '-1.2e3'], - ['double:0.07', '7E-2'], - ['double:-0.07', '-7E-2'], - // Explicitly quoted strings - ['string:0', '"0"'], - ['string:1', '\'1\''], - ['string:foobar', '"foobar"'], - ['string:foo bar baz', '"foo bar baz"'], - ['string:false', '\'false\''], - ['string:true', '\'true\''], - ['string:null', '\'null\''], - ['string:false', '"false"'], - ['string:true', '"true"'], - ['string:null', '"null"'], - // Implicit strings - ['string:foobar', 'foobar'], - ['string:foo bar baz', 'foo bar baz'] - ]; - } - - #[DataProvider('typePreservationDataProvider')] - public function testTypesArePreserved($expected, $templateArg) - { - $data = new ArrayData([ - 'Test' => new TestModelData() - ]); - - $this->assertEquals($expected, $this->render("\$Test.Type({$templateArg})", $data)); - } - - #[DataProvider('typePreservationDataProvider')] - public function testTypesArePreservedAsIncludeArguments($expected, $templateArg) - { - $data = new ArrayData([ - 'Test' => new TestModelData() - ]); - - $this->assertEquals( - $expected, - $this->render("<% include SSTemplateEngineTestTypePreservation Argument={$templateArg} %>", $data) - ); - } - - public function testTypePreservationInConditionals() - { - $data = new ArrayData([ - 'Test' => new TestModelData() - ]); - - // Types in conditionals - $this->assertEquals('pass', $this->render('<% if true %>pass<% else %>fail<% end_if %>', $data)); - $this->assertEquals('pass', $this->render('<% if false %>fail<% else %>pass<% end_if %>', $data)); - $this->assertEquals('pass', $this->render('<% if 1 %>pass<% else %>fail<% end_if %>', $data)); - $this->assertEquals('pass', $this->render('<% if 0 %>fail<% else %>pass<% end_if %>', $data)); - } - - public function testControls() - { - // Single item controls - $this->assertEquals( - 'a[out:Foo.Bar.Item]b - [out:Foo.Bar(Arg1).Item] - [out:Foo(Arg1).Item] - [out:Foo(Arg1,Arg2).Item] - [out:Foo(Arg1,Arg2,Arg3).Item]', - $this->render( - '<% with Foo.Bar %>a{$Item}b<% end_with %> - <% with Foo.Bar(Arg1) %>$Item<% end_with %> - <% with Foo(Arg1) %>$Item<% end_with %> - <% with Foo(Arg1, Arg2) %>$Item<% end_with %> - <% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>' - ) - ); - - // Loop controls - $this->assertEquals( - 'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b', - $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>') - ); - - $this->assertEquals( - '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]', - $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>') - ); - - $this->assertEquals( - '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]', - $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>') - ); - - $this->assertEquals( - '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]', - $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>') - ); - - $this->assertEquals( - '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]', - $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>') - ); - } - - public function testIfBlocks() - { - // Basic test - $this->assertEquals( - 'AC', - $this->render('A<% if NotSet %>B$NotSet<% end_if %>C') - ); - - // Nested test - $this->assertEquals( - 'AB1C', - $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C') - ); - - // else_if - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D') - ); - $this->assertEquals( - 'ADE', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') - ); - - $this->assertEquals( - 'ADE', - $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E') - ); - - // Dot syntax - $this->assertEquals( - 'ACD', - $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D') - ); - - // Params - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D') - ); - - // Negation - $this->assertEquals( - 'AC', - $this->render('A<% if not IsSet %>B<% end_if %>C') - ); - $this->assertEquals( - 'ABC', - $this->render('A<% if not NotSet %>B<% end_if %>C') - ); - - // Or - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D') - ); - - // Negated Or - $this->assertEquals( - 'ACD', - $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABD', - $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D') - ); - - // And - $this->assertEquals( - 'ABD', - $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D') - ); - - // Equality - $this->assertEquals( - 'ABC', - $this->render('A<% if RawVal == RawVal %>B<% end_if %>C') - ); - $this->assertEquals( - 'ACD', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D') - ); - $this->assertEquals( - 'ABC', - $this->render('A<% if Right != Wrong %>B<% end_if %>C') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D') - ); - - // test inequalities with simple numbers - $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D')); - - $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D')); - - $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D')); - $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D')); - - // empty else_if and else tags, if this would not be supported, - // the output would stop after A, thereby failing the assert - $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D')); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D') - ); - $this->assertEquals( - 'AD', - $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D') - ); - - // Bare words with ending space - $this->assertEquals( - 'ABC', - $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C') - ); - - // Else - $this->assertEquals( - 'ADE', - $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E') - ); - - // Empty if with else - $this->assertEquals( - 'ABC', - $this->render('A<% if NotSet %><% else %>B<% end_if %>C') - ); - } - - public static function provideIfBlockWithIterable(): array - { - $scenarios = [ - 'empty array' => [ - 'iterable' => [], - 'inScope' => false, - ], - 'array' => [ - 'iterable' => [1, 2, 3], - 'inScope' => false, - ], - 'ArrayList' => [ - 'iterable' => new ArrayList([['Val' => 1], ['Val' => 2], ['Val' => 3]]), - 'inScope' => false, - ], - ]; - foreach ($scenarios as $name => $scenario) { - $scenario['inScope'] = true; - $scenarios[$name . ' in scope'] = $scenario; - } - return $scenarios; - } - - #[DataProvider('provideIfBlockWithIterable')] - public function testIfBlockWithIterable(iterable $iterable, bool $inScope): void - { - $expected = count($iterable) ? 'has value' : 'no value'; - $data = new ArrayData(['Iterable' => $iterable]); - if ($inScope) { - $template = '<% with $Iterable %><% if $Me %>has value<% else %>no value<% end_if %><% end_with %>'; - } else { - $template = '<% if $Iterable %>has value<% else %>no value<% end_if %>'; - } - $this->assertEqualIgnoringWhitespace($expected, $this->render($template, $data)); - } - - public function testBaseTagGeneration() - { - // XHTML will have a closed base tag - $tmpl1 = ' - - - <% base_tag %> -

test

- '; - $this->assertMatchesRegularExpression('/<\/head>/', $this->render($tmpl1)); - - // HTML4 and 5 will only have it for IE - $tmpl2 = ' - - <% base_tag %> -

test

- '; - $this->assertMatchesRegularExpression( - '/<\/head>/', - $this->render($tmpl2) - ); - - - $tmpl3 = ' - - <% base_tag %> -

test

- '; - $this->assertMatchesRegularExpression( - '/<\/head>/', - $this->render($tmpl3) - ); - - // Check that the content negotiator converts to the equally legal formats - $negotiator = new ContentNegotiator(); - - $response = new HTTPResponse($this->render($tmpl1)); - $negotiator->html($response); - $this->assertMatchesRegularExpression( - '/<\/head>/', - $response->getBody() - ); - - $response = new HTTPResponse($this->render($tmpl1)); - $negotiator->xhtml($response); - $this->assertMatchesRegularExpression('/<\/head>/', $response->getBody()); - } - - public function testIncludeWithArguments() - { - $this->assertEquals( - '

[out:Arg1]

[out:Arg2]

[out:Arg2.Count]

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments %>') - ); - - $this->assertEquals( - '

A

[out:Arg2]

[out:Arg2.Count]

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1=A %>') - ); - - $this->assertEquals( - '

A

B

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1=A, Arg2=B %>') - ); - - $this->assertEquals( - '

A Bare String

B Bare String

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>') - ); - - $this->assertEquals( - '

A

Bar

', - $this->render( - '<% include SSTemplateEngineTestIncludeWithArguments Arg1="A", Arg2=$B %>', - new ArrayData(['B' => 'Bar']) - ) - ); - - $this->assertEquals( - '

A

Bar

', - $this->render( - '<% include SSTemplateEngineTestIncludeWithArguments Arg1="A" %>', - new ArrayData(['Arg1' => 'Foo', 'Arg2' => 'Bar']) - ) - ); - - $this->assertEquals( - '

A

0

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1="A", Arg2=0 %>') - ); - - $this->assertEquals( - '

A

', - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1="A", Arg2=false %>') - ); - - $this->assertEquals( - '

A

', - // Note Arg2 is explicitly overridden with null - $this->render('<% include SSTemplateEngineTestIncludeWithArguments Arg1="A", Arg2=null %>') - ); - - $this->assertEquals( - 'SomeArg - Foo - Bar - SomeArg', - $this->render( - '<% include SSTemplateEngineTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>', - new ArrayData( - ['Items' => new ArrayList( - [ - new ArrayData(['Title' => 'Foo']), - new ArrayData(['Title' => 'Bar']) - ] - )] - ) - ) - ); - - $this->assertEquals( - 'A - B - A', - $this->render( - '<% include SSTemplateEngineTestIncludeScopeInheritanceWithArgsInWith Title="A" %>', - new ArrayData(['Item' => new ArrayData(['Title' =>'B'])]) - ) - ); - - $this->assertEquals( - 'A - B - C - B - A', - $this->render( - '<% include SSTemplateEngineTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>', - new ArrayData( - [ - 'Item' => new ArrayData( - [ - 'Title' =>'B', 'NestedItem' => new ArrayData(['Title' => 'C']) - ] - )] - ) - ) - ); - - $this->assertEquals( - 'A - A - A', - $this->render( - '<% include SSTemplateEngineTestIncludeScopeInheritanceWithUpAndTop Title="A" %>', - new ArrayData( - [ - 'Item' => new ArrayData( - [ - 'Title' =>'B', 'NestedItem' => new ArrayData(['Title' => 'C']) - ] - )] - ) - ) - ); - - $data = new ArrayData( - [ - 'Nested' => new ArrayData( - [ - 'Object' => new ArrayData(['Key' => 'A']) - ] - ), - 'Object' => new ArrayData(['Key' => 'B']) - ] - ); - - $res = $this->render('<% include SSTemplateEngineTestIncludeObjectArguments A=$Nested.Object, B=$Object %>', $data); - $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments'); - } - - public function testNamespaceInclude() - { - $data = new ArrayData([]); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data), - 'Backslashes work for namespace references in includes' - ); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include Namespace\\NamespaceInclude %> )', $data), - 'Escaped backslashes work for namespace references in includes' - ); - - $this->assertEquals( - "tests:( NamespaceInclude\n )", - $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data), - 'Forward slashes work for namespace references in includes' - ); - } - - /** - * Test search for includes fallback to non-includes folder - */ - public function testIncludeFallbacks() - { - $data = new ArrayData([]); - - $this->assertEquals( - "tests:( Namespace/Includes/IncludedTwice.ss\n )", - $this->render('tests:( <% include Namespace\\IncludedTwice %> )', $data), - 'Prefer Includes in the Includes folder' - ); - - $this->assertEquals( - "tests:( Namespace/Includes/IncludedOnceSub.ss\n )", - $this->render('tests:( <% include Namespace\\IncludedOnceSub %> )', $data), - 'Includes in only Includes folder can be found' - ); - - $this->assertEquals( - "tests:( Namespace/IncludedOnceBase.ss\n )", - $this->render('tests:( <% include Namespace\\IncludedOnceBase %> )', $data), - 'Includes outside of Includes folder can be found' - ); - } - - public function testRecursiveInclude() - { - $data = new ArrayData( - [ - 'Title' => 'A', - 'Children' => new ArrayList( - [ - new ArrayData( - [ - 'Title' => 'A1', - 'Children' => new ArrayList( - [ - new ArrayData([ 'Title' => 'A1 i', ]), - new ArrayData([ 'Title' => 'A1 ii', ]), - ] - ), - ] - ), - new ArrayData([ 'Title' => 'A2', ]), - new ArrayData([ 'Title' => 'A3', ]), - ] - ), - ] - ); - - $engine = new SSTemplateEngine('Includes/SSTemplateEngineTestRecursiveInclude'); - $result = $engine->render(new ViewLayerData($data)); - // We don't care about whitespace - $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result ?? '') ?? ''); - - $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult); - } - - /** - * See {@link ModelDataTest} for more extensive casting tests, - * this test just ensures that basic casting is correctly applied during template parsing. - */ - public function testCastingHelpers() - { - $vd = new SSTemplateEngineTest\TestModelData(); - $vd->TextValue = 'html'; - $vd->HTMLValue = 'html'; - $vd->UncastedValue = 'html'; - - // Value casted as "Text" - $this->assertEquals( - '<b>html</b>', - $this->render('$TextValue', $vd) - ); - $this->assertEquals( - 'html', - $this->render('$TextValue.RAW', $vd) - ); - $this->assertEquals( - '<b>html</b>', - $this->render('$TextValue.XML', $vd) - ); - - // Value casted as "HTMLText" - $this->assertEquals( - 'html', - $this->render('$HTMLValue', $vd) - ); - $this->assertEquals( - 'html', - $this->render('$HTMLValue.RAW', $vd) - ); - $this->assertEquals( - '<b>html</b>', - $this->render('$HTMLValue.XML', $vd) - ); - - // Uncasted value (falls back to the relevant DBField class for the data type) - $vd = new SSTemplateEngineTest\TestModelData(); - $vd->UncastedValue = 'html'; - $this->assertEquals( - '<b>html</b>', - $this->render('$UncastedValue', $vd) - ); - $this->assertEquals( - 'html', - $this->render('$UncastedValue.RAW', $vd) - ); - $this->assertEquals( - '<b>html</b>', - $this->render('$UncastedValue.XML', $vd) - ); - } - - public static function provideLoop(): array - { - return [ - 'nested array and iterator' => [ - 'iterable' => [ - [ - 'value 1', - 'value 2', - ], - new ArrayList([ - 'value 3', - 'value 4', - ]), - ], - 'template' => '<% loop $Iterable %><% loop $Me %>$Me<% end_loop %><% end_loop %>', - 'expected' => 'value 1 value 2 value 3 value 4', - ], - 'nested associative arrays' => [ - 'iterable' => [ - [ - 'Foo' => 'one', - ], - [ - 'Foo' => 'two', - ], - [ - 'Foo' => 'three', - ], - ], - 'template' => '<% loop $Iterable %>$Foo<% end_loop %>', - 'expected' => 'one two three', - ], - ]; - } - - #[DataProvider('provideLoop')] - public function testLoop(iterable $iterable, string $template, string $expected): void - { - $data = new ArrayData(['Iterable' => $iterable]); - $this->assertEqualIgnoringWhitespace($expected, $this->render($template, $data)); - } - - public static function provideCountIterable(): array - { - $scenarios = [ - 'empty array' => [ - 'iterable' => [], - 'inScope' => false, - ], - 'array' => [ - 'iterable' => [1, 2, 3], - 'inScope' => false, - ], - 'ArrayList' => [ - 'iterable' => new ArrayList([['Val' => 1], ['Val' => 2], ['Val' => 3]]), - 'inScope' => false, - ], - ]; - foreach ($scenarios as $name => $scenario) { - $scenario['inScope'] = true; - $scenarios[$name . ' in scope'] = $scenario; - } - return $scenarios; - } - - #[DataProvider('provideCountIterable')] - public function testCountIterable(iterable $iterable, bool $inScope): void - { - $expected = count($iterable); - $data = new ArrayData(['Iterable' => $iterable]); - if ($inScope) { - $template = '<% with $Iterable %>$Count<% end_with %>'; - } else { - $template = '$Iterable.Count'; - } - $this->assertEqualIgnoringWhitespace($expected, $this->render($template, $data)); - } - - public function testSSViewerBasicIteratorSupport() - { - $data = new ArrayData( - [ - 'Set' => new ArrayList( - [ - new SSTemplateEngineTest\TestObject("1"), - new SSTemplateEngineTest\TestObject("2"), - new SSTemplateEngineTest\TestObject("3"), - new SSTemplateEngineTest\TestObject("4"), - new SSTemplateEngineTest\TestObject("5"), - new SSTemplateEngineTest\TestObject("6"), - new SSTemplateEngineTest\TestObject("7"), - new SSTemplateEngineTest\TestObject("8"), - new SSTemplateEngineTest\TestObject("9"), - new SSTemplateEngineTest\TestObject("10"), - ] - ) - ] - ); - - //base test - $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data); - $this->assertEquals("12345678910", $result, "Numbers rendered in order"); - - //test First - $result = $this->render('<% loop Set %><% if $IsFirst %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("1", $result, "Only the first number is rendered"); - - //test Last - $result = $this->render('<% loop Set %><% if $IsLast %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("10", $result, "Only the last number is rendered"); - - //test Even - $result = $this->render('<% loop Set %><% if $Even() %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); - - //test Even with quotes - $result = $this->render('<% loop Set %><% if $Even("1") %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); - - //test Even without quotes - $result = $this->render('<% loop Set %><% if $Even(1) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("246810", $result, "Even numbers rendered in order"); - - //test Even with zero-based start index - $result = $this->render('<% loop Set %><% if $Even("0") %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order"); - - //test Odd - $result = $this->render('<% loop Set %><% if $Odd %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("13579", $result, "Odd numbers rendered in order"); - - //test FirstLast - $result = $this->render('<% loop Set %><% if $FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data); - $this->assertEquals("1first10last", $result, "First and last numbers rendered in order"); - - //test Middle - $result = $this->render('<% loop Set %><% if $Middle %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("23456789", $result, "Middle numbers rendered in order"); - - //test MiddleString - $result = $this->render( - '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>' - . '<% end_loop %>', - $data - ); - $this->assertEquals( - "2middle3middle4middle5middle6middle7middle8middle9middle", - $result, - "Middle numbers rendered in order" - ); - - //test EvenOdd - $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data); - $this->assertEquals( - "oddevenoddevenoddevenoddevenoddeven", - $result, - "Even and Odd is returned in sequence numbers rendered in order" - ); - - //test Pos - $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data); - $this->assertEquals("12345678910", $result, '$Pos is rendered in order'); - - //test Pos - $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data); - $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order'); - - //test FromEnd - $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data); - $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order'); - - //test FromEnd - $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data); - $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order'); - - //test Total - $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data); - $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned"); - - //test Modulus - $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data); - $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order"); - - //test MultipleOf 3 - $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned"); - - //test MultipleOf 4 - $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned"); - - //test MultipleOf 5 - $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned"); - - //test MultipleOf 10 - $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("10", $result, "Only numbers that are multiples of 10 (with 1-based indexing) are returned"); - - //test MultipleOf 9 zero-based - $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals( - "110", - $result, - "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)" - ); - - //test MultipleOf 11 - $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data); - $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned"); - } - - /** - * Test $Up works when the scope $Up refers to was entered with a "with" block - */ - public function testUpInWith() - { - - // Data to run the loop tests on - three levels deep - $data = new ArrayData( - [ - 'Name' => 'Top', - 'Foo' => new ArrayData( - [ - 'Name' => 'Foo', - 'Bar' => new ArrayData( - [ - 'Name' => 'Bar', - 'Baz' => new ArrayData( - [ - 'Name' => 'Baz' - ] - ), - 'Qux' => new ArrayData( - [ - 'Name' => 'Qux' - ] - ) - ] - ) - ] - ) - ] - ); - - // Basic functionality - $this->assertEquals( - 'BarFoo', - $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data) - ); - - // Two level with block, up refers to internally referenced Bar - $this->assertEquals( - 'BarTop', - $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data) - ); - - // Stepping up & back down the scope tree - $this->assertEquals( - 'BazFooBar', - $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Foo.Name}{$Up.Foo.Bar.Name}<% end_with %>', $data) - ); - - // Using $Up in a with block - $this->assertEquals( - 'BazTopBar', - $this->render( - '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Foo.Bar.Name}<% end_with %>' - . '<% end_with %>', - $data - ) - ); - - // Stepping up & back down the scope tree with with blocks - $this->assertEquals( - 'BazTopBarTopBaz', - $this->render( - '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Foo.Bar %>{$Name}<% end_with %>' - . '{$Name}<% end_with %>{$Name}<% end_with %>', - $data - ) - ); - - // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo - $this->assertEquals( - 'Foo', - $this->render( - '<% with Foo %><% with Bar %><% with Baz %>{$Up.Up.Name}<% end_with %><% end_with %>' - . '<% end_with %>', - $data - ) - ); - - // Using $Up as part of a lookup chain in <% with %> - $this->assertEquals( - 'Top', - $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Name}<% end_with %>', $data) - ); - } - - public function testTooManyUps() - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage("Up called when we're already at the top of the scope"); - $data = new ArrayData([ - 'Foo' => new ArrayData([ - 'Name' => 'Foo', - 'Bar' => new ArrayData([ - 'Name' => 'Bar' - ]) - ]) - ]); - - $this->assertEquals( - 'Foo', - $this->render('<% with Foo.Bar %>{$Up.Up.Name}<% end_with %>', $data) - ); - } - - /** - * Test $Up works when the scope $Up refers to was entered with a "loop" block - */ - public function testUpInLoop() - { - - // Data to run the loop tests on - one sequence of three items, each with a subitem - $data = new ArrayData( - [ - 'Name' => 'Top', - 'Foo' => new ArrayList( - [ - new ArrayData( - [ - 'Name' => '1', - 'Sub' => new ArrayData( - [ - 'Name' => 'Bar' - ] - ) - ] - ), - new ArrayData( - [ - 'Name' => '2', - 'Sub' => new ArrayData( - [ - 'Name' => 'Baz' - ] - ) - ] - ), - new ArrayData( - [ - 'Name' => '3', - 'Sub' => new ArrayData( - [ - 'Name' => 'Qux' - ] - ) - ] - ) - ] - ) - ] - ); - - // Make sure inside a loop, $Up refers to the current item of the loop - $this->assertEqualIgnoringWhitespace( - '111 222 333', - $this->render( - '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>', - $data - ) - ); - - // Make sure inside a loop, looping over $Up uses a separate iterator, - // and doesn't interfere with the original iterator - $this->assertEqualIgnoringWhitespace( - '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3', - $this->render( - '<% loop $Foo %> - $Name - <% with $Sub %> - $Name - <% loop $Up %>$Name<% end_loop %> - $Name - <% end_with %> - $Name - <% end_loop %>', - $data - ) - ); - - // Make sure inside a loop, looping over $Up uses a separate iterator, - // and doesn't interfere with the original iterator or local lookups - $this->assertEqualIgnoringWhitespace( - '1 Bar1 123 1Bar 1 2 Baz2 123 2Baz 2 3 Qux3 123 3Qux 3', - $this->render( - '<% loop $Foo %> - $Name - <% with $Sub %> - {$Name}{$Up.Name} - <% loop $Up %>$Name<% end_loop %> - {$Up.Name}{$Name} - <% end_with %> - $Name - <% end_loop %>', - $data - ) - ); - } - - /** - * Test that nested loops restore the loop variables correctly when pushing and popping states - */ - public function testNestedLoops() - { - - // Data to run the loop tests on - one sequence of three items, one with child elements - // (of a different size to the main sequence) - $data = new ArrayData( - [ - 'Foo' => new ArrayList( - [ - new ArrayData( - [ - 'Name' => '1', - 'Children' => new ArrayList( - [ - new ArrayData( - [ - 'Name' => 'a' - ] - ), - new ArrayData( - [ - 'Name' => 'b' - ] - ), - ] - ), - ] - ), - new ArrayData( - [ - 'Name' => '2', - 'Children' => new ArrayList(), - ] - ), - new ArrayData( - [ - 'Name' => '3', - 'Children' => new ArrayList(), - ] - ), - ] - ), - ] - ); - - // Make sure that including a loop inside a loop will not destroy the internal count of - // items, checked by using "Last" - $this->assertEqualIgnoringWhitespace( - '1ab23last', - $this->render( - '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if $IsLast %>last<% end_if %>' - . '<% end_loop %>', - $data - ) - ); - } - - public function testLayout() - { - $this->useTestTheme( - __DIR__ . '/SSTemplateEngineTest', - 'layouttest', - function () { - $engine = new SSTemplateEngine('Page'); - $this->assertEquals("Foo\n\n", $engine->render(new ViewLayerData([]))); - - $engine = new SSTemplateEngine(['Shortcodes', 'Page']); - $this->assertEquals("[file_link]\n\n", $engine->render(new ViewLayerData([]))); - } - ); - } - - public static function provideRenderWithSourceFileComments(): array - { - $i = __DIR__ . '/SSTemplateEngineTest/templates/Includes'; - $f = __DIR__ . '/SSTemplateEngineTest/templates/SSTemplateEngineTestComments'; - return [ - [ - 'name' => 'SSTemplateEngineTestCommentsFullSource', - 'expected' => "" - . "" - . "" - . "" - . "\t" - . "\t" - . "" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsFullSourceHTML4Doctype', - 'expected' => "" - . "" - . "" - . "" - . "\t" - . "\t" - . "" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsFullSourceNoDoctype', - 'expected' => "" - . "" - . "\t" - . "\t" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsFullSourceIfIE', - 'expected' => "" - . "" - . "" - . "" - . "" - . " " - . "\t" - . "\t" - . "" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsFullSourceIfIENoDoctype', - 'expected' => "" - . "" - . "" - . " " - . "" - . " " - . "\t" - . "\t" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsPartialSource', - 'expected' => - "" - . "
" - . "", - ], - [ - 'name' => 'SSTemplateEngineTestCommentsWithInclude', - 'expected' => - "" - . "
" - . "" - . "" - . "Included" - . "" - . "" - . "
" - . "", - ], - ]; - } - - #[DataProvider('provideRenderWithSourceFileComments')] - public function testRenderWithSourceFileComments(string $name, string $expected) - { - SSViewer::config()->set('source_file_comments', true); - $this->_renderWithSourceFileComments('SSTemplateEngineTestComments/' . $name, $expected); - } - - public static function provideRenderWithMissingTemplate(): array - { - return [ - [ - 'templateCandidates' => [], - ], - [ - 'templateCandidates' => '', - ], - [ - 'templateCandidates' => ['noTemplate'], - ], - [ - 'templateCandidates' => 'noTemplate', - ], - ]; - } - - #[DataProvider('provideRenderWithMissingTemplate')] - public function testRenderWithMissingTemplate(string|array $templateCandidates): void - { - if (empty($templateCandidates)) { - $message = 'No template to render. Try calling setTemplate() or passing template candidates into the constructor.'; - } else { - $message = 'None of the following templates could be found: ' - . print_r($templateCandidates, true) - . ' in themes "' . print_r(SSViewer::get_themes(), true) . '"'; - } - $engine = new SSTemplateEngine($templateCandidates); - $this->expectException(MissingTemplateException::class); - $this->expectExceptionMessage($message); - $engine->render(new ViewLayerData([])); - } - - public function testLoopIteratorIterator() - { - $list = new PaginatedList(new ArrayList()); - $result = $this->render( - '<% loop List %>$ID - $FirstName
<% end_loop %>', - new ArrayData(['List' => $list]) - ); - $this->assertEquals('', $result); - } - - public static function provideCallsWithArguments(): array - { - return [ - [ - 'template' => '$Level.output(1)', - 'expected' => '1-1', - ], - [ - 'template' => '$Nest.Level.output($Set.First.Number)', - 'expected' => '2-1', - ], - [ - 'template' => '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>', - 'expected' => '1-1', - ], - [ - 'template' => '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>', - 'expected' => '2-1', - ], - [ - 'template' => '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>', - 'expected' => '2-12-22-32-42-5', - ], - [ - 'template' => '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>', - 'expected' => '1-11-21-31-41-5', - ], - [ - 'template' => '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>', - 'expected' => '2-1', - ], - [ - 'template' => '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>', - 'expected' => '1-5', - ], - [ - 'template' => '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>', - 'expected' => '5-hi', - ], - [ - 'template' => '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>', - 'expected' => '!0', - ], - [ - 'template' => '<% with $Nest %> - <% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %> - <% end_with %>', - 'expected' => '1-hi', - ], - [ - 'template' => '<% with $Nest %> - <% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %> - <% end_with %>', - 'expected' => '!0!1!2!3!4', - ], - ]; - } - - #[DataProvider('provideCallsWithArguments')] - public function testCallsWithArguments(string $template, string $expected): void - { - $data = new ArrayData( - [ - 'Set' => new ArrayList( - [ - new SSTemplateEngineTest\TestObject("1"), - new SSTemplateEngineTest\TestObject("2"), - new SSTemplateEngineTest\TestObject("3"), - new SSTemplateEngineTest\TestObject("4"), - new SSTemplateEngineTest\TestObject("5"), - ] - ), - 'Level' => new SSTemplateEngineTest\LevelTestData(1), - 'Nest' => [ - 'Level' => new SSTemplateEngineTest\LevelTestData(2), - ], - ] - ); - - $this->assertEquals($expected, trim($this->render($template, $data) ?? '')); - } - - public function testRepeatedCallsAreCached() - { - $data = new SSTemplateEngineTest\CacheTestData(); - $template = ' - <% if $TestWithCall %> - <% with $TestWithCall %> - {$Message} - <% end_with %> - - {$TestWithCall.Message} - <% end_if %>'; - - $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data) ?? '')); - $this->assertEquals( - 1, - $data->testWithCalls, - 'SSTemplateEngineTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached' - ); - - $data = new SSTemplateEngineTest\CacheTestData(); - $template = ' - <% if $TestLoopCall %> - <% loop $TestLoopCall %> - {$Message} - <% end_loop %> - <% end_if %>'; - - $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data) ?? '')); - $this->assertEquals( - 1, - $data->testLoopCalls, - 'SSTemplateEngineTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached' - ); - } - - public function testClosedBlockExtension() - { - $count = 0; - $parser = new SSTemplateParser(); - $parser->addClosedBlock( - 'test', - function () use (&$count) { - $count++; - } - ); - - $engine = new SSTemplateEngine('SSTemplateEngineTestRecursiveInclude'); - $engine->setParser($parser); - $engine->renderString('<% test %><% end_test %>', new ViewLayerData([])); - - $this->assertEquals(1, $count); - } - - public function testOpenBlockExtension() - { - $count = 0; - $parser = new SSTemplateParser(); - $parser->addOpenBlock( - 'test', - function () use (&$count) { - $count++; - } - ); - - $engine = new SSTemplateEngine('SSTemplateEngineTestRecursiveInclude'); - $engine->setParser($parser); - $engine->renderString('<% test %>', new ViewLayerData([])); - - $this->assertEquals(1, $count); - } - - public function testFromStringCaching() - { - $content = 'Test content'; - $cacheFile = TEMP_PATH . DIRECTORY_SEPARATOR . '.cache.' . sha1($content ?? ''); - if (file_exists($cacheFile ?? '')) { - unlink($cacheFile ?? ''); - } - - // Test instance behaviors - $this->render($content, cache: false); - $this->assertFileDoesNotExist($cacheFile, 'Cache file was created when caching was off'); - - $this->render($content, cache: true); - $this->assertFileExists($cacheFile, 'Cache file wasn\'t created when it was meant to'); - unlink($cacheFile ?? ''); - } - - public function testPrimitivesConvertedToDBFields() - { - $data = new ArrayData([ - // null value should not be rendered, though should also not throw exception - 'Foo' => new ArrayList(['hello', true, 456, 7.89, null]) - ]); - $this->assertEqualIgnoringWhitespace( - 'hello 1 456 7.89', - $this->render('<% loop $Foo %>$Me<% end_loop %>', $data) - ); - } - - #[DoesNotPerformAssertions] - public function testMe(): void - { - $myArrayData = new class extends ArrayData { - public function forTemplate(): string - { - return ''; - } - }; - $this->render('$Me', $myArrayData); - } - - public function testLoopingThroughArrayInOverlay(): void - { - $modelData = new ModelData(); - $theArray = [ - ['Val' => 'one'], - ['Val' => 'two'], - ['Val' => 'red'], - ['Val' => 'blue'], - ]; - $engine = new SSTemplateEngine('SSTemplateEngineTestLoopArray'); - $output = $engine->render(new ViewLayerData($modelData), ['MyArray' => $theArray]); - $this->assertEqualIgnoringWhitespace('one two red blue', $output); - } - - public static function provideGetterMethod(): array - { - return [ - 'as property (not getter)' => [ - 'template' => '$MyProperty', - 'expected' => 'Nothing passed in', - ], - 'as method (not getter)' => [ - 'template' => '$MyProperty()', - 'expected' => 'Nothing passed in', - ], - 'as method (not getter), with arg' => [ - 'template' => '$MyProperty("Some Value")', - 'expected' => 'Was passed in: Some Value', - ], - 'as property (getter)' => [ - 'template' => '$getMyProperty', - 'expected' => 'Nothing passed in', - ], - 'as method (getter)' => [ - 'template' => '$getMyProperty()', - 'expected' => 'Nothing passed in', - ], - 'as method (getter), with arg' => [ - 'template' => '$getMyProperty("Some Value")', - 'expected' => 'Was passed in: Some Value', - ], - ]; - } - - #[DataProvider('provideGetterMethod')] - public function testGetterMethod(string $template, string $expected): void - { - $model = new SSTemplateEngineTest\TestObject(); - $this->assertSame($expected, $this->render($template, $model)); - } - - /** - * Small helper to render templates from strings - */ - private function render(string $templateString, mixed $data = null, array $overlay = [], bool $cache = false): string - { - $engine = new SSTemplateEngine(); - if ($data === null) { - $data = new SSTemplateEngineTest\TestFixture(); - } - $data = new ViewLayerData($data); - return trim('' . $engine->renderString($templateString, $data, $overlay, $cache)); - } - - private function _renderWithSourceFileComments($name, $expected) - { - $viewer = new SSViewer([$name]); - $data = new ArrayData([]); - $result = $viewer->process($data); - $expected = str_replace(["\r", "\n"], '', $expected ?? ''); - $result = str_replace(["\r", "\n"], '', $result ?? ''); - $this->assertEquals($result, $expected); - } - - private function getScopeInheritanceTestData() - { - return new ArrayData([ - 'Title' => 'TopTitleValue', - 'Items' => new ArrayList([ - new ArrayData(['Title' => 'Item 1']), - new ArrayData(['Title' => 'Item 2']), - new ArrayData(['Title' => 'Item 3']), - new ArrayData(['Title' => 'Item 4']), - new ArrayData(['Title' => 'Item 5']), - new ArrayData(['Title' => 'Item 6']) - ]) - ]); - } - - private function assertExpectedStrings($result, $expected) - { - foreach ($expected as $expectedStr) { - $this->assertTrue( - (boolean) preg_match("/{$expectedStr}/", $result ?? ''), - "Didn't find '{$expectedStr}' in:\n{$result}" - ); - } - } - - private function assertEqualIgnoringWhitespace($a, $b, $message = '') - { - $this->assertEquals(preg_replace('/\s+/', '', $a ?? ''), preg_replace('/\s+/', '', $b ?? ''), $message); - } -} diff --git a/tests/php/View/SSTemplateEngineTest/CacheTestData.php b/tests/php/View/SSTemplateEngineTest/CacheTestData.php deleted file mode 100644 index b155b9826b7..00000000000 --- a/tests/php/View/SSTemplateEngineTest/CacheTestData.php +++ /dev/null @@ -1,32 +0,0 @@ -testWithCalls++; - return ArrayData::create(['Message' => 'Hi']); - } - - public function TestLoopCall() - { - $this->testLoopCalls++; - return ArrayList::create( - [ - ArrayData::create(['Message' => 'One']), - ArrayData::create(['Message' => 'Two']) - ] - ); - } -} diff --git a/tests/php/View/SSTemplateEngineTest/LevelTestData.php b/tests/php/View/SSTemplateEngineTest/LevelTestData.php deleted file mode 100644 index 39a32e4f0f8..00000000000 --- a/tests/php/View/SSTemplateEngineTest/LevelTestData.php +++ /dev/null @@ -1,37 +0,0 @@ -depth = $depth; - } - - public function output($val) - { - return "$this->depth-$val"; - } - - public function forLoop($number) - { - $ret = []; - for ($i = 0; $i < (int)$number; ++$i) { - $ret[] = new TestObject("!$i"); - } - return new ArrayList($ret); - } - - public function forWith($number) - { - return new LevelTestData($number); - } -} diff --git a/tests/php/View/SSTemplateEngineTest/TestFixture.php b/tests/php/View/SSTemplateEngineTest/TestFixture.php deleted file mode 100644 index da2809a75c2..00000000000 --- a/tests/php/View/SSTemplateEngineTest/TestFixture.php +++ /dev/null @@ -1,85 +0,0 @@ -name = $name; - } - - public function __call(string $name, array $arguments = []): static|array|null - { - return $this->getValue($name, $arguments); - } - - public function __get(string $name): static|array|null - { - return $this->getValue($name); - } - - public function __isset(string $name): bool - { - if (preg_match('/NotSet/i', $name)) { - return false; - } - $reflectionScope = new ReflectionClass(SSViewer_Scope::class); - $globalProperties = $reflectionScope->getStaticPropertyValue('globalProperties'); - if (array_key_exists($name, $globalProperties)) { - return false; - } - return true; - } - - public function __toString(): string - { - if (preg_match('/NotSet/i', $this->name ?? '')) { - return ''; - } - if (preg_match('/Raw/i', $this->name ?? '')) { - return $this->name ?? ''; - } - return '[out:' . $this->name . ']'; - } - - private function getValue(string $name, array $arguments = []): static|array|null - { - $childName = $this->argedName($name, $arguments); - - // Special field name Loop### to create a list - if (preg_match('/^Loop([0-9]+)$/', $name ?? '', $matches)) { - $output = []; - for ($i = 0; $i < $matches[1]; $i++) { - $output[] = new TestFixture($childName); - } - return $output; - } - - if (preg_match('/NotSet/i', $name)) { - return null; - } - - return new TestFixture($childName); - } - - private function argedName(string $fieldName, array $arguments): string - { - $childName = $this->name ? "$this->name.$fieldName" : $fieldName; - if ($arguments) { - return $childName . '(' . implode(',', $arguments) . ')'; - } else { - return $childName; - } - } -} diff --git a/tests/php/View/SSTemplateEngineTest/TestGlobalProvider.php b/tests/php/View/SSTemplateEngineTest/TestGlobalProvider.php deleted file mode 100644 index 6896f43baba..00000000000 --- a/tests/php/View/SSTemplateEngineTest/TestGlobalProvider.php +++ /dev/null @@ -1,51 +0,0 @@ - ['method' => 'get_html', 'casting' => 'HTMLFragment'], - 'SSTemplateEngineTest_GlobalHTMLEscaped' => ['method' => 'get_html'], - - 'SSTemplateEngineTest_GlobalAutomatic', - 'SSTemplateEngineTest_GlobalReferencedByString' => 'get_reference', - 'SSTemplateEngineTest_GlobalReferencedInArray' => ['method' => 'get_reference'], - - 'SSTemplateEngineTest_GlobalThatTakesArguments' => ['method' => 'get_argmix', 'casting' => 'HTMLFragment'], - 'SSTemplateEngineTest_GlobalReturnsNull' => 'getNull', - ]; - } - - public static function get_html() - { - return '
'; - } - - public static function SSTemplateEngineTest_GlobalAutomatic() - { - return 'automatic'; - } - - public static function get_reference() - { - return 'reference'; - } - - public static function get_argmix() - { - $args = func_get_args(); - return 'z' . implode(':', $args) . 'z'; - } - - public static function getNull() - { - return null; - } -} diff --git a/tests/php/View/SSTemplateEngineTest/TestModelData.php b/tests/php/View/SSTemplateEngineTest/TestModelData.php deleted file mode 100644 index 15c9bd98ebe..00000000000 --- a/tests/php/View/SSTemplateEngineTest/TestModelData.php +++ /dev/null @@ -1,45 +0,0 @@ - 'Text', - 'HTMLValue' => 'HTMLFragment' - ]; - - public function methodWithOneArgument($arg1) - { - return "arg1:{$arg1}"; - } - - public function methodWithTwoArguments($arg1, $arg2) - { - return "arg1:{$arg1},arg2:{$arg2}"; - } - - public function methodWithTypedArguments(...$args) - { - $ret = []; - foreach ($args as $i => $arg) { - $ret[] = "arg$i:" . json_encode($arg); - } - return implode(',', $ret); - } - - public function Type($arg) - { - return gettype($arg) . ':' . $arg; - } -} diff --git a/tests/php/View/SSTemplateEngineTest/TestObject.php b/tests/php/View/SSTemplateEngineTest/TestObject.php deleted file mode 100644 index 5698fcf38fd..00000000000 --- a/tests/php/View/SSTemplateEngineTest/TestObject.php +++ /dev/null @@ -1,52 +0,0 @@ - 'Text', - ]; - - - public function __construct($number = null) - { - parent::__construct(); - $this->number = $number; - } - - public function Number() - { - return $this->number; - } - - public function absoluteBaseURL() - { - return "testLocalFunctionPriorityCalled"; - } - - public function lotsOfArguments11($a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k) - { - return $a . $b . $c . $d . $e . $f . $g . $h . $i . $j . $k; - } - - public function Link() - { - return 'some/url.html'; - } - - public function getMyProperty(mixed $someArg = null): string - { - if ($someArg) { - return "Was passed in: $someArg"; - } - return 'Nothing passed in'; - } -} diff --git a/tests/php/View/SSTemplateEngineTest/javascript/RequirementsTest_a.js b/tests/php/View/SSTemplateEngineTest/javascript/RequirementsTest_a.js deleted file mode 100644 index 99fa3922dff..00000000000 --- a/tests/php/View/SSTemplateEngineTest/javascript/RequirementsTest_a.js +++ /dev/null @@ -1 +0,0 @@ -alert('a'); diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestCommentsInclude.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestCommentsInclude.ss deleted file mode 100644 index 01e9183b91d..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestCommentsInclude.ss +++ /dev/null @@ -1 +0,0 @@ -Included diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeObjectArguments.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeObjectArguments.ss deleted file mode 100644 index 269d0d14546..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeObjectArguments.ss +++ /dev/null @@ -1 +0,0 @@ -$A.Key $B.Key diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceInclude.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceInclude.ss deleted file mode 100644 index bd92206e75a..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceInclude.ss +++ /dev/null @@ -1 +0,0 @@ -<% if $Title %>$Title<% else %>Untitled<% end_if %> <% if $ArgA %>_ $ArgA <% end_if %>- <% if $IsFirst %>First-<% end_if %><% if $IsLast %>Last-<% end_if %><%if $MultipleOf(2) %>EVEN<% else %>ODD<% end_if %> top:$Top.Title diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInLoop.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInLoop.ss deleted file mode 100644 index 32dd1fccffc..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInLoop.ss +++ /dev/null @@ -1 +0,0 @@ -$Title - <% loop $Items %>$Title<% if not $IsLast %> - <% else %> - {$Top.Title}<% end_if %><% end_loop %> \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInNestedWith.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInNestedWith.ss deleted file mode 100644 index e9ca46b50b0..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInNestedWith.ss +++ /dev/null @@ -1 +0,0 @@ -$Top.Title - <% with $Item %>$Title - <% with $NestedItem %>{$Title} - {$Up.Title} - {$Top.Title}<% end_with %><% end_with %> \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInWith.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInWith.ss deleted file mode 100644 index f4e5f4ac80c..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithArgsInWith.ss +++ /dev/null @@ -1 +0,0 @@ -$Title - <% with $Item %>$Title - {$Up.Title}<% end_with %> \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithUpAndTop.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithUpAndTop.ss deleted file mode 100644 index 14f43b9076a..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeScopeInheritanceWithUpAndTop.ss +++ /dev/null @@ -1 +0,0 @@ -$Title<% with $Item %> - <% with $Up %>$Title<% end_with %> - <% with $NestedItem %><% with $Top %>$Title<% end_with %><% end_with %><% end_with %> \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeWithArguments.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeWithArguments.ss deleted file mode 100644 index 73004e1b793..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestIncludeWithArguments.ss +++ /dev/null @@ -1 +0,0 @@ -

$Arg1

$Arg2

{$Arg2.Count}

diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestProcessHead.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestProcessHead.ss deleted file mode 100644 index 094cfa29a33..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestProcessHead.ss +++ /dev/null @@ -1,3 +0,0 @@ - - <% require themedJavascript(RequirementsTest_a) %> - diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestRecursiveInclude.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestRecursiveInclude.ss deleted file mode 100644 index af5b2db8a97..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestRecursiveInclude.ss +++ /dev/null @@ -1,6 +0,0 @@ -$Title -<% if Children %> -<% loop Children %> -<% include SSTemplateEngineTestRecursiveInclude %> -<% end_loop %> -<% end_if %> diff --git a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestTypePreservation.ss b/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestTypePreservation.ss deleted file mode 100644 index 0f9e2f8bf9f..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Includes/SSTemplateEngineTestTypePreservation.ss +++ /dev/null @@ -1 +0,0 @@ -$Test.Type($Argument) diff --git a/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedOnceBase.ss b/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedOnceBase.ss deleted file mode 100644 index 75700a0d6bf..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedOnceBase.ss +++ /dev/null @@ -1 +0,0 @@ -Namespace/IncludedOnceBase.ss diff --git a/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedTwice.ss b/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedTwice.ss deleted file mode 100644 index ef0b569e011..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Namespace/IncludedTwice.ss +++ /dev/null @@ -1 +0,0 @@ -Namespace/IncludedTwice.ss diff --git a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedOnceSub.ss b/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedOnceSub.ss deleted file mode 100644 index b3f7fb5b87d..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedOnceSub.ss +++ /dev/null @@ -1 +0,0 @@ -Namespace/Includes/IncludedOnceSub.ss diff --git a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedTwice.ss b/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedTwice.ss deleted file mode 100644 index aa0e4c2ba0d..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/IncludedTwice.ss +++ /dev/null @@ -1 +0,0 @@ -Namespace/Includes/IncludedTwice.ss diff --git a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/NamespaceInclude.ss b/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/NamespaceInclude.ss deleted file mode 100644 index 48d7eb546e5..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/Namespace/Includes/NamespaceInclude.ss +++ /dev/null @@ -1 +0,0 @@ -NamespaceInclude diff --git a/tests/php/View/SSTemplateEngineTest/templates/RSSFeedTest.ss b/tests/php/View/SSTemplateEngineTest/templates/RSSFeedTest.ss deleted file mode 100644 index afcae7f76ec..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/RSSFeedTest.ss +++ /dev/null @@ -1,6 +0,0 @@ - - - - Test Custom Template - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSource.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSource.ss deleted file mode 100644 index 82f008b01de..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSource.ss +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceHTML4Doctype.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceHTML4Doctype.ss deleted file mode 100644 index ec38777ce5a..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceHTML4Doctype.ss +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIE.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIE.ss deleted file mode 100644 index 676405f4a67..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIE.ss +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIENoDoctype.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIENoDoctype.ss deleted file mode 100644 index a34df2c3e9a..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceIfIENoDoctype.ss +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceNoDoctype.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceNoDoctype.ss deleted file mode 100644 index 2cb856a7fdc..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsFullSourceNoDoctype.ss +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsPartialSource.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsPartialSource.ss deleted file mode 100644 index 13ecad1b18f..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsPartialSource.ss +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsWithInclude.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsWithInclude.ss deleted file mode 100644 index a3a4949454c..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestComments/SSTemplateEngineTestCommentsWithInclude.ss +++ /dev/null @@ -1 +0,0 @@ -
<% include SSTemplateEngineTestCommentsInclude %>
diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritance.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritance.ss deleted file mode 100644 index f588c9d46ba..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritance.ss +++ /dev/null @@ -1,3 +0,0 @@ -<% loop Items %> - <% include SSTemplateEngineTestIncludeScopeInheritanceInclude %> -<% end_loop %> diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritanceWithArgs.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritanceWithArgs.ss deleted file mode 100644 index 772f76ae438..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestIncludeScopeInheritanceWithArgs.ss +++ /dev/null @@ -1,3 +0,0 @@ -<% loop Items %> - <% include SSTemplateEngineTestIncludeScopeInheritanceInclude ArgA=$Title %> -<% end_loop %> diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestLoopArray.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestLoopArray.ss deleted file mode 100644 index f9a20f36eb5..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestLoopArray.ss +++ /dev/null @@ -1,3 +0,0 @@ -<% loop $MyArray %> - $Val -<% end_loop %> diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestPartialTemplate.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestPartialTemplate.ss deleted file mode 100644 index 5b097c5c701..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestPartialTemplate.ss +++ /dev/null @@ -1 +0,0 @@ -Test partial template: $Var diff --git a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestProcess.ss b/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestProcess.ss deleted file mode 100644 index 7486f81dbce..00000000000 --- a/tests/php/View/SSTemplateEngineTest/templates/SSTemplateEngineTestProcess.ss +++ /dev/null @@ -1,6 +0,0 @@ - - <% include SSTemplateEngineTestProcessHead %> - - - - diff --git a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Controller.ss b/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Controller.ss deleted file mode 100644 index 8cba75e31a7..00000000000 --- a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Controller.ss +++ /dev/null @@ -1 +0,0 @@ -Controller diff --git a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Page.ss b/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Page.ss deleted file mode 100644 index bc56c4d8944..00000000000 --- a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Page.ss +++ /dev/null @@ -1 +0,0 @@ -Foo diff --git a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Shortcodes.ss b/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Shortcodes.ss deleted file mode 100644 index 0264a4d3276..00000000000 --- a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Layout/Shortcodes.ss +++ /dev/null @@ -1 +0,0 @@ -[file_link] diff --git a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Page.ss b/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Page.ss deleted file mode 100644 index 8e3d95d79e9..00000000000 --- a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/Page.ss +++ /dev/null @@ -1 +0,0 @@ -$Layout diff --git a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/TestNamespace/SSTemplateEngineTestModel_Controller.ss b/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/TestNamespace/SSTemplateEngineTestModel_Controller.ss deleted file mode 100644 index b3b85b793d8..00000000000 --- a/tests/php/View/SSTemplateEngineTest/themes/layouttest/templates/TestNamespace/SSTemplateEngineTestModel_Controller.ss +++ /dev/null @@ -1 +0,0 @@ -SSTemplateEngineTest diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/_manifest_exclude b/tests/php/View/SSTemplateEngineTest_findTemplate/_manifest_exclude deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/Root.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/Root.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/composer.json b/tests/php/View/SSTemplateEngineTest_findTemplate/module/composer.json deleted file mode 100644 index 5d9e5da73be..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/composer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "silverstripe/module", - "type": "silverstripe-vendormodule" -} diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/subfolder/templates/Subfolder.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/subfolder/templates/Subfolder.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/CustomTemplate.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/CustomTemplate.ss deleted file mode 100644 index 0ad39c91609..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/CustomTemplate.ss +++ /dev/null @@ -1 +0,0 @@ -Custom Template diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomPage.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomPage.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomThemePage.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/CustomThemePage.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/Page.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Layout/Page.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/Layout/MyClass.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/Layout/MyClass.ss deleted file mode 100644 index d1edecb7912..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/Layout/MyClass.ss +++ /dev/null @@ -1 +0,0 @@ -MyClass.ss \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MyClass.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MyClass.ss deleted file mode 100644 index d1edecb7912..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MyClass.ss +++ /dev/null @@ -1 +0,0 @@ -MyClass.ss \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MySubnamespace/MySubclass.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MySubnamespace/MySubclass.ss deleted file mode 100644 index 7d5e1028f8c..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/MyNamespace/MySubnamespace/MySubclass.ss +++ /dev/null @@ -1 +0,0 @@ -MySubclass.ss \ No newline at end of file diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Page.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/templates/Page.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/tests/templates/Test.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/tests/templates/Test.ss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/module/themes/subtheme/templates/NestedThemePage.ss b/tests/php/View/SSTemplateEngineTest_findTemplate/module/themes/subtheme/templates/NestedThemePage.ss deleted file mode 100644 index 68716bf0b0e..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/module/themes/subtheme/templates/NestedThemePage.ss +++ /dev/null @@ -1 +0,0 @@ -

Nested theme page

diff --git a/tests/php/View/SSTemplateEngineTest_findTemplate/myproject/_config.php b/tests/php/View/SSTemplateEngineTest_findTemplate/myproject/_config.php deleted file mode 100644 index b3d9bbc7f37..00000000000 --- a/tests/php/View/SSTemplateEngineTest_findTemplate/myproject/_config.php +++ /dev/null @@ -1 +0,0 @@ -parent = $parent ; - $this->flags = array() ; - } - - function __set( $k, $v ) { - $this->flags[$k] = $v ; - return $v ; - } - - function __get( $k ) { - if ( isset( $this->flags[$k] ) ) return $this->flags[$k] ; - if ( isset( $this->parent ) ) return $this->parent->$k ; - return NULL ; - } -} - -/** - * PHPWriter contains several code generation snippets that are used both by the Token and the Rule compiler - */ -class PHPWriter { - - static $varid = 0 ; - - function varid() { - return '_' . (self::$varid++) ; - } - - function function_name( $str ) { - $str = preg_replace( '/-/', '_', $str ?? '' ) ; - $str = preg_replace( '/\$/', 'DLR', $str ?? '' ) ; - $str = preg_replace( '/\*/', 'STR', $str ?? '' ) ; - $str = preg_replace( '/[^\w]+/', '', $str ?? '' ) ; - return $str ; - } - - function save($id) { - return PHPBuilder::build() - ->l( - '$res'.$id.' = $result;', - '$pos'.$id.' = $this->pos;' - ); - } - - function restore( $id, $remove = FALSE ) { - $code = PHPBuilder::build() - ->l( - '$result = $res'.$id.';', - '$this->pos = $pos'.$id.';' - ); - - if ( $remove ) $code->l( - 'unset( $res'.$id.' );', - 'unset( $pos'.$id.' );' - ); - - return $code ; - } - - function match_fail_conditional( $on, $match = NULL, $fail = NULL ) { - return PHPBuilder::build() - ->b( 'if (' . $on . ')', - $match, - 'MATCH' - ) - ->b( 'else', - $fail, - 'FAIL' - ); - } - - function match_fail_block( $code ) { - $id = $this->varid() ; - - return PHPBuilder::build() - ->l( - '$'.$id.' = NULL;' - ) - ->b( 'do', - $code->replace(array( - 'MBREAK' => '$'.$id.' = TRUE; break;', - 'FBREAK' => '$'.$id.' = FALSE; break;' - )) - ) - ->l( - 'while(0);' - ) - ->b( 'if( $'.$id.' === TRUE )', 'MATCH' ) - ->b( 'if( $'.$id.' === FALSE)', 'FAIL' ) - ; - } -} - -/** - * A Token is any portion of a match rule. Tokens are responsible for generating the code to match against them. - * - * This base class provides the compile() function, which handles the token modifiers ( ? * + & ! ) - * - * Each child class should provide the function match_code() which will generate the code to match against that specific token type. - * In that generated code they should include the lines MATCH or FAIL when a match or a decisive failure occurs. These will - * be overwritten when they are injected into parent Tokens or Rules. There is no requirement on where MATCH and FAIL can occur. - * They tokens are also responsible for storing and restoring state when nessecary to handle a non-decisive failure. - * - * @author hamish - * - */ -abstract class Token extends PHPWriter { - public $optional = FALSE ; - public $zero_or_more = FALSE ; - public $one_or_more = FALSE ; - public $positive_lookahead = FALSE ; - public $negative_lookahead = FALSE ; - public $silent = FALSE ; - - public $tag = FALSE ; - - public $type ; - public $value ; - - function __construct( $type, $value = NULL ) { - $this->type = $type ; - $this->value = $value ; - } - - // abstract protected function match_code() ; - - function compile() { - $code = $this->match_code($this->value) ; - - $id = $this->varid() ; - - if ( $this->optional ) { - $code = PHPBuilder::build() - ->l( - $this->save($id), - $code->replace( array( 'FAIL' => $this->restore($id,true) )) - ); - } - - if ( $this->zero_or_more ) { - $code = PHPBuilder::build() - ->b( 'while (true)', - $this->save($id), - $code->replace( array( - 'MATCH' => NULL, - 'FAIL' => - $this->restore($id,true) - ->l( 'break;' ) - )) - ) - ->l( - 'MATCH' - ); - } - - if ( $this->one_or_more ) { - $code = PHPBuilder::build() - ->l( - '$count = 0;' - ) - ->b( 'while (true)', - $this->save($id), - $code->replace( array( - 'MATCH' => NULL, - 'FAIL' => - $this->restore($id,true) - ->l( 'break;' ) - )), - '$count += 1;' - ) - ->b( 'if ($count > 0)', 'MATCH' ) - ->b( 'else', 'FAIL' ); - } - - if ( $this->positive_lookahead ) { - $code = PHPBuilder::build() - ->l( - $this->save($id), - $code->replace( array( - 'MATCH' => - $this->restore($id) - ->l( 'MATCH' ), - 'FAIL' => - $this->restore($id) - ->l( 'FAIL' ) - ))); - } - - if ( $this->negative_lookahead ) { - $code = PHPBuilder::build() - ->l( - $this->save($id), - $code->replace( array( - 'MATCH' => - $this->restore($id) - ->l( 'FAIL' ), - 'FAIL' => - $this->restore($id) - ->l( 'MATCH' ) - ))); - } - - if ( $this->tag && !($this instanceof TokenRecurse ) ) { - $code = PHPBuilder::build() - ->l( - '$stack[] = $result; $result = $this->construct( $matchrule, "'.$this->tag.'" ); ', - $code->replace(array( - 'MATCH' => PHPBuilder::build() - ->l( - '$subres = $result; $result = array_pop($stack);', - '$this->store( $result, $subres, \''.$this->tag.'\' );', - 'MATCH' - ), - 'FAIL' => PHPBuilder::build() - ->l( - '$result = array_pop($stack);', - 'FAIL' - ) - ))); - } - - return $code ; - } - -} - -abstract class TokenTerminal extends Token { - function set_text( $text ) { - return $this->silent ? NULL : '$result["text"] .= ' . $text . ';'; - } - - protected function match_code( $value ) { - return $this->match_fail_conditional( '( $subres = $this->'.$this->type.'( '.$value.' ) ) !== FALSE', - $this->set_text('$subres') - ); - } -} - -abstract class TokenExpressionable extends TokenTerminal { - - static $expression_rx = '/ \$(\w+) | { \$(\w+) } /x'; - - function contains_expression( $value ){ - return preg_match(self::$expression_rx, $value ?? ''); - } - - function expression_replace($matches) { - return '\'.$this->expression($result, $stack, \'' . (!empty($matches[1]) ? $matches[1] : $matches[2]) . "').'"; - } - - function match_code( $value ) { - $value = preg_replace_callback(self::$expression_rx, array($this, 'expression_replace'), $value ?? ''); - return parent::match_code($value); - } -} - -class TokenLiteral extends TokenExpressionable { - function __construct( $value ) { - parent::__construct( 'literal', "'" . substr($value ?? '',1,-1) . "'" ); - } - - function match_code( $value ) { - // We inline single-character matches for speed - if ( !$this->contains_expression($value) && strlen( eval( 'return '. $value . ';' ) ) == 1 ) { - return $this->match_fail_conditional( 'substr($this->string ?? \'\',$this->pos ?? 0,1) == '.$value, - PHPBuilder::build()->l( - '$this->pos += 1;', - $this->set_text($value) - ) - ); - } - return parent::match_code($value); - } -} - -class TokenRegex extends TokenExpressionable { - static function escape( $rx ) { - $rx = str_replace( "'", "\\'", $rx ?? '' ) ; - $rx = str_replace( '\\\\', '\\\\\\\\', $rx ?? '' ) ; - return $rx ; - } - - function __construct( $value ) { - parent::__construct('rx', self::escape($value)); - } - - function match_code( $value ) { - return parent::match_code("'{$value}'"); - } -} - -class TokenWhitespace extends TokenTerminal { - function __construct( $optional ) { - parent::__construct( 'whitespace', $optional ) ; - } - - /* Call recursion indirectly */ - function match_code( $value ) { - $code = parent::match_code( '' ) ; - return $value ? $code->replace( array( 'FAIL' => NULL )) : $code ; - } -} - -class TokenRecurse extends Token { - function __construct( $value ) { - parent::__construct( 'recurse', $value ) ; - } - - function match_function( $value ) { - return "'".$this->function_name($value)."'"; - } - - function match_code( $value ) { - $function = $this->match_function($value) ; - $storetag = $this->function_name( $this->tag ? $this->tag : $this->match_function($value) ) ; - - if ( ParserCompiler::$debug ) { - $debug_header = PHPBuilder::build() - ->l( - '$indent = str_repeat( " ", $this->depth );', - '$this->depth += 2;', - '$sub = ( strlen( $this->string ) - $this->pos > 20 ) ? ( substr( $this->string, $this->pos, 20 ) . "..." ) : substr( $this->string, $this->pos );', - '$sub = preg_replace( \'/(\r|\n)+/\', " {NL} ", $sub );', - 'print( $indent."Matching against $matcher (".$sub.")\n" );' - ); - - $debug_match = PHPBuilder::build() - ->l( - 'print( $indent."MATCH\n" );', - '$this->depth -= 2;' - ); - - $debug_fail = PHPBuilder::build() - ->l( - 'print( $indent."FAIL\n" );', - '$this->depth -= 2;' - ); - } - else { - $debug_header = $debug_match = $debug_fail = NULL ; - } - - return PHPBuilder::build()->l( - '$matcher = \'match_\'.'.$function.'; $key = $matcher; $pos = $this->pos;', - $debug_header, - '$subres = ( $this->packhas( $key, $pos ) ? $this->packread( $key, $pos ) : $this->packwrite( $key, $pos, $this->$matcher(array_merge($stack, array($result))) ) );', - $this->match_fail_conditional( '$subres !== FALSE', - PHPBuilder::build()->l( - $debug_match, - $this->tag === FALSE ? - '$this->store( $result, $subres );' : - '$this->store( $result, $subres, "'.$storetag.'" );' - ), - PHPBuilder::build()->l( - $debug_fail - ) - )); - } -} - -class TokenExpressionedRecurse extends TokenRecurse { - function match_function( $value ) { - return '$this->expression($result, $stack, \''.$value.'\')'; - } -} - -class TokenSequence extends Token { - function __construct( $value ) { - parent::__construct( 'sequence', $value ) ; - } - - function match_code( $value ) { - $code = PHPBuilder::build() ; - foreach( $value as $token ) { - $code->l( - $token->compile()->replace(array( - 'MATCH' => NULL, - 'FAIL' => 'FBREAK' - )) - ); - } - $code->l( 'MBREAK' ); - - return $this->match_fail_block( $code ) ; - } -} - -class TokenOption extends Token { - function __construct( $opt1, $opt2 ) { - parent::__construct( 'option', array( $opt1, $opt2 ) ) ; - } - - function match_code( $value ) { - $id = $this->varid() ; - $code = PHPBuilder::build() - ->l( - $this->save($id) - ) ; - - foreach ( $value as $opt ) { - $code->l( - $opt->compile()->replace(array( - 'MATCH' => 'MBREAK', - 'FAIL' => NULL - )), - $this->restore($id) - ); - } - $code->l( 'FBREAK' ) ; - - return $this->match_fail_block( $code ) ; - } -} - - -/** - * Handles storing of information for an expression that applys to the next token, and deletion of that - * information after applying - * - * @author Hamish Friedlander - */ -class Pending { - function __construct() { - $this->what = NULL ; - } - - function set( $what, $val = TRUE ) { - $this->what = $what ; - $this->val = $val ; - } - - function apply_if_present( $on ) { - if ( $this->what !== NULL ) { - $what = $this->what ; - $on->$what = $this->val ; - - $this->what = NULL ; - } - } -} - -/** - * Rule parsing and code generation - * - * A rule is the basic unit of a PEG. This parses one rule, and generates a function that will match on a string - * - * @author Hamish Friedlander - */ -class Rule extends PHPWriter { - - static $rule_rx = '@ - (? \w+) # The name of the rule - ( \s+ extends \s+ (?\w+) )? # The extends word - ( \s* \( (?.*) \) )? # Any variable setters - ( - \s*(?:) | # Marks the matching rule start - \s*(?;) | # Marks the replacing rule start - \s*$ - ) - (?[\s\S]*) - @x'; - - static $argument_rx = '@ - ( [^=]+ ) # Name - = # Seperator - ( [^=,]+ ) # Variable - (,|$) - @x'; - - static $replacement_rx = '@ - ( ([^=]|=[^>])+ ) # What to replace - => # The replacement mark - ( [^,]+ ) # What to replace it with - (,|$) - @x'; - - static $function_rx = '@^\s+function\s+([^\s(]+)\s*(.*)@' ; - - protected $parser; - protected $lines; - - public $name; - public $extends; - public $mode; - public $rule; - - function __construct($parser, $lines) { - $this->parser = $parser; - $this->lines = $lines; - - // Find the first line (if any) that's an attached function definition. Can skip first line (unless this block is malformed) - for ($i = 1; $i < count($lines ?? []); $i++) { - if (preg_match(self::$function_rx, $lines[$i] ?? '')) break; - } - - // Then split into the two parts - $spec = array_slice($lines ?? [], 0, $i); - $funcs = array_slice($lines ?? [], $i ?? 0); - - // Parse out the spec - $spec = implode("\n", $spec); - if (!preg_match(self::$rule_rx, $spec ?? '', $specmatch)) user_error('Malformed rule spec ' . $spec, E_USER_ERROR); - - $this->name = $specmatch['name']; - - if ($specmatch['extends']) { - $this->extends = $this->parser->rules[$specmatch['extends']]; - if (!$this->extends) user_error('Extended rule '.$specmatch['extends'].' is not defined before being extended', E_USER_ERROR); - } - - $this->arguments = array(); - - if ($specmatch['arguments']) { - preg_match_all(self::$argument_rx, $specmatch['arguments'] ?? '', $arguments, PREG_SET_ORDER); - - foreach ($arguments as $argument){ - $this->arguments[trim($argument[1])] = trim($argument[2] ?? ''); - } - } - - $this->mode = $specmatch['matchmark'] ? 'rule' : 'replace'; - - if ($this->mode == 'rule') { - $this->rule = $specmatch['rule']; - $this->parse_rule() ; - } - else { - if (!$this->extends) user_error('Replace matcher, but not on an extends rule', E_USER_ERROR); - - $this->replacements = array(); - preg_match_all(self::$replacement_rx, $specmatch['rule'] ?? '', $replacements, PREG_SET_ORDER); - - $rule = $this->extends->rule; - - foreach ($replacements as $replacement) { - $search = trim($replacement[1] ?? ''); - $replace = trim($replacement[3] ?? ''); if ($replace == "''" || $replace == '""') $replace = ""; - - $rule = str_replace($search ?? '', ' '.$replace.' ', $rule ?? ''); - } - - $this->rule = $rule; - $this->parse_rule() ; - } - - // Parse out the functions - - $this->functions = array() ; - - $active_function = NULL ; - - foreach( $funcs as $line ) { - /* Handle function definitions */ - if ( preg_match( self::$function_rx, $line ?? '', $func_match, 0 ) ) { - $active_function = $func_match[1]; - $this->functions[$active_function] = $func_match[2] . PHP_EOL; - } - else $this->functions[$active_function] .= $line . PHP_EOL ; - } - } - - /* Manual parsing, because we can't bootstrap ourselves yet */ - function parse_rule() { - $rule = trim( $this->rule ?? '' ) ; - - /* If this is a regex end-token, just mark it and return */ - if ( substr( $rule ?? '', 0, 1 ) == '/' ) { - $this->parsed = new TokenRegex( $rule ) ; - } - else { - $tokens = array() ; - $this->tokenize( $rule, $tokens ) ; - $this->parsed = ( count( $tokens ?? [] ) == 1 ? array_pop( $tokens ) : new TokenSequence( $tokens ) ) ; - } - - } - - static $rx_rx = '{^/( - ((\\\\\\\\)*\\\\/) # Escaped \/, making sure to catch all the \\ first, so that we dont think \\/ is an escaped / - | - [^/] # Anything except / - )*/}xu' ; - - function tokenize( $str, &$tokens, $o = 0 ) { - - $pending = new Pending() ; - - while ( $o < strlen( $str ?? '' ) ) { - $sub = substr( $str ?? '', $o ?? 0 ) ; - - /* Absorb white-space */ - if ( preg_match( '/^\s+/', $sub ?? '', $match ) ) { - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle expression labels */ - elseif ( preg_match( '/^(\w*):/', $sub ?? '', $match ) ) { - $pending->set( 'tag', isset( $match[1] ) ? $match[1] : '' ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle descent token */ - elseif ( preg_match( '/^[\w-]+/', $sub ?? '', $match ) ) { - $tokens[] = $t = new TokenRecurse( $match[0] ) ; $pending->apply_if_present( $t ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle " quoted literals */ - elseif ( preg_match( '/^"[^"]*"/', $sub ?? '', $match ) ) { - $tokens[] = $t = new TokenLiteral( $match[0] ) ; $pending->apply_if_present( $t ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle ' quoted literals */ - elseif ( preg_match( "/^'[^']*'/", $sub ?? '', $match ) ) { - $tokens[] = $t = new TokenLiteral( $match[0] ) ; $pending->apply_if_present( $t ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle regexs */ - elseif ( preg_match( self::$rx_rx, $sub ?? '', $match ) ) { - $tokens[] = $t = new TokenRegex( $match[0] ) ; $pending->apply_if_present( $t ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle $ call literals */ - elseif ( preg_match( '/^\$(\w+)/', $sub ?? '', $match ) ) { - $tokens[] = $t = new TokenExpressionedRecurse( $match[1] ) ; $pending->apply_if_present( $t ) ; - $o += strlen( $match[0] ?? '' ) ; - } - /* Handle flags */ - elseif ( preg_match( '/^\@(\w+)/', $sub ?? '', $match ) ) { - $l = count( $tokens ?? [] ) - 1 ; - $o += strlen( $match[0] ?? '' ) ; - user_error( "TODO: Flags not currently supported", E_USER_WARNING ) ; - } - /* Handle control tokens */ - else { - $c = substr( $sub ?? '', 0, 1 ) ; - $l = count( $tokens ?? [] ) - 1 ; - $o += 1 ; - switch( $c ) { - case '?': - $tokens[$l]->optional = TRUE ; - break ; - case '*': - $tokens[$l]->zero_or_more = TRUE ; - break ; - case '+': - $tokens[$l]->one_or_more = TRUE ; - break ; - - case '&': - $pending->set( 'positive_lookahead' ) ; - break ; - case '!': - $pending->set( 'negative_lookahead' ) ; - break ; - - case '.': - $pending->set( 'silent' ); - break; - - case '[': - case ']': - $tokens[] = new TokenWhitespace( FALSE ) ; - break ; - case '<': - case '>': - $tokens[] = new TokenWhitespace( TRUE ) ; - break ; - - case '(': - $subtokens = array() ; - $o = $this->tokenize( $str, $subtokens, $o ) ; - $tokens[] = $t = new TokenSequence( $subtokens ) ; $pending->apply_if_present( $t ) ; - break ; - case ')': - return $o ; - - case '|': - $option1 = $tokens ; - $option2 = array() ; - $o = $this->tokenize( $str, $option2, $o ) ; - - $option1 = (count($option1) == 1) ? $option1[0] : new TokenSequence( $option1 ); - $option2 = (count($option2) == 1) ? $option2[0] : new TokenSequence( $option2 ); - - $pending->apply_if_present( $option2 ) ; - - $tokens = array( new TokenOption( $option1, $option2 ) ) ; - return $o ; - - default: - user_error( "Can't parser $c - attempting to skip", E_USER_WARNING ) ; - } - } - } - - return $o ; - } - - /** - * Generate the PHP code for a function to match against a string for this rule - */ - function compile($indent) { - $function_name = $this->function_name( $this->name ) ; - - // Build the typestack - $typestack = array(); $class=$this; - do { - $typestack[] = $this->function_name($class->name); - } - while($class = $class->extends); - - $typestack = "array('" . implode("','", $typestack) . "')"; - - // Build an array of additional arguments to add to result node (if any) - if (empty($this->arguments)) { - $arguments = 'null'; - } - else { - $arguments = "array("; - foreach ($this->arguments as $k=>$v) { $arguments .= "'$k' => '$v'"; } - $arguments .= ")"; - } - - $match = PHPBuilder::build() ; - - $match->l("protected \$match_{$function_name}_typestack = $typestack;"); - - $match->b( "function match_{$function_name} (\$stack = array())", - '$matchrule = "'.$function_name.'"; $result = $this->construct($matchrule, $matchrule, '.$arguments.');', - $this->parsed->compile()->replace(array( - 'MATCH' => 'return $this->finalise($result);', - 'FAIL' => 'return FALSE;' - )) - ); - - $functions = array() ; - foreach( $this->functions as $name => $function ) { - $function_name = $this->function_name( preg_match( '/^_/', $name ?? '' ) ? $this->name.$name : $this->name.'_'.$name ) ; - $functions[] = implode( PHP_EOL, array( - 'function ' . $function_name . ' ' . $function - )); - } - - // print_r( $match ) ; return '' ; - return $match->render(NULL, $indent) . PHP_EOL . PHP_EOL . implode( PHP_EOL, $functions ) ; - } -} - -class RuleSet { - public $rules = array(); - - function addRule($indent, $lines, &$out) { - $rule = new Rule($this, $lines) ; - $this->rules[$rule->name] = $rule; - - $out[] = $indent . '/* ' . $rule->name . ':' . $rule->rule . ' */' . PHP_EOL ; - $out[] = $rule->compile($indent) ; - $out[] = PHP_EOL ; - } - - function compile($indent, $rulestr) { - $indentrx = '@^'.preg_quote($indent ?? '').'@'; - - $out = array(); - $block = array(); - - foreach (preg_split('/\r\n|\r|\n/', $rulestr ?? '') as $line) { - // Ignore blank lines - if (!trim($line ?? '')) continue; - // Ignore comments - if (preg_match('/^[\x20|\t]+#/', $line ?? '')) continue; - - // Strip off indent - if (!empty($indent)) { - if (strpos($line ?? '', $indent ?? '') === 0) $line = substr($line ?? '', strlen($indent ?? '')); - else user_error('Non-blank line with inconsistent index in parser block', E_USER_ERROR); - } - - // Any indented line, add to current set of lines - if (preg_match('/^\x20|\t/', $line ?? '')) $block[] = $line; - - // Any non-indented line marks a new block. Add a rule for the current block, then start a new block - else { - if (count($block ?? [])) $this->addRule($indent, $block, $out); - $block = array($line); - } - } - - // Any unfinished block add a rule for - if (count($block ?? [])) $this->addRule($indent, $block, $out); - - // And return the compiled version - return implode( '', $out ) ; - } -} - -class ParserCompiler { - - static $parsers = array(); - - static $debug = false; - - static $currentClass = null; - - static function create_parser( $match ) { - /* We allow indenting of the whole rule block, but only to the level of the comment start's indent */ - $indent = $match[1]; - - /* Get the parser name for this block */ - if ($class = trim($match[2] ?? '')) self::$currentClass = $class; - elseif (self::$currentClass) $class = self::$currentClass; - else $class = self::$currentClass = 'Anonymous Parser'; - - /* Check for pragmas */ - if (strpos($class ?? '', '!') === 0) { - switch ($class) { - case '!silent': - // NOP - dont output - return ''; - case '!insert_autogen_warning': - return $indent . implode(PHP_EOL.$indent, array( - '/*', - 'WARNING: This file has been machine generated. Do not edit it, or your changes will be overwritten next time it is compiled.', - '*/' - )) . PHP_EOL; - case '!debug': - self::$debug = true; - return ''; - } - - throw new Exception("Unknown pragma $class encountered when compiling parser"); - } - - if (!isset(self::$parsers[$class])) self::$parsers[$class] = new RuleSet(); - - return self::$parsers[$class]->compile($indent, $match[3]); - } - - static function compile( $string ) { - static $rx = '@ - ^([\x20\t]*)/\*!\* (?:[\x20\t]*(!?\w*))? # Start with some indent, a comment with the special marker, then an optional name - ((?:[^*]|\*[^/])*) # Any amount of "a character that isnt a star, or a star not followed by a / - \*/ # The comment end - @mx'; - - return preg_replace_callback( $rx ?? '', array( 'ParserCompiler', 'create_parser' ), $string ?? '' ) ; - } - - static function cli( $args ) { - if ( count( $args ?? [] ) == 1 ) { - print "Parser Compiler: A compiler for PEG parsers in PHP \n" ; - print "(C) 2009 SilverStripe. See COPYING for redistribution rights. \n" ; - print "\n" ; - print "Usage: {$args[0]} infile [ outfile ]\n" ; - print "\n" ; - } - else { - $fname = ( $args[1] == '-' ? 'php://stdin' : $args[1] ) ; - $string = file_get_contents( $fname ?? '' ) ; - $string = self::compile( $string ) ; - - if ( !empty( $args[2] ) && $args[2] != '-' ) { - file_put_contents( $args[2] ?? '', $string ) ; - } - else { - print $string ; - } - } - } -} diff --git a/thirdparty/php-peg/LICENSE b/thirdparty/php-peg/LICENSE deleted file mode 100644 index 5ae0128156d..00000000000 --- a/thirdparty/php-peg/LICENSE +++ /dev/null @@ -1,149 +0,0 @@ - -PHP-PEG is released under three licenses: MIT, BSD, and GPL. You may pick the license that best suits your development needs. The text of all three licenses are provided below. - -MIT License ----- - -Copyright (C) 2009 Hamish Friedlander (hamish@silverstripe.com) and SilverStripe Limited (www.silverstripe.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -BSD License ----- - -Copyright (C) 2009 Hamish Friedlander (hamish@silverstripe.com) and SilverStripe Limited (www.silverstripe.com) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY SilverStripe Limited ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -GPL License ----- - -The GNU General Public License (GPL) -Version 2, June 1991 - -Copyright (C) 1989, 1991 Free Software Foundation, Inc. -59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - -Preamble - -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. - -To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. - -For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. - -We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. - -Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. - -Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. - -The precise terms and conditions for copying, distribution and modification follow. - -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. - -1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. - -You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. - - c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - -3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - -If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - -4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - -5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. - -6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. - -7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - -This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - -8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - -9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. - -10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - -NO WARRANTY - -11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/thirdparty/php-peg/PHPBuilder.php b/thirdparty/php-peg/PHPBuilder.php deleted file mode 100644 index 3a1ebc00b05..00000000000 --- a/thirdparty/php-peg/PHPBuilder.php +++ /dev/null @@ -1,127 +0,0 @@ -lines = array() ; - } - - function l() { - foreach ( func_get_args() as $lines ) { - if ( !$lines ) continue ; - - if ( is_string( $lines ) ) $lines = preg_split( '/\r\n|\r|\n/', $lines ?? '' ) ; - if ( !$lines ) continue ; - - if ( $lines instanceof PHPBuilder ) $lines = $lines->lines ; - else $lines = array_map( 'ltrim', $lines ?? [] ) ; - if ( !$lines ) continue ; - - $this->lines = array_merge( $this->lines, $lines ) ; - } - return $this ; - } - - function b() { - $args = func_get_args() ; - $entry = array_shift( $args ) ; - - $block = new PHPBuilder() ; - call_user_func_array( array( $block, 'l' ), $args ?? [] ) ; - - $this->lines[] = array( $entry, $block->lines ) ; - - return $this ; - } - - function replace( $replacements, &$array = NULL ) { - if ( $array === NULL ) { - unset( $array ) ; - $array =& $this->lines ; - } - - $i = 0 ; - while ( $i < count( $array ?? [] ) ) { - - /* Recurse into blocks */ - if ( is_array( $array[$i] ) ) { - $this->replace( $replacements, $array[$i][1] ) ; - - if ( count( $array[$i][1] ?? [] ) == 0 ) { - $nextelse = isset( $array[$i+1] ) && is_array( $array[$i+1] ) && preg_match( '/^\s*else\s*$/i', $array[$i+1][0] ?? '' ) ; - - $delete = preg_match( '/^\s*else\s*$/i', $array[$i][0] ?? '' ) ; - $delete = $delete || ( preg_match( '/^\s*if\s*\(/i', $array[$i][0] ?? '' ) && !$nextelse ) ; - - if ( $delete ) { - // Is this always safe? Not if the expression has side-effects. - // print "/* REMOVING EMPTY BLOCK: " . $array[$i][0] . "*/\n" ; - array_splice( $array, $i ?? 0, 1 ) ; - continue ; - } - } - } - - /* Handle replacing lines with NULL to remove, or string, array of strings or PHPBuilder to replace */ - else { - if ( array_key_exists( $array[$i], $replacements ?? [] ) ) { - $rep = $replacements[$array[$i]] ; - - if ( $rep === NULL ) { - array_splice( $array, $i ?? 0, 1 ) ; - continue ; - } - - if ( is_string( $rep ) ) { - $array[$i] = $rep ; - $i++ ; - continue ; - } - - if ( $rep instanceof PHPBuilder ) $rep = $rep->lines ; - - if ( is_array( $rep ) ) { - array_splice( $array, $i ?? 0, 1, $rep ) ; $i += count( $rep ?? [] ) + 1 ; - continue ; - } - - throw 'Unknown type passed to PHPBuilder#replace' ; - } - } - - $i++ ; - } - - return $this ; - } - - function render( $array = NULL, $indent = "" ) { - if ( $array === NULL ) $array = $this->lines ; - - $out = array() ; - foreach( $array as $line ) { - if ( is_array( $line ) ) { - list( $entry, $block ) = $line ; - $str = $this->render( $block, $indent . "\t" ) ; - - if ( strlen( $str ?? '' ) < 40 ) { - $out[] = $indent . $entry . ' { ' . ltrim( $str ?? '' ) . ' }' ; - } - else { - $out[] = $indent . $entry . ' {' ; - $out[] = $str ; - $out[] = $indent . '}' ; - } - } - else { - $out[] = $indent . $line ; - } - } - - return implode( PHP_EOL, $out ) ; - } -} diff --git a/thirdparty/php-peg/Parser.php b/thirdparty/php-peg/Parser.php deleted file mode 100644 index bcb57ce06da..00000000000 --- a/thirdparty/php-peg/Parser.php +++ /dev/null @@ -1,319 +0,0 @@ -parser = $parser ; - $this->rx = $rx . 'Sx' ; - - $this->matches = NULL ; - $this->match_pos = NULL ; // NULL is no-match-to-end-of-string, unless check_pos also == NULL, in which case means undefined - $this->check_pos = NULL ; - } - - function match() { - $current_pos = $this->parser->pos ; - $dirty = $this->check_pos === NULL || $this->check_pos > $current_pos || ( $this->match_pos !== NULL && $this->match_pos < $current_pos ) ; - - if ( $dirty ) { - $this->check_pos = $current_pos ; - $matched = preg_match( $this->rx ?? '', $this->parser->string ?? '', $this->matches, PREG_OFFSET_CAPTURE, $this->check_pos ?? 0) ; - if ( $matched ) $this->match_pos = $this->matches[0][1] ; else $this->match_pos = NULL ; - } - - if ( $this->match_pos === $current_pos ) { - $this->parser->pos += strlen( $this->matches[0][0] ?? '' ); - return $this->matches[0][0] ; - } - - return FALSE ; - } -} - -/** - * Parser base class - * - handles current position in string - * - handles matching that position against literal or rx - * - some abstraction of code that would otherwise be repeated many times in a compiled grammer, mostly related to calling user functions - * for result construction and building - */ -class Parser { - /** - * @var string - */ - public $string; - - /** - * @var int - */ - public $pos; - - /** - * @var int - */ - public $depth; - - /** - * @var array - */ - public $regexps; - - function __construct( $string ) { - $this->string = $string ; - $this->pos = 0 ; - - $this->depth = 0 ; - - $this->regexps = array() ; - } - - function whitespace() { - $matched = preg_match( '/[ \t]+/', $this->string ?? '', $matches, PREG_OFFSET_CAPTURE, $this->pos ?? 0 ) ; - if ( $matched && $matches[0][1] == $this->pos ) { - $this->pos += strlen( $matches[0][0] ?? '' ); - return ' ' ; - } - return FALSE ; - } - - function literal( $token ) { - /* Debugging: * / print( "Looking for token '$token' @ '" . substr( $this->string, $this->pos ) . "'\n" ) ; /* */ - $toklen = strlen( $token ?? '' ) ; - $substr = substr( $this->string ?? '', $this->pos ?? 0, $toklen ) ; - if ( $substr == $token ) { - $this->pos += $toklen ; - return $token ; - } - return FALSE ; - } - - function rx( $rx ) { - if ( !isset( $this->regexps[$rx] ) ) $this->regexps[$rx] = new ParserRegexp( $this, $rx ) ; - return $this->regexps[$rx]->match() ; - } - - function expression( $result, $stack, $value ) { - $stack[] = $result; $rv = false; - - /* Search backwards through the sub-expression stacks */ - for ( $i = count($stack) - 1 ; $i >= 0 ; $i-- ) { - $node = $stack[$i]; - - if ( isset($node[$value]) ) { $rv = $node[$value]; break; } - - foreach ($this->typestack($node['_matchrule']) as $type) { - $callback = array($this, "{$type}_DLR{$value}"); - if ( is_callable( $callback ) ) { $rv = call_user_func( $callback ) ; if ($rv !== FALSE) break; } - } - } - - if ($rv === false) $rv = @$this->$value; - if ($rv === false) $rv = @$this->$value(); - - return is_array($rv) ? $rv['text'] : ($rv ? $rv : ''); - } - - function packhas( $key, $pos ) { - return false ; - } - - function packread( $key, $pos ) { - throw new \Exception('PackRead after PackHas=>false in Parser.php'); - } - - function packwrite( $key, $pos, $res ) { - return $res ; - } - - function typestack( $name ) { - $prop = "match_{$name}_typestack"; - return $this->$prop; - } - - function construct( $matchrule, $name, $arguments = null ) { - $result = array( '_matchrule' => $matchrule, 'name' => $name, 'text' => '' ); - if ($arguments) $result = array_merge($result, $arguments) ; - - foreach ($this->typestack($matchrule) as $type) { - $callback = array( $this, "{$type}__construct" ) ; - if ( is_callable( $callback ) ) { - call_user_func_array( $callback, array( &$result ) ) ; - break; - } - } - - return $result ; - } - - function finalise( &$result ) { - foreach ($this->typestack($result['_matchrule']) as $type) { - $callback = array( $this, "{$type}__finalise" ) ; - if ( is_callable( $callback ) ) { - call_user_func_array( $callback, array( &$result ) ) ; - break; - } - } - - return $result ; - } - - function store ( &$result, $subres, $storetag = NULL ) { - $result['text'] .= $subres['text'] ; - - $storecalled = false; - - foreach ($this->typestack($result['_matchrule']) as $type) { - $callback = array( $this, $storetag ? "{$type}_{$storetag}" : "{$type}_{$subres['name']}" ) ; - if ( is_callable( $callback ) ) { - call_user_func_array( $callback, array( &$result, $subres ) ) ; - $storecalled = true; break; - } - - $globalcb = array( $this, "{$type}_STR" ) ; - if ( is_callable( $globalcb ) ) { - call_user_func_array( $globalcb, array( &$result, $subres ) ) ; - $storecalled = true; break; - } - } - - if ( $storetag && !$storecalled ) { - if ( !isset( $result[$storetag] ) ) $result[$storetag] = $subres ; - else { - if ( isset( $result[$storetag]['text'] ) ) $result[$storetag] = array( $result[$storetag] ) ; - $result[$storetag][] = $subres ; - } - } - } -} - -/** - * By inheriting from Packrat instead of Parser, the parser will run in linear time (instead of exponential like - * Parser), but will require a lot more memory, since every match-attempt at every position is memorised. - * - * We now use a string as a byte-array to store position information rather than a straight array for memory reasons. This - * means there is a (roughly) 8MB limit on the size of the string we can parse - * - * @author Hamish Friedlander - */ -class Packrat extends Parser { - function __construct( $string ) { - parent::__construct( $string ) ; - - $max = unpack( 'N', "\x00\xFD\xFF\xFF" ) ; - if ( strlen( $string ?? '' ) > $max[1] ) user_error( 'Attempting to parse string longer than Packrat Parser can handle', E_USER_ERROR ) ; - - $this->packstatebase = str_repeat( "\xFF", strlen( $string ?? '' )*3 ) ; - $this->packstate = array() ; - $this->packres = array() ; - } - - function packhas( $key, $pos ) { - $pos *= 3 ; - return isset( $this->packstate[$key] ) && $this->packstate[$key][$pos] != "\xFF" ; - } - - function packread( $key, $pos ) { - $pos *= 3 ; - if ( $this->packstate[$key][$pos] == "\xFE" ) return FALSE ; - - $this->pos = ord($this->packstate[$key][$pos] ?? '') << 16 | ord($this->packstate[$key][$pos+1] ?? '') << 8 | ord($this->packstate[$key][$pos+2] ?? '') ; - return $this->packres["$key:$pos"] ; - } - - function packwrite( $key, $pos, $res ) { - if ( !isset( $this->packstate[$key] ) ) $this->packstate[$key] = $this->packstatebase ; - - $pos *= 3 ; - - if ( $res !== FALSE ) { - $i = pack( 'N', $this->pos ) ; - - $this->packstate[$key][$pos] = $i[1] ; - $this->packstate[$key][$pos+1] = $i[2] ; - $this->packstate[$key][$pos+2] = $i[3] ; - - $this->packres["$key:$pos"] = $res ; - } - else { - $this->packstate[$key][$pos] = "\xFE" ; - } - - return $res ; - } -} - -/** - * FalseOnlyPackrat only remembers which results where false. Experimental. - * - * @author Hamish Friedlander - */ -class FalseOnlyPackrat extends Parser { - function __construct( $string ) { - parent::__construct( $string ) ; - - $this->packstatebase = str_repeat( '.', strlen( $string ?? '' ) ) ; - $this->packstate = array() ; - } - - function packhas( $key, $pos ) { - return isset( $this->packstate[$key] ) && $this->packstate[$key][$pos] == 'F' ; - } - - function packread( $key, $pos ) { - return FALSE ; - } - - function packwrite( $key, $pos, $res ) { - if ( !isset( $this->packstate[$key] ) ) $this->packstate[$key] = $this->packstatebase ; - - if ( $res === FALSE ) { - $this->packstate[$key][$pos] = 'F' ; - } - - return $res ; - } -} - -/** - * Conservative Packrat will only memo-ize a result on the second hit, making it more memory-lean than Packrat, - * but less likely to go exponential that Parser. Because the store logic is much more complicated this is a net - * loss over Parser for many simple grammars. - * - * @author Hamish Friedlander - */ -class ConservativePackrat extends Parser { - function packhas( $key, $pos ) { - return isset( $this->packres[$key] ) && $this->packres[$key] !== NULL ; - } - - function packread( $key, $pos ) { - $this->pos = $this->packpos[$key]; - return $this->packres[$key] ; - } - - function packwrite( $key, $pos, $res ) { - if ( isset( $this->packres[$key] ) ) { - $this->packres[$key] = $res ; - $this->packpos[$key] = $this->pos ; - } - else { - $this->packres[$key] = NULL ; - } - return $res ; - } -} - diff --git a/thirdparty/php-peg/README.md b/thirdparty/php-peg/README.md deleted file mode 100644 index 807b3b73d2b..00000000000 --- a/thirdparty/php-peg/README.md +++ /dev/null @@ -1,329 +0,0 @@ -# PHP PEG - A PEG compiler for parsing text in PHP - -This is a Paring Expression Grammar compiler for PHP. PEG parsers are an alternative to other CFG grammars that includes both tokenization -and lexing in a single top down grammar. For a basic overview of the subject, see http://en.wikipedia.org/wiki/Parsing_expression_grammar - -## Quick start - -- Write a parser. A parser is a PHP class with a grammar contained within it in a special syntax. The filetype is .peg.inc. See the examples directory. -- Compile the parser. php ./cli.php ExampleParser.peg.inc > ExampleParser.php -- Use the parser (you can also include code to do this in the input parser - again see the examples directory): - -

-	$x = new ExampleParser( 'string to parse' ) ;
-	$res = $x->match_Expr() ;
-
- -### Parser Format - -Parsers are contained within a PHP file, in one or more special comment blocks that start with `/*!* [name | !pragma]` (like a docblock, but with an -exclamation mark in the middle of the stars) - -You can have multiple comment blocks, all of which are treated as contiguous for the purpose of compiling. During compilation these blocks will be replaced -with a set of "matching" functions (functions which match a string against their rules) for each rule in the block. - -The optional name marks the start of a new set of parser rules. This is currently unused, but might be used in future for opimization & debugging purposes. -If unspecified, it defaults to the same name as the previous parser comment block, or 'Anonymous Parser' if no name has ever been set. - -If the name starts with an '!' symbol, that comment block is a pragma, and is treated not as some part of the parser, but as a special block of meta-data - -Lexically, these blocks are a set of rules & comments. A rule can be a base rule or an extension rule - -##### Base rules - -Base rules consist of a name for the rule, some optional arguments, the matching rule itself, and an optional set of attached functions - -NAME ( "(" ARGUMENT, ... ")" )? ":" MATCHING_RULE - ATTACHED_FUNCTIONS? - -Names must be the characters a-z, A-Z, 0-9 and _ only, and must not start with a number - -Base rules can be split over multiple lines as long as subsequent lines are indented - -##### Extension rules - -Extension rules are either the same as a base rule but with an addition name of the rule to extend, or as a replacing extension consist of -a name for the rule, the name of the rule to extend, and optionally: some arguments, some replacements, and a set of attached functions - -NAME extend BASENAME ( "(" ARGUMENT, ... ")" )? ":" MATCHING_RULE - ATTACHED_FUNCTIONS? - -NAME extends BASENAME ( "(" ARGUMENT, ... ")" )? ( ";" REPLACE "=>" REPLACE_WITH, ... )? - ATTACHED_FUNCTIONS? - -##### Tricks and traps - -We allow indenting a parser block, but only in a consistant manner - whatever the indent of the /*** marker becomes the "base" indent, and needs to be used -for all lines. You can mix tabs and spaces, but the indent must always be an exact match - if the "base" indent is a tab then two spaces, every line within the -block also needs indenting with a tab then two spaces, not two tabs (even if in your editor, that gives the same indent). - -Any line with more than the "base" indent is considered a continuation of the previous rule - -Any line with less than the "base" indent is an error - -This might get looser if I get around to re-writing the internal "parser parser" in php-peg, bootstrapping the whole thing - -### Rules - -PEG matching rules try to follow standard PEG format, summarised thusly: - -

-	token* - Token is optionally repeated
-	token+ - Token is repeated at least one
-	token? - Token is optionally present
-
-	tokena tokenb - Token tokenb follows tokena, both of which are present
-	tokena | tokenb - One of tokena or tokenb are present, prefering tokena
-
-	&token - Token is present next (but not consumed by parse)
-	!token - Token is not present next (but not consumed by parse)
-
- 	( expression ) - Grouping for priority
-
- -But with these extensions: - -

-	< or > - Optionally match whitespace
-	[ or ] - Require some whitespace
-
- -### Tokens - -Tokens may be - - - bare-words, which are recursive matchers - references to token rules defined elsewhere in the grammar, - - literals, surrounded by `"` or `'` quote pairs. No escaping support is provided in literals. - - regexs, surrounded by `/` pairs. - - expressions - single words (match \w+) starting with `$` or more complex surrounded by `${ }` which call a user defined function to perform the match - -##### Regular expression tokens - -Automatically anchored to the current string start - do not include a string start anchor (`^`) anywhere. Always acts as when the 'x' flag is enabled in PHP - -whitespace is ignored unless escaped, and '#' stats a comment. - -Be careful when ending a regular expression token - the '*/' pattern (as in /foo\s*/) will end a PHP comment. Since the 'x' flag is always active, -just split with a space (as in / foo \s* /) - -### Expressions - -Expressions allow run-time calculated matching. You can embed an expression within a literal or regex token to -match against a calculated value, or simply specify the expression as a token to match against a dynamic rule. - -#### Expression stack - -When getting a value to use for an expression, the parser will travel up the stack looking for a set value. The expression -stack is a list of all the rules passed through to get to this point. For example, given the parser - -

-	A: $a
-	B: A
-	C: B
-
- -The expression stack for finding $a will be C, B, A - in other words, the A rule will be checked first, followed by B, followed by C - -#### In terminals (literals and regexes) - -The token will be replaced by the looked up value. To find the value for the token, the expression stack will be -travelled up checking for one of the following: - - - A key / value pair in the result array node - - A rule-attached method INCLUDING `$` ( i.e. `function $foo()` ) - -If no value is found it will then check if a method or a property excluding the $ exists on the parser. If neither of those is found -the expression will be replaced with an exmpty string/ - -#### As tokens - -The token will be looked up to find a value, which must be the name of a matching rule. That rule will then be matched -against as if the token was a recurse token for that rule. - -To find the name of the rule to match against, the expression stack will be travelled up checking for one of the following: - - - A key / value pair in the result array node - - A rule-attached method INCLUDING `$` ( i.e. `function $foo()` ) - -If no value is found it will then check if a method or a property excluding the $ exists on the parser. If neither of those if found -the rule will fail to match. - -#### Tricks and traps - -Be careful against using a token expression when you meant to use a terminal expression - -

-	quoted_good: q:/['"]/ string "$q"
-	quoted_bad:  q:/['"]/ string $q
-
- -`"$q"` matches against the value of q again. `$q` tries to match against a rule named `"` or `'` (both of which are illegal rule -names, and will therefore fail) - -### Named matching rules - -Tokens and groups can be given names by prepending name and `:`, e.g., - -

-	rulea: "'" name:( tokena tokenb )* "'"
-
- -There must be no space betweeen the name and the `:` - -

-	badrule: "'" name : ( tokena tokenb )* "'"
-
- -Recursive matchers can be given a name the same as their rule name by prepending with just a `:`. These next two rules are equivilent - -

-	rulea: tokena tokenb:tokenb
-	rulea: tokena :tokenb
-
- -### Rule-attached functions - -Each rule can have a set of functions attached to it. These functions can be defined - -- in-grammar by indenting the function body after the rule -- in-class after close of grammar comment by defining a regular method who's name is `{$rulename}_{$functionname}`, or `{$rulename}{$functionname}` if function name starts with `_` -- in a sub class - -All functions that are not in-grammar must have PHP compatible names (see PHP name mapping). In-grammar functions will have their names converted if needed. - -All these definitions define the same rule-attached function - -

-	class A extends Parser {
-		/*!* Parser
-		foo: bar baz
-			function bar() {}
-		*/
-
-		function foo_bar() {}
-	}
-
-	class B extends A {
-		function foo_bar() {}
-	}
-
- -### PHP name mapping - -Rules in the grammar map to php functions named `match_{$rulename}`. However rule names can contain characters that php functions can't. -These characters are remapped: - -

-	'-' => '_'
-	'$' => 'DLR'
-	'*' => 'STR'
-
- -Other dis-allowed characters are removed. - -## Results - -Results are a tree of nested arrays. - -Without any specific control, each rules result will just be the text it matched against in a `['text']` member. This member must always exist. - -Marking a subexpression, literal, regex or recursive match with a name (see Named matching rules) will insert a member into the -result array named that name. If there is only one match it will be a single result array. If there is more than one match it will be an array of arrays. - -You can override result storing by specifying a rule-attached function with the given name. It will be called with a reference to the current result array -and the sub-match - in this case the default storage action will not occur. - -If you specify a rule-attached function for a recursive match, you do not need to name that token at all - it will be call automatically. E.g. - -

-	rulea: tokena tokenb
-	  function tokenb ( &$res, $sub ) { print 'Will be called, even though tokenb is not named or marked with a :' ; }
-
- -You can also specify a rule-attached function called `*`, which will be called with every recursive match made - -

-	rulea: tokena tokenb
-	  function * ( &$res, $sub ) { print 'Will be called for both tokena and tokenb' ; }
-
- -### Silent matches - -By default all matches are added to the 'text' property of a result. By prepending a member with `.` that match will not be added to the ['text'] member. This -doesn't affect the other result properties that named rules' add. - -### Inheritance - -Rules can inherit off other rules using the keyword extends. There are several ways to change the matching of the rule, but -they all share a common feature - when building a result set the rule will also check the inherited-from rule's rule-attached -functions for storage handlers. This lets you do something like - -

-A: Foo Bar Baz
-  function *(){ /* Generic store handler */ }
-  
-B extends A
-  function Bar(){ /* Custom handling for Bar - Foo and Baz will still fall through to the A#* function defined above */ }
-
- -The actual matching rule can be specified in three ways: - -#### Duplication - -If you don't specify a new rule or a replacement set the matching rule is copied as is. This is useful when you want to -override some storage logic but not the rule itself - -#### Text replacement - -You can replace some parts of the inherited rule using test replacement by using a ';' instead of an ':' after the name - of the extended rule. You can then put replacements in a comma seperated list. An example might help - -

-A: Foo | Bar | Baz
-
-# Makes B the equivalent of Foo | Bar | (Baz | Qux)
-B extends A: Baz => (Baz | Qux)
-
- -Note that the replacements are not quoted. The exception is when you want to replace with the empty string, e.g. - -

-A: Foo | Bar | Baz
-
-# Makes B the equivalent of Foo | Bar
-B extends A: | Baz => ""
-
- -Currently there is no escaping supported - if you want to replace "," or "=>" characters you'll have to use full replacement - -#### Full replacement - -You can specify an entirely new rule in the same format as a non-inheriting rule, eg. - -

-A: Foo | Bar | Baz
-
-B extends A: Foo | Bar | (Baz Qux)
-
- -This is useful is the rule changes too much for text replacement to be readable, but want to keep the storage logic - -### Pragmas - -When opening a parser comment block, if instead of a name (or no name) you put a word starting with '!', that comment block is treated as a pragma - not -part of the parser language itself, but some other instruction to the compiler. These pragmas are currently understood: - - !silent - - This is a comment that should only appear in the source code. Don't output it in the generated code - - !insert_autogen_warning - - Insert a warning comment into the generated code at this point, warning that the file is autogenerated and not to edit it - -## TODO - -- Allow configuration of whitespace - specify what matches, and wether it should be injected into results as-is, collapsed, or not at all -- Allow inline-ing of rules into other rules for speed -- More optimisation -- Make Parser-parser be self-generated, instead of a bad hand rolled parser like it is now. -- PHP token parser, and other token streams, instead of strings only like now diff --git a/thirdparty/php-peg/cli.php b/thirdparty/php-peg/cli.php deleted file mode 100644 index ab979615d45..00000000000 --- a/thirdparty/php-peg/cli.php +++ /dev/null @@ -1,5 +0,0 @@ -