diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e96e09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# Dependencies
+node_modules/
+package-lock.json
+
+# Build outputs
+*.log
+.DS_Store
+Thumbs.db
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Cache
+.cache/
+.sass-cache/
+
+# Temporary files
+*.tmp
+*.temp
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..90848ce
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "mustache/mustache": "^3.0"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..582bf01
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,72 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "44b7b2c58a30151ae57314c84e2abdd5",
+ "packages": [
+ {
+ "name": "mustache/mustache",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bobthecow/mustache.php.git",
+ "reference": "176b6b21d68516dd5107a63ab71b0050e518b7a4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/176b6b21d68516dd5107a63ab71b0050e518b7a4",
+ "reference": "176b6b21d68516dd5107a63ab71b0050e518b7a4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.19.3",
+ "yoast/phpunit-polyfills": "^2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Mustache\\": "src/"
+ },
+ "classmap": [
+ "src/compat.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Justin Hileman",
+ "email": "justin@justinhileman.info",
+ "homepage": "http://justinhileman.com"
+ }
+ ],
+ "description": "A Mustache implementation in PHP.",
+ "homepage": "https://github.com/bobthecow/mustache.php",
+ "keywords": [
+ "mustache",
+ "templating"
+ ],
+ "support": {
+ "issues": "https://github.com/bobthecow/mustache.php/issues",
+ "source": "https://github.com/bobthecow/mustache.php/tree/v3.0.0"
+ },
+ "time": "2025-06-28T18:28:20+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.9.0"
+}
diff --git a/composer.phar b/composer.phar
new file mode 100755
index 0000000..02740c5
Binary files /dev/null and b/composer.phar differ
diff --git a/config.php b/config.php
deleted file mode 100644
index efde3e6..0000000
--- a/config.php
+++ /dev/null
@@ -1,16 +0,0 @@
- 'CodePress',
- 'site_description' => 'A simple PHP CMS',
- 'base_url' => '/',
- 'content_dir' => __DIR__ . '/public/content',
- 'templates_dir' => __DIR__ . '/templates',
- 'cache_dir' => __DIR__ . '/cache',
- 'default_page' => 'home',
- 'error_404' => '404',
- 'markdown_enabled' => true,
- 'php_enabled' => true,
- 'bootstrap_version' => '5.3.0',
- 'jquery_version' => '3.7.1'
-];
\ No newline at end of file
diff --git a/engine/assets/css/style.scss b/engine/assets/css/style.scss
new file mode 100644
index 0000000..b1cb023
--- /dev/null
+++ b/engine/assets/css/style.scss
@@ -0,0 +1,246 @@
+// SCSS variables
+$primary: #0d6efd;
+$secondary: #6c757d;
+$success: #198754;
+$info: #0dcaf0;
+$warning: #ffc107;
+$danger: #dc3545;
+$light: #f8f9fa;
+$dark: #212529;
+
+// SCSS mixins
+@mixin transition($properties...) {
+ transition: $properties;
+}
+
+// Custom styles
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ position: relative;
+}
+
+.main-wrapper {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ min-height: calc(100vh - 70px);
+}
+
+.content-wrapper {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+.sidebar {
+ width: 250px;
+ background-color: #f8f9fa;
+ border-right: 1px solid #dee2e6;
+ overflow-y: auto;
+ flex-shrink: 0;
+ @include transition(transform 0.3s ease);
+ position: fixed;
+ top: 70px;
+ left: 0;
+ height: calc(100vh - 140px);
+ z-index: 999;
+ transform: translateX(0);
+}
+
+.sidebar.collapsed {
+ transform: translateX(-250px);
+}
+
+.sidebar-toggle {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ z-index: 1001;
+ background: none;
+ border: none;
+ cursor: pointer;
+ @include transition(all 0.3s ease);
+ font-size: 20px;
+ color: #6c757d;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+
+ &:hover {
+ color: #0d6efd;
+ background-color: #f8f9fa;
+ }
+}
+
+.sidebar-toggle-outer {
+ position: relative;
+ z-index: 1001;
+ background-color: rgba(255, 255, 255, 0.2);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ margin-right: 10px;
+ color: white !important;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+ color: white !important;
+ }
+}
+
+.sidebar.collapsed .sidebar-toggle-inner {
+ right: auto;
+ left: 15px;
+}
+
+.sidebar.collapsed ~ .main-content {
+ margin-left: 0;
+}
+
+.sidebar.collapsed .sidebar-toggle-outer {
+ display: block !important;
+}
+
+.sidebar:not(.collapsed) .sidebar-toggle-outer {
+ display: none !important;
+}
+
+.main-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+ @include transition(margin-left 0.3s ease);
+ margin-left: 250px;
+}
+
+.folder-toggle {
+ font-weight: bold;
+ color: #212529 !important;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ padding: 0.5rem 0.75rem;
+ background-color: #f8f9fa !important;
+ margin: 0.125rem 0;
+ @include transition(background-color 0.2s ease);
+
+ &:hover {
+ background-color: #e9ecef !important;
+ }
+
+ &[aria-expanded=true] {
+ background-color: #dee2e6 !important;
+ color: #212529 !important;
+ font-weight: 600;
+ }
+
+ .arrow {
+ margin-right: 8px;
+ @include transition(transform 0.2s);
+ font-size: 0.8em;
+ }
+
+ &[aria-expanded=true] .arrow {
+ transform: rotate(90deg);
+ }
+}
+
+.page-link {
+ color: #495057 !important;
+ font-weight: 500;
+ padding: 0.5rem 0.75rem;
+ padding-left: 2rem;
+ background-color: #ffffff !important;
+ margin: 0.125rem 0;
+ @include transition(background-color 0.2s ease);
+
+ &:hover {
+ color: #212529 !important;
+ background-color: #f8f9fa !important;
+ }
+
+ &.active {
+ background-color: #0d6efd !important;
+ color: #212529 !important;
+ font-weight: 600;
+ }
+}
+
+.file-info {
+ font-size: 0.9rem;
+ color: #6c757d;
+}
+
+.site-info a {
+ color: #0d6efd;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.auto-link {
+ color: #0d6efd;
+ text-decoration: none;
+ border-bottom: 2px dashed #0d6efd;
+ font-weight: 500;
+ @include transition(all 0.2s ease);
+
+ &:hover {
+ color: #0a58ca;
+ text-decoration: none;
+ border-bottom-style: solid;
+ border-bottom-color: #0a58ca;
+ }
+}
+
+.search-form {
+ max-width: 300px;
+}
+
+.card-title a {
+ text-decoration: none;
+ color: inherit;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.folder-disabled {
+ font-weight: 500;
+ color: #6c757d !important;
+ cursor: not-allowed;
+ display: flex;
+ align-items: center;
+ padding: 0.5rem 0.75rem;
+ background-color: #f8f9fa !important;
+ margin: 0.125rem 0;
+ opacity: 0.7;
+
+ .arrow {
+ margin-right: 8px;
+ font-size: 0.8em;
+ opacity: 0.6;
+ }
+}
+
+@media (max-width: 768px) {
+ .sidebar {
+ width: 200px;
+ }
+}
\ No newline at end of file
diff --git a/engine/core/config.php b/engine/core/config.php
index 76139ed..e60653b 100644
--- a/engine/core/config.php
+++ b/engine/core/config.php
@@ -1,16 +1,8 @@
'CodePress',
- 'site_description' => 'A simple PHP CMS',
- 'base_url' => '/',
'content_dir' => __DIR__ . '/../../content',
'templates_dir' => __DIR__ . '/../templates',
- 'cache_dir' => __DIR__ . '/../../cache',
- 'default_page' => 'home',
- 'error_404' => '404',
- 'markdown_enabled' => true,
- 'php_enabled' => true,
- 'bootstrap_version' => '5.3.0',
- 'jquery_version' => '3.7.1'
+ 'default_page' => 'home'
];
\ No newline at end of file
diff --git a/engine/core/index.php b/engine/core/index.php
index 4f6a101..4e901fa 100644
--- a/engine/core/index.php
+++ b/engine/core/index.php
@@ -2,6 +2,52 @@
require_once 'config.php';
+// Simple template rendering without Mustache for now
+class SimpleTemplate {
+ public static function render($template, $data) {
+ // Handle conditional blocks first
+ foreach ($data as $key => $value) {
+ if (is_array($value) || (is_string($value) && !empty($value))) {
+ // Handle {{#key}}...{{/key}} blocks
+ $pattern = '/{{#' . preg_quote($key, '/') . '}}(.*?){{\/' . preg_quote($key, '/') . '}}/s';
+ if (preg_match($pattern, $template, $matches)) {
+ $replacement = $matches[1];
+ $template = preg_replace($pattern, $replacement, $template);
+ }
+
+ // Handle {{^key}}...{{/key}} blocks (negative condition)
+ $pattern = '/{{\^' . preg_quote($key, '/') . '}}(.*?){{\/' . preg_quote($key, '/') . '}}/s';
+ $template = preg_replace($pattern, '', $template);
+ } else {
+ // Handle empty blocks
+ $pattern = '/{{#' . preg_quote($key, '/') . '}}.*?{{\/' . preg_quote($key, '/') . '}}/s';
+ $template = preg_replace($pattern, '', $template);
+
+ // Handle {{^key}}...{{/key}} blocks (show when empty)
+ $pattern = '/{{\^' . preg_quote($key, '/') . '}}(.*?){{\/' . preg_quote($key, '/') . '}}/s';
+ if (preg_match_all($pattern, $template, $matches)) {
+ foreach ($matches[1] as $match) {
+ $template = preg_replace('/{{\^' . preg_quote($key, '/') . '}}.*?{{\/' . preg_quote($key, '/') . '}}/s', $match, $template, 1);
+ }
+ }
+ }
+ }
+
+ // Handle variable replacements
+ foreach ($data as $key => $value) {
+ // Handle triple braces for unescaped HTML content
+ if (strpos($template, '{{{' . $key . '}}}') !== false) {
+ $template = str_replace('{{{' . $key . '}}}', $value, $template);
+ }
+ // Handle double braces for escaped content
+ elseif (strpos($template, '{{' . $key . '}}') !== false) {
+ $template = str_replace('{{' . $key . '}}', htmlspecialchars($value, ENT_QUOTES, 'UTF-8'), $template);
+ }
+ }
+ return $template;
+ }
+}
+
$config = include 'config.php';
class CodePressCMS {
@@ -104,6 +150,16 @@ class CodePressCMS {
return $this->getSearchResults();
}
+ // Check if guide is requested
+ if (isset($_GET['guide'])) {
+ return $this->getGuidePage();
+ }
+
+ // Check if content directory is empty
+ if ($this->isContentDirEmpty()) {
+ return $this->getGuidePage();
+ }
+
$page = $_GET['page'] ?? $this->config['default_page'];
$page = preg_replace('/\.[^.]+$/', '', $page);
@@ -348,6 +404,46 @@ private function autoLinkPageTitles($content) {
];
}
+ private function isContentDirEmpty() {
+ $contentDir = $this->config['content_dir'];
+ if (!is_dir($contentDir)) {
+ return true;
+ }
+
+ $files = scandir($contentDir);
+ $files = array_diff($files, ['.', '..']);
+
+ return empty($files);
+ }
+
+ private function getGuidePage() {
+ $lang = $this->detectLanguage();
+ $guideFile = __DIR__ . '/../../guide/' . $lang . '.md';
+
+ if (!file_exists($guideFile)) {
+ $guideFile = __DIR__ . '/../../guide/en.md'; // Fallback to English
+ }
+
+ $content = file_get_contents($guideFile);
+ $result = $this->parseMarkdown($content);
+
+ // Set special title for guide
+ $result['title'] = 'Handleiding - CodePress CMS';
+
+ return $result;
+ }
+
+ private function detectLanguage() {
+ // Simple language detection based on browser Accept-Language header
+ $acceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
+
+ if (strpos($acceptLanguage, 'nl') !== false) {
+ return 'nl';
+ }
+
+ return 'en'; // Default to English
+ }
+
private function getError404() {
return [
'title' => 'Page Not Found',
@@ -364,27 +460,59 @@ private function autoLinkPageTitles($content) {
$menu = $this->getMenu();
$breadcrumb = $this->getBreadcrumb();
- $template = file_get_contents($this->config['templates_dir'] . '/layout.html');
-
- $template = str_replace('{{site_title}}', $this->config['site_title'], $template);
- $template = str_replace('{{page_title}}', $page['title'], $template);
- $template = str_replace('{{content}}', $page['content'], $template);
- $template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template);
- $template = str_replace('{{breadcrumb}}', $breadcrumb, $template);
+ // Prepare template data
+ $templateData = [
+ 'site_title' => $this->config['site_title'],
+ 'page_title' => htmlspecialchars($page['title']),
+ 'content' => $page['content'],
+ 'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
+ 'menu' => $this->renderMenu($menu),
+ 'breadcrumb' => $breadcrumb,
+ 'default_page' => $this->config['default_page']
+ ];
// File info for footer
- $fileInfo = '';
if (isset($page['file_info'])) {
- $fileInfo = ' Created: ' . htmlspecialchars($page['file_info']['created']) .
- ' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
+ $templateData['file_info'] = 'Created: ' . htmlspecialchars($page['file_info']['created']) .
+ ' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
+ } else {
+ $templateData['file_info'] = '';
}
- $template = str_replace('{{file_info}}', $fileInfo, $template);
- $menuHtml = $this->renderMenu($menu);
+ // Check if content exists for guide link
+ $hasContent = !$this->isContentDirEmpty();
+ $templateData['has_content'] = $hasContent;
- $template = str_replace('{{menu}}', $menuHtml, $template);
+ // Don't show site title link on guide page
+ $templateData['show_site_link'] = !$this->isContentDirEmpty() && !isset($_GET['guide']);
- echo $template;
+ // Load partials manually
+ $hasContent = !$this->isContentDirEmpty() && !isset($_GET['guide']);
+
+ $headerContent = file_get_contents($this->config['templates_dir'] . '/assets/header.mustache');
+ if (!$hasContent) {
+ // Remove the link from header when no content
+ $headerContent = preg_replace('/\s*
', $body);
- $body = '
' . $body . '
';
- $body = preg_replace('/<\/p>/', '', $body);
- $body = preg_replace('/
()/', '$1', $body);
- $body = preg_replace('/(<\/h[1-6]>)<\/p>/', '$1', $body);
-
- return [
- 'title' => $title ?: 'Untitled',
- 'content' => $body
- ];
- }
-
- private function parsePHP($filePath) {
- ob_start();
- $title = 'Untitled';
- include $filePath;
- $content = ob_get_clean();
-
- return [
- 'title' => $title,
- 'content' => $content
- ];
- }
-
- private function parseHTML($content) {
- $title = 'Untitled';
-
- if (preg_match('/(.*?)<\/title>/i', $content, $matches)) {
- $title = strip_tags($matches[1]);
- } elseif (preg_match('/]*>(.*?)<\/h1>/i', $content, $matches)) {
- $title = strip_tags($matches[1]);
- }
-
- return [
- 'title' => $title,
- 'content' => $content
- ];
- }
-
- private function getError404() {
- return [
- 'title' => 'Page Not Found',
- 'content' => '404 - Page Not Found
The page you are looking for does not exist.
'
- ];
- }
-
- public function getMenu() {
- return $this->menu;
- }
-
- public function render() {
- $page = $this->getPage();
- $menu = $this->getMenu();
- $breadcrumb = $this->getBreadcrumb();
-
- $template = file_get_contents($this->config['templates_dir'] . '/layout.html');
-
- $template = str_replace('{{site_title}}', $this->config['site_title'], $template);
- $template = str_replace('{{page_title}}', $page['title'], $template);
- $template = str_replace('{{content}}', $page['content'], $template);
- $template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template);
- $template = str_replace('{{breadcrumb}}', $breadcrumb, $template);
-
- // File info for footer
- $fileInfo = '';
- if (isset($page['file_info'])) {
- $fileInfo = ' Created: ' . htmlspecialchars($page['file_info']['created']) .
- ' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
- }
- $template = str_replace('{{file_info}}', $fileInfo, $template);
-
- $menuHtml = $this->renderMenu($menu);
-
- $template = str_replace('{{menu}}', $menuHtml, $template);
-
- echo $template;
- }
-
- private function getBreadcrumb() {
- if (isset($_GET['search'])) {
- return '';
- }
-
- $page = $_GET['page'] ?? $this->config['default_page'];
- $page = preg_replace('/\.[^.]+$/', '', $page);
-
- if ($page === $this->config['default_page']) {
- return '';
- }
-
- $parts = explode('/', $page);
- $breadcrumb = '';
- return $breadcrumb;
- }
-
- private function renderMenu($items, $level = 0) {
- $html = '';
- foreach ($items as $item) {
- if ($item['type'] === 'folder') {
- $hasChildren = !empty($item['children']);
- $html .= '';
-
- if ($hasChildren) {
- $folderId = 'folder-' . str_replace('/', '-', $item['path']);
- $html .= '';
- $html .= ' ' . htmlspecialchars($item['title']);
- $html .= '';
- $html .= '';
- $html .= $this->renderMenu($item['children'], $level + 1);
- $html .= '
';
- } else {
- $html .= '';
- $html .= ' ' . htmlspecialchars($item['title']);
- $html .= '';
- }
-
- $html .= '';
- } else {
- $active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
- $html .= '';
- $html .= '' . htmlspecialchars($item['title']) . '';
- $html .= '';
- }
- }
- return $html;
- }
-}
-
// Block direct access to content files
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
if (strpos($requestUri, '/content/') !== false) {
diff --git a/server.log b/server.log
deleted file mode 100644
index c548f93..0000000
--- a/server.log
+++ /dev/null
@@ -1 +0,0 @@
-[Wed Nov 19 17:58:28 2025] Failed to listen on localhost:8080 (reason: Address already in use)
diff --git a/templates/layout.html b/templates/layout.html
deleted file mode 100644
index 4539bd4..0000000
--- a/templates/layout.html
+++ /dev/null
@@ -1,381 +0,0 @@
-
-
-
-
-
- {{page_title}} - {{site_title}}
-
-
-
-
-
-
-
-
-
-
-
-

-
{{site_title}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{breadcrumb}}
-
-
-
{{page_title}}
-
-
- {{content}}
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000..504385e
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,22 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..2052022
--- /dev/null
+++ b/vendor/composer/InstalledVersions.php
@@ -0,0 +1,396 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+ * @internal
+ */
+ private static $selfDir = null;
+
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return string
+ */
+ private static function getSelfDir()
+ {
+ if (self::$selfDir === null) {
+ self::$selfDir = strtr(__DIR__, '\\', '/');
+ }
+
+ return self::$selfDir;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+ $copiedLocalDir = false;
+
+ if (self::$canGetVendors) {
+ $selfDir = self::getSelfDir();
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\\', '/');
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
+ }
+ }
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array() && !$copiedLocalDir) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..4e90e56
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,43 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+ 'Mustache_Cache' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_AbstractCache' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_FilesystemCache' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_NoopCache' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Compiler' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Context' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Engine' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_InvalidArgumentException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_LogicException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_RuntimeException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_SyntaxException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownFilterException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownHelperException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownTemplateException' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_HelperCollection' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_LambdaHelper' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_ArrayLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_CascadingLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_FilesystemLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_InlineLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_MutableLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_ProductionFilesystemLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_StringLoader' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger_AbstractLogger' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger_StreamLogger' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Parser' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Source' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Source_FilesystemSource' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Template' => $vendorDir . '/mustache/mustache/src/compat.php',
+ 'Mustache_Tokenizer' => $vendorDir . '/mustache/mustache/src/compat.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..15a2ff3
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/mustache/mustache/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..ed4d131
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,38 @@
+register(true);
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..a467d4b
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,69 @@
+
+ array (
+ 'Mustache\\' => 9,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Mustache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mustache/mustache/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'Mustache_Cache' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_AbstractCache' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_FilesystemCache' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Cache_NoopCache' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Compiler' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Context' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Engine' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_InvalidArgumentException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_LogicException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_RuntimeException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_SyntaxException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownFilterException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownHelperException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Exception_UnknownTemplateException' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_HelperCollection' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_LambdaHelper' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_ArrayLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_CascadingLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_FilesystemLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_InlineLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_MutableLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_ProductionFilesystemLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Loader_StringLoader' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger_AbstractLogger' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Logger_StreamLogger' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Parser' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Source' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Source_FilesystemSource' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Template' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ 'Mustache_Tokenizer' => __DIR__ . '/..' . '/mustache/mustache/src/compat.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit071586d19f5409de22b3235d85d8476c::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit071586d19f5409de22b3235d85d8476c::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInit071586d19f5409de22b3235d85d8476c::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000..7b7be13
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,62 @@
+{
+ "packages": [
+ {
+ "name": "mustache/mustache",
+ "version": "v3.0.0",
+ "version_normalized": "3.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bobthecow/mustache.php.git",
+ "reference": "176b6b21d68516dd5107a63ab71b0050e518b7a4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/176b6b21d68516dd5107a63ab71b0050e518b7a4",
+ "reference": "176b6b21d68516dd5107a63ab71b0050e518b7a4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.19.3",
+ "yoast/phpunit-polyfills": "^2.0"
+ },
+ "time": "2025-06-28T18:28:20+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Mustache\\": "src/"
+ },
+ "classmap": [
+ "src/compat.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Justin Hileman",
+ "email": "justin@justinhileman.info",
+ "homepage": "http://justinhileman.com"
+ }
+ ],
+ "description": "A Mustache implementation in PHP.",
+ "homepage": "https://github.com/bobthecow/mustache.php",
+ "keywords": [
+ "mustache",
+ "templating"
+ ],
+ "support": {
+ "issues": "https://github.com/bobthecow/mustache.php/issues",
+ "source": "https://github.com/bobthecow/mustache.php/tree/v3.0.0"
+ },
+ "install-path": "../mustache/mustache"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
new file mode 100644
index 0000000..ab2f2f9
--- /dev/null
+++ b/vendor/composer/installed.php
@@ -0,0 +1,32 @@
+ array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-main',
+ 'version' => 'dev-main',
+ 'reference' => '0f1c7234b8e213130e58252a3aa58a58290c959e',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-main',
+ 'version' => 'dev-main',
+ 'reference' => '0f1c7234b8e213130e58252a3aa58a58290c959e',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'mustache/mustache' => array(
+ 'pretty_version' => 'v3.0.0',
+ 'version' => '3.0.0.0',
+ 'reference' => '176b6b21d68516dd5107a63ab71b0050e518b7a4',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../mustache/mustache',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
new file mode 100644
index 0000000..b74a4c9
--- /dev/null
+++ b/vendor/composer/platform_check.php
@@ -0,0 +1,25 @@
+= 50600)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ throw new \RuntimeException(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues)
+ );
+}
diff --git a/vendor/mustache/mustache/.github/workflows/tests.yml b/vendor/mustache/mustache/.github/workflows/tests.yml
new file mode 100644
index 0000000..7ef087c
--- /dev/null
+++ b/vendor/mustache/mustache/.github/workflows/tests.yml
@@ -0,0 +1,47 @@
+name: Tests
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 * * *'
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php:
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+ - 7.4
+ - 8.0
+ - 8.1
+ - 8.2
+ - 8.3
+ - 8.4
+
+ name: PHP ${{ matrix.php }}
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+ with:
+ submodules: true
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-interaction --no-progress
+
+ - name: Run tests
+ run: vendor/bin/phpunit
diff --git a/vendor/mustache/mustache/.php-cs-fixer.php b/vendor/mustache/mustache/.php-cs-fixer.php
new file mode 100644
index 0000000..e4f15b4
--- /dev/null
+++ b/vendor/mustache/mustache/.php-cs-fixer.php
@@ -0,0 +1,20 @@
+setRules([
+ '@Symfony' => true,
+ 'binary_operator_spaces' => false,
+ 'concat_space' => ['spacing' => 'one'],
+ 'increment_style' => false,
+ 'single_line_throw' => false,
+ 'yoda_style' => false,
+]);
+
+$finder = $config->getFinder()
+ ->in('src')
+ ->in('test');
+
+return $config;
diff --git a/vendor/mustache/mustache/LICENSE b/vendor/mustache/mustache/LICENSE
new file mode 100644
index 0000000..2e686ed
--- /dev/null
+++ b/vendor/mustache/mustache/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2010-2025 Justin Hileman
+
+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.
diff --git a/vendor/mustache/mustache/README.md b/vendor/mustache/mustache/README.md
new file mode 100644
index 0000000..902c113
--- /dev/null
+++ b/vendor/mustache/mustache/README.md
@@ -0,0 +1,94 @@
+# Mustache.php
+
+A [Mustache][mustache] implementation in PHP.
+
+[][packagist]
+[][packagist]
+
+
+## Installation
+
+```
+composer require mustache/mustache
+```
+
+## Usage
+
+A quick example:
+
+```php
+ ENT_QUOTES]);
+echo $m->render('Hello {{planet}}', ['planet' => 'World!']); // "Hello World!"
+```
+
+
+And a more in-depth example -- this is the canonical Mustache template:
+
+```html+jinja
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+```
+
+
+Create a view "context" object -- which could also be an associative array, but those don't do functions quite as well:
+
+```php
+value - ($this->value * 0.4);
+ }
+
+ public $in_ca = true;
+}
+```
+
+
+And render it:
+
+```php
+ ENT_QUOTES]);
+$chris = new \Chris;
+echo $m->render($template, $chris);
+```
+
+*Note:* we recommend using `ENT_QUOTES` as a default of [entity_flags][entity_flags] to decrease the chance of Cross-site scripting vulnerability.
+
+
+## And That's Not All!
+
+Read [the Mustache.php documentation][docs] for more information.
+
+
+## Upgrading from v2.x
+_Mustache.php v3.x drops support for PHP 5.2–5.5_, but is otherwise backwards compatible with v2.x.
+
+To ease the transition, previous behavior can be preserved via configuration:
+
+ - The `strict_callables` config option now defaults to `true`. Lambda sections should use closures or callable objects. To continue supporting array-style callables for lambda sections (e.g. `[$this, 'foo']`), set `strict_callables` to `false`.
+ - [A context shadowing bug from v2.x has been fixed](https://github.com/bobthecow/mustache.php/commit/66ecb327ce15b9efa0cfcb7026fdc62c6659b27f), but if you depend on the previous buggy behavior you can preserve it via the `buggy_property_shadowing` config option.
+ - By default the return value of higher-order sections that are rendered via the lambda helper will no longer be double-rendered. To preserve the previous behavior, set `double_render_lambdas` to `true`. _This is not recommended._
+
+In order to maintain a wide PHP version support range, there are minor changes to a few interfaces, which you might need to handle if you extend Mustache (see [c0453be](https://github.com/bobthecow/mustache.php/commit/c0453be5c09e7d988b396982e29218fcb25b7304)).
+
+
+## See Also
+
+ - [mustache(5)][manpage] man page.
+ - [Readme for the Ruby Mustache implementation][ruby].
+
+
+[mustache]: https://mustache.github.io/
+[packagist]: https://packagist.org/packages/mustache/mustache
+[entity_flags]: https://github.com/bobthecow/mustache.php/wiki#entity_flags
+[docs]: https://github.com/bobthecow/mustache.php/wiki/Home
+[manpage]: https://mustache.github.io/mustache.5.html
+[ruby]: https://github.com/mustache/mustache/blob/master/README.md
diff --git a/vendor/mustache/mustache/composer.json b/vendor/mustache/mustache/composer.json
new file mode 100644
index 0000000..b034a10
--- /dev/null
+++ b/vendor/mustache/mustache/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "mustache/mustache",
+ "description": "A Mustache implementation in PHP.",
+ "keywords": [
+ "templating",
+ "mustache"
+ ],
+ "homepage": "https://github.com/bobthecow/mustache.php",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Justin Hileman",
+ "email": "justin@justinhileman.info",
+ "homepage": "http://justinhileman.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "~2.19.3",
+ "yoast/phpunit-polyfills": "^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Mustache\\": "src/"
+ },
+ "classmap": [
+ "src/compat.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Mustache\\Test\\": "test/"
+ }
+ }
+}
diff --git a/vendor/mustache/mustache/src/Cache.php b/vendor/mustache/mustache/src/Cache.php
new file mode 100644
index 0000000..8f1e36b
--- /dev/null
+++ b/vendor/mustache/mustache/src/Cache.php
@@ -0,0 +1,46 @@
+logger;
+ }
+
+ /**
+ * Set a logger instance.
+ *
+ * @param Logger|LoggerInterface $logger
+ */
+ public function setLogger($logger = null)
+ {
+ // n.b. this uses `is_a` to prevent a dependency on Psr\Log
+ if ($logger !== null && !$logger instanceof Logger && !is_a($logger, 'Psr\\Log\\LoggerInterface')) {
+ throw new InvalidArgumentException('Expected an instance of Mustache\\Logger or Psr\\Log\\LoggerInterface.');
+ }
+
+ $this->logger = $logger;
+ }
+
+ /**
+ * Add a log record if logging is enabled.
+ *
+ * @param string $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ */
+ protected function log($level, $message, array $context = [])
+ {
+ if (isset($this->logger)) {
+ $this->logger->log($level, $message, $context);
+ }
+ }
+}
diff --git a/vendor/mustache/mustache/src/Cache/FilesystemCache.php b/vendor/mustache/mustache/src/Cache/FilesystemCache.php
new file mode 100644
index 0000000..94d9cd6
--- /dev/null
+++ b/vendor/mustache/mustache/src/Cache/FilesystemCache.php
@@ -0,0 +1,166 @@
+cache($className, $compiledSource);
+ *
+ * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
+ */
+class FilesystemCache extends AbstractCache
+{
+ private $baseDir;
+ private $fileMode;
+
+ /**
+ * Filesystem cache constructor.
+ *
+ * @param string $baseDir Directory for compiled templates
+ * @param int $fileMode Override default permissions for cache files. Defaults to using the system-defined umask
+ */
+ public function __construct($baseDir, $fileMode = null)
+ {
+ $this->baseDir = $baseDir;
+ $this->fileMode = $fileMode;
+ }
+
+ /**
+ * Load the class from cache using `require_once`.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function load($key)
+ {
+ $fileName = $this->getCacheFilename($key);
+ if (!is_file($fileName)) {
+ return false;
+ }
+
+ require_once $fileName;
+
+ return true;
+ }
+
+ /**
+ * Cache and load the compiled class.
+ *
+ * @param string $key
+ * @param string $value
+ */
+ public function cache($key, $value)
+ {
+ $fileName = $this->getCacheFilename($key);
+
+ $this->log(
+ Logger::DEBUG,
+ 'Writing to template cache: "{fileName}"',
+ ['fileName' => $fileName]
+ );
+
+ $this->writeFile($fileName, $value);
+ $this->load($key);
+ }
+
+ /**
+ * Build the cache filename.
+ * Subclasses should override for custom cache directory structures.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function getCacheFilename($name)
+ {
+ return sprintf('%s/%s.php', $this->baseDir, $name);
+ }
+
+ /**
+ * Create cache directory.
+ *
+ * @throws RuntimeException If unable to create directory
+ *
+ * @param string $fileName
+ *
+ * @return string
+ */
+ private function buildDirectoryForFilename($fileName)
+ {
+ $dirName = dirname($fileName);
+ if (!is_dir($dirName)) {
+ $this->log(
+ Logger::INFO,
+ 'Creating Mustache template cache directory: "{dirName}"',
+ ['dirName' => $dirName]
+ );
+
+ @mkdir($dirName, 0777, true);
+ // @codeCoverageIgnoreStart
+ if (!is_dir($dirName)) {
+ throw new RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $dirName;
+ }
+
+ /**
+ * Write cache file.
+ *
+ * @throws RuntimeException If unable to write file
+ *
+ * @param string $fileName
+ * @param string $value
+ */
+ private function writeFile($fileName, $value)
+ {
+ $dirName = $this->buildDirectoryForFilename($fileName);
+
+ $this->log(
+ Logger::DEBUG,
+ 'Caching compiled template to "{fileName}"',
+ ['fileName' => $fileName]
+ );
+
+ $tempFile = tempnam($dirName, basename($fileName));
+ if (false !== @file_put_contents($tempFile, $value)) {
+ if (@rename($tempFile, $fileName)) {
+ $mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
+ @chmod($fileName, $mode);
+
+ return;
+ }
+
+ // @codeCoverageIgnoreStart
+ $this->log(
+ Logger::ERROR,
+ 'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
+ ['tempName' => $tempFile, 'fileName' => $fileName]
+ );
+ // @codeCoverageIgnoreEnd
+ }
+
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
+ // @codeCoverageIgnoreEnd
+ }
+}
diff --git a/vendor/mustache/mustache/src/Cache/NoopCache.php b/vendor/mustache/mustache/src/Cache/NoopCache.php
new file mode 100644
index 0000000..35e6822
--- /dev/null
+++ b/vendor/mustache/mustache/src/Cache/NoopCache.php
@@ -0,0 +1,51 @@
+log(
+ Logger::WARNING,
+ 'Template cache disabled, evaluating "{className}" class at runtime',
+ ['className' => $key]
+ );
+ eval('?>' . $value);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Compiler.php b/vendor/mustache/mustache/src/Compiler.php
new file mode 100644
index 0000000..a78c89f
--- /dev/null
+++ b/vendor/mustache/mustache/src/Compiler.php
@@ -0,0 +1,807 @@
+pragmas = $this->defaultPragmas;
+ $this->sections = [];
+ $this->blocks = [];
+ $this->source = $source;
+ $this->indentNextLine = true;
+ $this->customEscape = $customEscape;
+ $this->entityFlags = $entityFlags;
+ $this->charset = $charset;
+ $this->strictCallables = $strictCallables;
+
+ $code = $this->writeCode($tree, $name);
+
+ if (isset($this->pragmas[Engine::PRAGMA_FILTERS]) && !$this->lambdas) {
+ throw new InvalidArgumentException('The FILTERS pragma requires lambda support');
+ }
+
+ return $code;
+ }
+
+ /**
+ * Disable optional Mustache specs.
+ *
+ * @internal Users should set options in Mustache\Engine, not here :)
+ *
+ * @param bool[] $options
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['lambdas'])) {
+ $this->lambdas = $options['lambdas'] !== false;
+ }
+ }
+
+ /**
+ * Enable pragmas across all templates, regardless of the presence of pragma
+ * tags in the individual templates.
+ *
+ * @internal Users should set global pragmas in \Mustache\Engine, not here :)
+ *
+ * @param string[] $pragmas
+ */
+ public function setPragmas(array $pragmas)
+ {
+ $this->pragmas = [];
+ foreach ($pragmas as $pragma) {
+ $this->pragmas[$pragma] = true;
+ }
+ $this->defaultPragmas = $this->pragmas;
+ }
+
+ /**
+ * Helper function for walking the Mustache token parse tree.
+ *
+ * @throws SyntaxException upon encountering unknown token types
+ *
+ * @param array $tree Parse tree of Mustache tokens
+ * @param int $level (default: 0)
+ *
+ * @return string Generated PHP source code
+ */
+ private function walk(array $tree, $level = 0)
+ {
+ $code = '';
+ $level++;
+ foreach ($tree as $node) {
+ switch ($node[Tokenizer::TYPE]) {
+ case Tokenizer::T_PRAGMA:
+ $this->pragmas[$node[Tokenizer::NAME]] = true;
+ break;
+
+ case Tokenizer::T_SECTION:
+ $code .= $this->section(
+ $node[Tokenizer::NODES],
+ $node[Tokenizer::NAME],
+ isset($node[Tokenizer::FILTERS]) ? $node[Tokenizer::FILTERS] : [],
+ $node[Tokenizer::INDEX],
+ $node[Tokenizer::END],
+ $node[Tokenizer::OTAG],
+ $node[Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Tokenizer::T_INVERTED:
+ $code .= $this->invertedSection(
+ $node[Tokenizer::NODES],
+ $node[Tokenizer::NAME],
+ isset($node[Tokenizer::FILTERS]) ? $node[Tokenizer::FILTERS] : [],
+ $level
+ );
+ break;
+
+ case Tokenizer::T_PARTIAL:
+ $code .= $this->partial(
+ $node[Tokenizer::NAME],
+ isset($node[Tokenizer::DYNAMIC]) ? $node[Tokenizer::DYNAMIC] : false,
+ isset($node[Tokenizer::INDENT]) ? $node[Tokenizer::INDENT] : '',
+ $level
+ );
+ break;
+
+ case Tokenizer::T_PARENT:
+ $code .= $this->parent(
+ $node[Tokenizer::NAME],
+ isset($node[Tokenizer::DYNAMIC]) ? $node[Tokenizer::DYNAMIC] : false,
+ isset($node[Tokenizer::INDENT]) ? $node[Tokenizer::INDENT] : '',
+ $node[Tokenizer::NODES],
+ $level
+ );
+ break;
+
+ case Tokenizer::T_BLOCK_ARG:
+ $code .= $this->blockArg(
+ $node[Tokenizer::NODES],
+ $node[Tokenizer::NAME],
+ $node[Tokenizer::INDEX],
+ $node[Tokenizer::END],
+ $node[Tokenizer::OTAG],
+ $node[Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Tokenizer::T_BLOCK_VAR:
+ $code .= $this->blockVar(
+ $node[Tokenizer::NODES],
+ $node[Tokenizer::NAME],
+ $node[Tokenizer::INDEX],
+ $node[Tokenizer::END],
+ $node[Tokenizer::OTAG],
+ $node[Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Tokenizer::T_COMMENT:
+ break;
+
+ case Tokenizer::T_ESCAPED:
+ case Tokenizer::T_UNESCAPED:
+ case Tokenizer::T_UNESCAPED_2:
+ $code .= $this->variable(
+ $node[Tokenizer::NAME],
+ isset($node[Tokenizer::FILTERS]) ? $node[Tokenizer::FILTERS] : [],
+ $node[Tokenizer::TYPE] === Tokenizer::T_ESCAPED,
+ $level
+ );
+ break;
+
+ case Tokenizer::T_TEXT:
+ $code .= $this->text($node[Tokenizer::VALUE], $level);
+ break;
+
+ default:
+ throw new SyntaxException(sprintf('Unknown token type: %s', $node[Tokenizer::TYPE]), $node);
+ }
+ }
+
+ return $code;
+ }
+
+ const KLASS = 'lambdaHelper = new \\Mustache\\LambdaHelper($this->mustache, $context);
+ $buffer = \'\';
+ %s
+
+ return $buffer;
+ }
+ %s
+ %s
+ }';
+
+ const KLASS_NO_LAMBDAS = 'walk($tree);
+ $sections = implode("\n", $this->sections);
+ $blocks = implode("\n", $this->blocks);
+ $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
+
+ $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
+ $lambda = $this->lambdas ? '' : $this->prepare(self::NO_LAMBDAS);
+
+ return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $lambda, $code, $sections, $blocks);
+ }
+
+ const BLOCK_VAR = '
+ $blockFunction = $context->findInBlock(%s);
+ if (is_callable($blockFunction)) {
+ $buffer .= call_user_func($blockFunction, $context);
+ %s}
+ ';
+
+ const BLOCK_VAR_ELSE = '} else {%s';
+
+ /**
+ * Generate Mustache Template inheritance block variable PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function blockVar(array $nodes, $id, $start, $end, $otag, $ctag, $level)
+ {
+ $id = var_export($id, true);
+
+ $else = $this->walk($nodes, $level);
+ if ($else !== '') {
+ $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else);
+ }
+
+ return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else);
+ }
+
+ const BLOCK_ARG = '%s => [$this, \'block%s\'],';
+
+ /**
+ * Generate Mustache Template inheritance block argument PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
+ {
+ $key = $this->block($nodes);
+ $id = var_export($id, true);
+
+ return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key);
+ }
+
+ const BLOCK_FUNCTION = '
+ public function block%s($context)
+ {
+ $indent = $buffer = \'\';%s
+
+ return $buffer;
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inheritance block function PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ *
+ * @return string key of new block function
+ */
+ private function block(array $nodes)
+ {
+ $code = $this->walk($nodes, 0);
+ $key = ucfirst(md5($code));
+
+ if (!isset($this->blocks[$key])) {
+ $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
+ }
+
+ return $key;
+ }
+
+ const SECTION_CALL = '
+ $value = $context->%s(%s%s);%s
+ $buffer .= $this->section%s($context, $indent, $value);
+ ';
+
+ const SECTION = '
+ private function section%s(\\Mustache\\Context $context, $indent, $value)
+ {
+ $buffer = \'\';
+
+ if (%s) {
+ $source = %s;
+ $value = call_user_func($value, $source, %s);
+
+ if ($value instanceof \\Mustache\\RenderedString) {
+ return $value->getValue();
+ }
+
+ if (is_string($value)) {
+ if (strpos($value, \'{{\') === false) {
+ return $value;
+ }
+
+ return $this->mustache
+ ->loadLambda($value%s)
+ ->renderInternal($context);
+ }
+ }
+
+ if (!empty($value)) {
+ $values = $this->isIterable($value) ? $value : [$value];
+ foreach ($values as $value) {
+ $context->push($value);
+ %s
+ $context->pop();
+ }
+ }
+
+ return $buffer;
+ }
+ ';
+
+ const SECTION_NO_LAMBDAS = '
+ private function section%s(\\Mustache\\Context $context, $indent, $value)
+ {
+ $buffer = \'\';
+
+ if (!empty($value)) {
+ $values = $this->isIterable($value) ? $value : [$value];
+ foreach ($values as $value) {
+ $context->push($value);
+ %s
+ $context->pop();
+ }
+ }
+
+ return $buffer;
+ }
+ ';
+
+ /**
+ * Generate Mustache Template section PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param string[] $filters Array of filters
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated section PHP source code
+ */
+ private function section(array $nodes, $id, $filters, $start, $end, $otag, $ctag, $level)
+ {
+ $source = var_export(substr($this->source, $start, $end - $start), true);
+ $callable = $this->getCallable();
+
+ if ($otag !== '{{' || $ctag !== '}}') {
+ $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
+ $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag);
+ $delims = ', ' . $delimTag;
+ } else {
+ $helper = '$this->lambdaHelper';
+ $delims = '';
+ }
+
+ $key = ucfirst(md5($delims . "\n" . $source));
+
+ if (!isset($this->sections[$key])) {
+ if ($this->lambdas) {
+ $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2));
+ } else {
+ $this->sections[$key] = sprintf($this->prepare(self::SECTION_NO_LAMBDAS), $key, $this->walk($nodes, 2));
+ }
+ }
+
+ $method = $this->getFindMethod($id);
+ $id = var_export($id, true);
+ $findArg = $this->getFindMethodArgs($method);
+ $filters = $this->getFilters($filters, $level);
+
+ return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $findArg, $filters, $key);
+ }
+
+ const INVERTED_SECTION = '
+ $value = $context->%s(%s%s);%s
+ if (empty($value)) {
+ %s
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inverted section PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param string[] $filters Array of filters
+ * @param int $level
+ *
+ * @return string Generated inverted section PHP source code
+ */
+ private function invertedSection(array $nodes, $id, $filters, $level)
+ {
+ $method = $this->getFindMethod($id);
+ $id = var_export($id, true);
+ $findArg = $this->getFindMethodArgs($method);
+ $filters = $this->getFilters($filters, $level);
+
+ return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $findArg, $filters, $this->walk($nodes, $level));
+ }
+
+ const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s%s), $context)';
+
+ /**
+ * Generate Mustache Template dynamic name resolution PHP source.
+ *
+ * @param string $id Tag name
+ * @param bool $dynamic True if the name is dynamic
+ *
+ * @return string Dynamic name resolution PHP source code
+ */
+ private function resolveDynamicName($id, $dynamic)
+ {
+ if (!$dynamic) {
+ return var_export($id, true);
+ }
+
+ $method = $this->getFindMethod($id);
+ $id = ($method !== 'last') ? var_export($id, true) : '';
+ $findArg = $this->getFindMethodArgs($method);
+
+ // TODO: filters?
+
+ return sprintf(self::DYNAMIC_NAME, $method, $id, $findArg);
+ }
+
+ const PARTIAL_INDENT = ', $indent . %s';
+ const PARTIAL = '
+ if ($partial = $this->mustache->loadPartial(%s)) {
+ $buffer .= $partial->renderInternal($context%s);
+ }
+ ';
+
+ /**
+ * Generate Mustache Template partial call PHP source.
+ *
+ * @param string $id Partial name
+ * @param bool $dynamic Partial name is dynamic
+ * @param string $indent Whitespace indent to apply to partial
+ * @param int $level
+ *
+ * @return string Generated partial call PHP source code
+ */
+ private function partial($id, $dynamic, $indent, $level)
+ {
+ if ($indent !== '') {
+ $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
+ } else {
+ $indentParam = '';
+ }
+
+ return sprintf(
+ $this->prepare(self::PARTIAL, $level),
+ $this->resolveDynamicName($id, $dynamic),
+ $indentParam
+ );
+ }
+
+ const PARENT = '
+ if ($parent = $this->mustache->loadPartial(%s)) {
+ $context->pushBlockContext([%s
+ ]);
+ $buffer .= $parent->renderInternal($context, $indent);
+ $context->popBlockContext();
+ }
+ ';
+
+ const PARENT_NO_CONTEXT = '
+ if ($parent = $this->mustache->loadPartial(%s)) {
+ $buffer .= $parent->renderInternal($context, $indent);
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inheritance parent call PHP source.
+ *
+ * @param string $id Parent tag name
+ * @param bool $dynamic Tag name is dynamic
+ * @param string $indent Whitespace indent to apply to parent
+ * @param array $children Child nodes
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function parent($id, $dynamic, $indent, array $children, $level)
+ {
+ $realChildren = array_filter($children, [self::class, 'onlyBlockArgs']);
+ $partialName = $this->resolveDynamicName($id, $dynamic);
+
+ if (empty($realChildren)) {
+ return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), $partialName);
+ }
+
+ return sprintf(
+ $this->prepare(self::PARENT, $level),
+ $partialName,
+ $this->walk($realChildren, $level + 1)
+ );
+ }
+
+ /**
+ * Helper method for filtering out non-block-arg tokens.
+ *
+ * @return bool True if $node is a block arg token
+ */
+ private static function onlyBlockArgs(array $node)
+ {
+ return $node[Tokenizer::TYPE] === Tokenizer::T_BLOCK_ARG;
+ }
+
+ const VARIABLE = '
+ $value = $this->resolveValue($context->%s(%s%s), $context);%s
+ $buffer .= %s($value === null ? \'\' : %s);
+ ';
+
+ /**
+ * Generate Mustache Template variable interpolation PHP source.
+ *
+ * @param string $id Variable name
+ * @param string[] $filters Array of filters
+ * @param bool $escape Escape the variable value for output?
+ * @param int $level
+ *
+ * @return string Generated variable interpolation PHP source
+ */
+ private function variable($id, $filters, $escape, $level)
+ {
+ $method = $this->getFindMethod($id);
+ $id = ($method !== 'last') ? var_export($id, true) : '';
+ $findArg = $this->getFindMethodArgs($method);
+ $filters = $this->getFilters($filters, $level);
+ $value = $escape ? $this->getEscape() : '$value';
+
+ return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $findArg, $filters, $this->flushIndent(), $value);
+ }
+
+ const FILTER = '
+ $filter = $context->%s(%s%s);
+ if (!(%s)) {
+ throw new \\Mustache\\Exception\\UnknownFilterException(%s);
+ }
+ $value = call_user_func($filter, %s);%s
+ ';
+ const FILTER_FIRST_VALUE = '$this->resolveValue($value, $context)';
+ const FILTER_VALUE = '$value';
+
+ /**
+ * Generate Mustache Template variable filtering PHP source.
+ *
+ * If the initial $value is a lambda it will be resolved before starting the filter chain.
+ *
+ * @param string[] $filters Array of filters
+ * @param int $level
+ * @param bool $first (default: false)
+ *
+ * @return string Generated filter PHP source
+ */
+ private function getFilters(array $filters, $level, $first = true)
+ {
+ if (empty($filters)) {
+ return '';
+ }
+
+ $name = array_shift($filters);
+ $method = $this->getFindMethod($name);
+ $filter = ($method !== 'last') ? var_export($name, true) : '';
+ $findArg = $this->getFindMethodArgs($method);
+ $callable = $this->getCallable('$filter');
+ $msg = var_export($name, true);
+ $value = $first ? self::FILTER_FIRST_VALUE : self::FILTER_VALUE;
+
+ return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $findArg, $callable, $msg, $value, $this->getFilters($filters, $level, false));
+ }
+
+ const LINE = '$buffer .= "\n";';
+ const TEXT = '$buffer .= %s%s;';
+
+ /**
+ * Generate Mustache Template output Buffer call PHP source.
+ *
+ * @param string $text
+ * @param int $level
+ *
+ * @return string Generated output Buffer call PHP source
+ */
+ private function text($text, $level)
+ {
+ $indentNextLine = (substr($text, -1) === "\n");
+ $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
+ $this->indentNextLine = $indentNextLine;
+
+ return $code;
+ }
+
+ /**
+ * Prepare PHP source code snippet for output.
+ *
+ * @param string $text
+ * @param int $bonus Additional indent level (default: 0)
+ * @param bool $prependNewline Prepend a newline to the snippet? (default: true)
+ * @param bool $appendNewline Append a newline to the snippet? (default: false)
+ *
+ * @return string PHP source code snippet
+ */
+ private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
+ {
+ $text = ($prependNewline ? "\n" : '') . trim($text);
+ if ($prependNewline) {
+ $bonus++;
+ }
+ if ($appendNewline) {
+ $text .= "\n";
+ }
+
+ return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text);
+ }
+
+ const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
+ const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)';
+
+ /**
+ * Get the current escaper.
+ *
+ * @param string $value (default: '$value')
+ *
+ * @return string Either a custom callback, or an inline call to `htmlspecialchars`
+ */
+ private function getEscape($value = '$value')
+ {
+ if ($this->customEscape) {
+ return sprintf(self::CUSTOM_ESCAPE, $value);
+ }
+
+ return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
+ }
+
+ /**
+ * Select the appropriate Context `find` method for a given $id.
+ *
+ * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`.
+ *
+ * @see \Mustache\Context::find
+ * @see \Mustache\Context::findDot
+ * @see \Mustache\Context::last
+ *
+ * @param string $id Variable name
+ *
+ * @return string `find` method name
+ */
+ private function getFindMethod($id)
+ {
+ if ($id === '.') {
+ return 'last';
+ }
+
+ if (isset($this->pragmas[Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Engine::PRAGMA_ANCHORED_DOT]) {
+ if (substr($id, 0, 1) === '.') {
+ return 'findAnchoredDot';
+ }
+ }
+
+ if (strpos($id, '.') === false) {
+ return 'find';
+ }
+
+ return 'findDot';
+ }
+
+ /**
+ * Get the args needed for a given find method.
+ *
+ * In this case, it's "true" iff it's a "find dot" method and strict callables is enabled.
+ *
+ * @param string $method Find method name
+ */
+ private function getFindMethodArgs($method)
+ {
+ if (($method === 'findDot' || $method === 'findAnchoredDot') && $this->strictCallables) {
+ return ', true';
+ }
+
+ return '';
+ }
+
+ const IS_CALLABLE = '!is_string(%s) && is_callable(%s)';
+ const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
+
+ /**
+ * Helper function to compile strict vs lax "is callable" logic.
+ *
+ * @param string $variable (default: '$value')
+ *
+ * @return string "is callable" logic
+ */
+ private function getCallable($variable = '$value')
+ {
+ $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
+
+ return sprintf($tpl, $variable, $variable);
+ }
+
+ const LINE_INDENT = '$indent . ';
+
+ /**
+ * Get the current $indent prefix to write to the buffer.
+ *
+ * @return string "$indent . " or ""
+ */
+ private function flushIndent()
+ {
+ if (!$this->indentNextLine) {
+ return '';
+ }
+
+ $this->indentNextLine = false;
+
+ return self::LINE_INDENT;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Context.php b/vendor/mustache/mustache/src/Context.php
new file mode 100644
index 0000000..9f60845
--- /dev/null
+++ b/vendor/mustache/mustache/src/Context.php
@@ -0,0 +1,277 @@
+stack = [$context];
+ }
+
+ $this->buggyPropertyShadowing = $buggyPropertyShadowing;
+ }
+
+ /**
+ * Push a new Context frame onto the stack.
+ *
+ * @param mixed $value Object or array to use for context
+ */
+ public function push($value)
+ {
+ array_push($this->stack, $value);
+ }
+
+ /**
+ * Push a new Context frame onto the block context stack.
+ *
+ * @param mixed $value Object or array to use for block context
+ */
+ public function pushBlockContext($value)
+ {
+ array_push($this->blockStack, $value);
+ }
+
+ /**
+ * Pop the last Context frame from the stack.
+ *
+ * @return mixed Last Context frame (object or array)
+ */
+ public function pop()
+ {
+ return array_pop($this->stack);
+ }
+
+ /**
+ * Pop the last block Context frame from the stack.
+ *
+ * @return mixed Last block Context frame (object or array)
+ */
+ public function popBlockContext()
+ {
+ return array_pop($this->blockStack);
+ }
+
+ /**
+ * Get the last Context frame.
+ *
+ * @return mixed Last Context frame (object or array)
+ */
+ public function last()
+ {
+ return end($this->stack);
+ }
+
+ /**
+ * Find a variable in the Context stack.
+ *
+ * Starting with the last Context frame (the context of the innermost section), and working back to the top-level
+ * rendering context, look for a variable with the given name:
+ *
+ * * If the Context frame is an associative array which contains the key $id, returns the value of that element.
+ * * If the Context frame is an object, this will check first for a public method, then a public property named
+ * $id. Failing both of these, it will try `__isset` and `__get` magic methods.
+ * * If a value named $id is not found in any Context frame, returns an empty string.
+ *
+ * @param string $id Variable name
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function find($id)
+ {
+ return $this->findVariableInStack($id, $this->stack);
+ }
+
+ /**
+ * Find a 'dot notation' variable in the Context stack.
+ *
+ * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
+ * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
+ * result. For example, given the following context stack:
+ *
+ * $data = [
+ * 'name' => 'Fred',
+ * 'child' => [
+ * 'name' => 'Bob'
+ * ],
+ * ];
+ *
+ * ... and the Mustache following template:
+ *
+ * {{ child.name }}
+ *
+ * ... the `name` value is only searched for within the `child` value of the global Context, not within parent
+ * Context frames.
+ *
+ * @param string $id Dotted variable selector
+ * @param bool $strictCallables (default: false)
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findDot($id, $strictCallables = false)
+ {
+ $chunks = explode('.', $id);
+ $first = array_shift($chunks);
+ $value = $this->findVariableInStack($first, $this->stack);
+
+ // This wasn't really a dotted name, so we can just return the value.
+ if (empty($chunks)) {
+ return $value;
+ }
+
+ foreach ($chunks as $chunk) {
+ $isCallable = $strictCallables ? (is_object($value) && is_callable($value)) : (!is_string($value) && is_callable($value));
+
+ if ($isCallable) {
+ $value = $value();
+ } elseif ($value === '') {
+ return $value;
+ }
+
+ $value = $this->findVariableInStack($chunk, [$value]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Find an 'anchored dot notation' variable in the Context stack.
+ *
+ * This is the same as findDot(), except it looks in the top of the context
+ * stack for the first value, rather than searching the whole context stack
+ * and starting from there.
+ *
+ * @see Mustache\Context::findDot
+ *
+ * @throws InvalidArgumentException if given an invalid anchored dot $id
+ *
+ * @param string $id Dotted variable selector
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findAnchoredDot($id)
+ {
+ $chunks = explode('.', $id);
+ $first = array_shift($chunks);
+ if ($first !== '') {
+ throw new InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s', $id));
+ }
+
+ $value = $this->last();
+
+ foreach ($chunks as $chunk) {
+ if ($value === '') {
+ return $value;
+ }
+
+ $value = $this->findVariableInStack($chunk, [$value]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Find an argument in the block context stack.
+ *
+ * @param string $id
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findInBlock($id)
+ {
+ foreach ($this->blockStack as $context) {
+ if (array_key_exists($id, $context)) {
+ return $context[$id];
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Helper function to find a variable in the Context stack.
+ *
+ * @see Mustache\Context::find
+ *
+ * @param string $id Variable name
+ * @param array $stack Context stack
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ private function findVariableInStack($id, array $stack)
+ {
+ for ($i = count($stack) - 1; $i >= 0; $i--) {
+ $frame = &$stack[$i];
+
+ switch (gettype($frame)) {
+ case 'object':
+ if (!($frame instanceof \Closure)) {
+ // Note that is_callable() *will not work here*
+ // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
+ if (method_exists($frame, $id)) {
+ return $frame->$id();
+ }
+
+ if (isset($frame->$id)) {
+ return $frame->$id;
+ }
+
+ // Preserve backwards compatibility with a property shadowing bug in
+ // Mustache.php <= 2.14.2
+ // See https://github.com/bobthecow/mustache.php/pull/410
+ if ($this->buggyPropertyShadowing) {
+ if ($frame instanceof \ArrayAccess && isset($frame[$id])) {
+ return $frame[$id];
+ }
+ } else {
+ if (property_exists($frame, $id)) {
+ $rp = new \ReflectionProperty($frame, $id);
+ if ($rp->isPublic()) {
+ return $frame->$id;
+ }
+ }
+
+ if ($frame instanceof \ArrayAccess && $frame->offsetExists($id)) {
+ return $frame[$id];
+ }
+ }
+ }
+ break;
+
+ case 'array':
+ if (array_key_exists($id, $frame)) {
+ return $frame[$id];
+ }
+ break;
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/mustache/mustache/src/Engine.php b/vendor/mustache/mustache/src/Engine.php
new file mode 100644
index 0000000..732a29a
--- /dev/null
+++ b/vendor/mustache/mustache/src/Engine.php
@@ -0,0 +1,963 @@
+ true,
+ self::PRAGMA_ANCHORED_DOT => true,
+ self::PRAGMA_BLOCKS => true,
+ ];
+
+ // Template cache
+ private $templates = [];
+
+ // Environment
+ private $templateClassPrefix = '__Mustache_';
+ private $cache;
+ private $lambdaCache;
+ private $cacheLambdaTemplates = false;
+ private $doubleRenderLambdas = false;
+ private $loader;
+ private $partialsLoader;
+ private $helpers;
+ private $escape;
+ private $entityFlags = ENT_COMPAT;
+ private $charset = 'UTF-8';
+ private $logger;
+ private $strictCallables = true;
+ private $pragmas = [];
+ private $delimiters;
+ private $buggyPropertyShadowing = false;
+
+ // Optional Mustache specs
+ private $dynamicNames = true;
+ private $inheritance = true;
+ private $lambdas = true;
+
+ // Services
+ private $tokenizer;
+ private $parser;
+ private $compiler;
+
+ /**
+ * Mustache class constructor.
+ *
+ * Passing an $options array allows overriding certain Mustache options during instantiation:
+ *
+ * $options = [
+ * // The class prefix for compiled templates. Defaults to '__Mustache_'.
+ * 'template_class_prefix' => '__MyTemplates_',
+ *
+ * // A Mustache cache instance or a cache directory string for compiled templates.
+ * // Mustache will not cache templates unless this is set.
+ * 'cache' => __DIR__.'/tmp/cache/mustache',
+ *
+ * // Override default permissions for cache files. Defaults to using the system-defined umask. It is
+ * // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
+ * 'cache_file_mode' => 0666,
+ *
+ * // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda
+ * // sections are often too dynamic to benefit from caching.
+ * 'cache_lambda_templates' => true,
+ *
+ * // Customize the tag delimiters used by this engine instance. Note that overriding here changes the
+ * // delimiters used to parse all templates and partials loaded by this instance. To override just for a
+ * // single template, use an inline "change delimiters" tag at the start of the template file:
+ * //
+ * // {{=<% %>=}}
+ * //
+ * 'delimiters' => '<% %>',
+ *
+ * // A Mustache template loader instance. Uses a StringLoader if not specified.
+ * 'loader' => new \Mustache\Loader\FilesystemLoader(__DIR__.'/views'),
+ *
+ * // A Mustache loader instance for partials.
+ * 'partials_loader' => new \Mustache\Loader\FilesystemLoader(__DIR__.'/views/partials'),
+ *
+ * // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
+ * // efficient or lazy as a Filesystem (or database) loader.
+ * 'partials' => ['foo' => file_get_contents(__DIR__.'/views/partials/foo.mustache')],
+ *
+ * // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
+ * // sections), or any other valid Mustache context value. They will be prepended to the context stack,
+ * // so they will be available in any template loaded by this Mustache instance.
+ * 'helpers' => ['i18n' => function ($text) {
+ * // do something translatey here...
+ * }],
+ *
+ * // An 'escape' callback, responsible for escaping double-mustache variables.
+ * 'escape' => function ($value) {
+ * return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
+ * },
+ *
+ * // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES.
+ * 'entity_flags' => ENT_QUOTES,
+ *
+ * // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
+ * 'charset' => 'ISO-8859-1',
+ *
+ * // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
+ * // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
+ * // available as well:
+ * 'logger' => new \Mustache\Logger\StreamLogger('php://stderr'),
+ *
+ *
+ * // OPTIONAL MUSTACHE FEATURES:
+ *
+ * // Enable dynamic names. By default, variables and sections like `{{*name}}` will be resolved dynamically.
+ * //
+ * // To disable dynamic name resolution, set this to false.
+ * 'dynamic_names' => true,
+ *
+ * // Enable template inheritance. By default, templates can extend other templates using the `{{< name}}` and
+ * // `{{$ block}}` tags.
+ * //
+ * // To disable inheritance, set this to false.
+ * 'inheritance' => true,
+ *
+ * // Enable lambda sections and values. By default, "lambdas" are enabled; if a variable resolves to a
+ * // callable value, that callable is called before interpolation. If a section name resolves to a callable
+ * // value, it is treated as a "higher order section", and the section content is passed to the callable
+ * // for processing prior to rendering.
+ * //
+ * // Note that the FILTERS pragma requires lambdas to function, so using FILTERS without lambdas enabled
+ * // will throw an invalid argument exception.
+ * //
+ * // To disable lambdas and higher order sections entirely, set this to false.
+ * 'lambdas' => true,
+ *
+ * // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
+ * // templates.
+ * 'pragmas' => [\Mustache\Engine::PRAGMA_FILTERS],
+ *
+ *
+ * // BACKWARDS COMPATIBILITY:
+ *
+ * // Only treat \Closure instances and invokable classes as callable. If true, values like
+ * // `['ClassName', 'methodName']` and `[$classInstance, 'methodName']`, which are traditionally
+ * // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
+ * // helps protect against arbitrary code execution when user input is passed directly into the template.
+ * //
+ * // Defaults to true, but can be set to false to preserve Mustache.php v2.x behavior.
+ * //
+ * // THIS IS NOT RECOMMENDED.
+ * 'strict_callables' => true,
+ *
+ * // Enable buggy property shadowing. Per the Mustache spec, keys of a value higher in the context stack
+ * // shadow similarly named keys lower in the stack. For example, in the template
+ * // `{{# foo }}{{ bar }}{{/ foo }}` if the value for `foo` has a method, property, or key named `bar`, it
+ * // will prevent looking lower in the context stack for a another value named `bar`.
+ * //
+ * // Setting the value of an array key to null prevents lookups higher in the context stack. The behavior
+ * // should have been identical for object properties (and ArrayAccess) as well, but a bug in the context
+ * // lookup logic meant that a property which exists but is set to null would not prevent further context
+ * // lookup.
+ * //
+ * // This bug was fixed in Mustache.php v3.x, but the previous buggy behavior can be preserved by setting this
+ * // option to true.
+ * //
+ * // THIS IS NOT RECOMMENDED.
+ * 'buggy_property_shadowing' => false,
+ *
+ * // Double-render lambda return values. By default, the return value of higher order sections that are
+ * // rendered via the lambda helper will *not* be re-rendered.
+ * //
+ * // To preserve the behavior of Mustache.php v2.x, set this to true.
+ * //
+ * // THIS IS NOT RECOMMENDED.
+ * 'double_render_lambdas' => false,
+ * ];
+ *
+ * @throws InvalidArgumentException If `escape` option is not callable
+ * @throws InvalidArgumentException If `lambdas` is disabled but the `FILTERS` pragma is enabled
+ */
+ public function __construct(array $options = [])
+ {
+ if (isset($options['template_class_prefix'])) {
+ if ((string) $options['template_class_prefix'] === '') {
+ throw new InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty');
+ }
+
+ $this->templateClassPrefix = $options['template_class_prefix'];
+ }
+
+ if (isset($options['cache'])) {
+ $cache = $options['cache'];
+
+ if (is_string($cache)) {
+ $mode = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
+ $cache = new FilesystemCache($cache, $mode);
+ }
+
+ $this->setCache($cache);
+ }
+
+ if (isset($options['cache_lambda_templates'])) {
+ $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
+ }
+
+ if (isset($options['loader'])) {
+ $this->setLoader($options['loader']);
+ }
+
+ if (isset($options['partials_loader'])) {
+ $this->setPartialsLoader($options['partials_loader']);
+ }
+
+ if (isset($options['partials'])) {
+ $this->setPartials($options['partials']);
+ }
+
+ if (isset($options['helpers'])) {
+ $this->setHelpers($options['helpers']);
+ }
+
+ if (isset($options['escape'])) {
+ if (!is_callable($options['escape'])) {
+ throw new InvalidArgumentException('Mustache Constructor "escape" option must be callable');
+ }
+
+ $this->escape = $options['escape'];
+ }
+
+ if (isset($options['entity_flags'])) {
+ $this->entityFlags = $options['entity_flags'];
+ }
+
+ if (isset($options['charset'])) {
+ $this->charset = $options['charset'];
+ }
+
+ if (isset($options['logger'])) {
+ $this->setLogger($options['logger']);
+ }
+
+ if (isset($options['delimiters'])) {
+ $this->delimiters = $options['delimiters'];
+ }
+
+ // Optional Mustache features
+
+ if (isset($options['dynamic_names'])) {
+ $this->dynamicNames = $options['dynamic_names'] !== false;
+ }
+
+ if (isset($options['inheritance'])) {
+ $this->inheritance = $options['inheritance'] !== false;
+ }
+
+ if (isset($options['lambdas'])) {
+ $this->lambdas = $options['lambdas'] !== false;
+ }
+
+ if (isset($options['pragmas'])) {
+ foreach ($options['pragmas'] as $pragma) {
+ if (!isset(self::$knownPragmas[$pragma])) {
+ throw new InvalidArgumentException(sprintf('Unknown pragma: "%s"', $pragma));
+ }
+ $this->pragmas[$pragma] = true;
+ }
+ }
+
+ if (!$this->lambdas && isset($this->pragmas[self::PRAGMA_FILTERS])) {
+ throw new InvalidArgumentException('The FILTERS pragma requires lambda support');
+ }
+
+ // Backwards compatibility
+
+ if (isset($options['strict_callables'])) {
+ $this->strictCallables = (bool) $options['strict_callables'];
+ }
+
+ if (isset($options['buggy_property_shadowing'])) {
+ $this->buggyPropertyShadowing = (bool) $options['buggy_property_shadowing'];
+ }
+
+ if (isset($options['double_render_lambdas'])) {
+ $this->doubleRenderLambdas = (bool) $options['double_render_lambdas'];
+ }
+ }
+
+ /**
+ * Shortcut 'render' invocation.
+ *
+ * Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
+ *
+ * @see Mustache\Engine::loadTemplate
+ * @see Mustache\Template::render
+ *
+ * @param string $template
+ *
+ * @return string Rendered template
+ */
+ public function render($template, $context = [])
+ {
+ return $this->loadTemplate($template)->render($context);
+ }
+
+ /**
+ * Get the current Mustache escape callback.
+ *
+ * @return callable|null
+ */
+ public function getEscape()
+ {
+ return $this->escape;
+ }
+
+ /**
+ * Get the current Mustache entity type to escape.
+ *
+ * @return int
+ */
+ public function getEntityFlags()
+ {
+ return $this->entityFlags;
+ }
+
+ /**
+ * Get the current Mustache character set.
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Check whether to double-render higher-order sections.
+ *
+ * By default, the return value of higher order sections that are rendered
+ * via the lambda helper will *not* be re-rendered. To preserve the
+ * behavior of Mustache.php v2.x, set this to true.
+ *
+ * THIS IS NOT RECOMMENDED.
+ */
+ public function getDoubleRenderLambdas()
+ {
+ return $this->doubleRenderLambdas;
+ }
+
+ /**
+ * Check whether to use buggy property shadowing.
+ *
+ * THIS IS NOT RECOMMENDED.
+ *
+ * See https://github.com/bobthecow/mustache.php/pull/410
+ */
+ public function getBuggyPropertyShadowing()
+ {
+ return $this->buggyPropertyShadowing;
+ }
+
+ /**
+ * Get currently enabled optional features.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return [
+ 'dynamic_names' => $this->dynamicNames,
+ 'inheritance' => $this->inheritance,
+ 'lambdas' => $this->lambdas,
+ ];
+ }
+
+ /**
+ * Get the current globally enabled pragmas.
+ *
+ * @return array
+ */
+ public function getPragmas()
+ {
+ return array_keys($this->pragmas);
+ }
+
+ /**
+ * Set the Mustache template Loader instance.
+ */
+ public function setLoader(Loader $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Get the current Mustache template Loader instance.
+ *
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
+ * a StringLoader instance.
+ *
+ * @return Loader
+ */
+ public function getLoader()
+ {
+ if (!isset($this->loader)) {
+ $this->loader = new StringLoader();
+ }
+
+ return $this->loader;
+ }
+
+ /**
+ * Set the Mustache partials Loader instance.
+ */
+ public function setPartialsLoader(Loader $partialsLoader)
+ {
+ $this->partialsLoader = $partialsLoader;
+ }
+
+ /**
+ * Get the current Mustache partials Loader instance.
+ *
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
+ * an ArrayLoader instance.
+ *
+ * @return Loader
+ */
+ public function getPartialsLoader()
+ {
+ if (!isset($this->partialsLoader)) {
+ $this->partialsLoader = new ArrayLoader();
+ }
+
+ return $this->partialsLoader;
+ }
+
+ /**
+ * Set partials for the current partials Loader instance.
+ *
+ * @throws RuntimeException If the current Loader instance is immutable
+ */
+ public function setPartials(array $partials = [])
+ {
+ if (!isset($this->partialsLoader)) {
+ $this->partialsLoader = new ArrayLoader();
+ }
+
+ if (!$this->partialsLoader instanceof MutableLoader) {
+ throw new RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
+ }
+
+ $this->partialsLoader->setTemplates($partials);
+ }
+
+ /**
+ * Set an array of Mustache helpers.
+ *
+ * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
+ * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
+ * any template loaded by this Mustache instance.
+ *
+ * @throws InvalidArgumentException if $helpers is not an array or \Traversable
+ *
+ * @param array|\Traversable $helpers
+ */
+ public function setHelpers($helpers)
+ {
+ if (!is_array($helpers) && !$helpers instanceof \Traversable) {
+ throw new InvalidArgumentException('setHelpers expects an array of helpers');
+ }
+
+ $this->getHelpers()->clear();
+
+ foreach ($helpers as $name => $helper) {
+ $this->addHelper($name, $helper);
+ }
+ }
+
+ /**
+ * Get the current set of Mustache helpers.
+ *
+ * @see Mustache\Engine::setHelpers
+ *
+ * @return HelperCollection
+ */
+ public function getHelpers()
+ {
+ if (!isset($this->helpers)) {
+ $this->helpers = new HelperCollection();
+ }
+
+ return $this->helpers;
+ }
+
+ /**
+ * Add a new Mustache helper.
+ *
+ * @see Mustache\Engine::setHelpers
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function addHelper($name, $helper)
+ {
+ $this->getHelpers()->add($name, $helper);
+ }
+
+ /**
+ * Get a Mustache helper by name.
+ *
+ * @see Mustache\Engine::setHelpers
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function getHelper($name)
+ {
+ return $this->getHelpers()->get($name);
+ }
+
+ /**
+ * Check whether this Mustache instance has a helper.
+ *
+ * @see Mustache\Engine::setHelpers
+ *
+ * @param string $name
+ *
+ * @return bool True if the helper is present
+ */
+ public function hasHelper($name)
+ {
+ return $this->getHelpers()->has($name);
+ }
+
+ /**
+ * Remove a helper by name.
+ *
+ * @see Mustache\Engine::setHelpers
+ *
+ * @param string $name
+ */
+ public function removeHelper($name)
+ {
+ $this->getHelpers()->remove($name);
+ }
+
+ /**
+ * Set the Mustache Logger instance.
+ *
+ * @throws InvalidArgumentException If logger is not an instance of Mustache\Logger or Psr\Log\LoggerInterface
+ *
+ * @param Logger|LoggerInterface $logger
+ */
+ public function setLogger($logger = null)
+ {
+ // n.b. this uses `is_a` to prevent a dependency on Psr\Log
+ if ($logger !== null && !$logger instanceof Logger && !is_a($logger, 'Psr\\Log\\LoggerInterface')) {
+ throw new InvalidArgumentException('Expected an instance of Mustache\\Logger or Psr\\Log\\LoggerInterface.');
+ }
+
+ if ($this->getCache()->getLogger() === null) {
+ $this->getCache()->setLogger($logger);
+ }
+
+ $this->logger = $logger;
+ }
+
+ /**
+ * Get the current Mustache Logger instance.
+ *
+ * @return Logger|LoggerInterface
+ */
+ public function getLogger()
+ {
+ return $this->logger;
+ }
+
+ /**
+ * Set the Mustache Tokenizer instance.
+ */
+ public function setTokenizer(Tokenizer $tokenizer)
+ {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * Get the current Mustache Tokenizer instance.
+ *
+ * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Tokenizer
+ */
+ public function getTokenizer()
+ {
+ if (!isset($this->tokenizer)) {
+ $this->tokenizer = new Tokenizer();
+ }
+
+ return $this->tokenizer;
+ }
+
+ /**
+ * Set the Mustache Parser instance.
+ */
+ public function setParser(Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ /**
+ * Get the current Mustache Parser instance.
+ *
+ * If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Parser
+ */
+ public function getParser()
+ {
+ if (!isset($this->parser)) {
+ $this->parser = new Parser();
+ }
+
+ return $this->parser;
+ }
+
+ /**
+ * Set the Mustache Compiler instance.
+ */
+ public function setCompiler(Compiler $compiler)
+ {
+ $this->compiler = $compiler;
+ }
+
+ /**
+ * Get the current Mustache Compiler instance.
+ *
+ * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Compiler
+ */
+ public function getCompiler()
+ {
+ if (!isset($this->compiler)) {
+ $this->compiler = new Compiler();
+ }
+
+ return $this->compiler;
+ }
+
+ /**
+ * Set the Mustache Cache instance.
+ */
+ public function setCache(Cache $cache)
+ {
+ if (isset($this->logger) && $cache->getLogger() === null) {
+ $cache->setLogger($this->getLogger());
+ }
+
+ $this->cache = $cache;
+ }
+
+ /**
+ * Get the current Mustache Cache instance.
+ *
+ * If no Cache instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Cache
+ */
+ public function getCache()
+ {
+ if (!isset($this->cache)) {
+ $this->setCache(new NoopCache());
+ }
+
+ return $this->cache;
+ }
+
+ /**
+ * Get the current Lambda Cache instance.
+ *
+ * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache.
+ *
+ * @see Mustache\Engine::getCache
+ *
+ * @return Cache
+ */
+ protected function getLambdaCache()
+ {
+ if ($this->cacheLambdaTemplates) {
+ return $this->getCache();
+ }
+
+ if (!isset($this->lambdaCache)) {
+ $this->lambdaCache = new NoopCache();
+ }
+
+ return $this->lambdaCache;
+ }
+
+ /**
+ * Helper method to generate a Mustache template class.
+ *
+ * This method must be updated any time options are added which make it so
+ * the same template could be parsed and compiled multiple different ways.
+ *
+ * @param string|Source $source
+ *
+ * @return string Mustache Template class name
+ */
+ public function getTemplateClassName($source)
+ {
+ // For the most part, adding a new option here should do the trick.
+ //
+ // Pick a value here which is unique for each possible way the template
+ // could be compiled... but not necessarily unique per option value. See
+ // escape below, which only needs to differentiate between 'custom' and
+ // 'default' escapes.
+ //
+ // Keep this list in alphabetical order :)
+ $chunks = [
+ 'charset' => $this->charset,
+ 'delimiters' => $this->delimiters ?: '{{ }}',
+ 'entityFlags' => $this->entityFlags,
+ 'escape' => isset($this->escape) ? 'custom' : 'default',
+ 'key' => ($source instanceof Source) ? $source->getKey() : 'source',
+ 'options' => $this->getOptions(),
+ 'pragmas' => $this->getPragmas(),
+ 'strictCallables' => $this->strictCallables,
+ 'version' => self::VERSION,
+ ];
+
+ $key = json_encode($chunks);
+
+ // Template Source instances have already provided their own source key. For strings, just include the whole
+ // source string in the md5 hash.
+ if (!$source instanceof Source) {
+ $key .= "\n" . $source;
+ }
+
+ return $this->templateClassPrefix . md5($key);
+ }
+
+ /**
+ * Load a Mustache Template by name.
+ *
+ * @param string $name
+ *
+ * @return Template
+ */
+ public function loadTemplate($name)
+ {
+ return $this->loadSource($this->getLoader()->load($name));
+ }
+
+ /**
+ * Load a Mustache partial Template by name.
+ *
+ * This is a helper method used internally by Template instances for loading partial templates. You can most likely
+ * ignore it completely.
+ *
+ * @param string $name
+ *
+ * @return Template
+ */
+ public function loadPartial($name)
+ {
+ try {
+ if (isset($this->partialsLoader)) {
+ $loader = $this->partialsLoader;
+ } elseif (isset($this->loader) && !$this->loader instanceof StringLoader) {
+ $loader = $this->loader;
+ } else {
+ throw new UnknownTemplateException($name);
+ }
+
+ return $this->loadSource($loader->load($name));
+ } catch (UnknownTemplateException $e) {
+ // If the named partial cannot be found, log then return null.
+ $this->log(
+ Logger::WARNING,
+ 'Partial not found: "{name}"',
+ ['name' => $e->getTemplateName()]
+ );
+ }
+ }
+
+ /**
+ * Load a Mustache lambda Template by source.
+ *
+ * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
+ * likely ignore it completely.
+ *
+ * @param string $source
+ * @param string $delims (default: null)
+ *
+ * @return Template
+ */
+ public function loadLambda($source, $delims = null)
+ {
+ if ($delims !== null) {
+ $source = $delims . "\n" . $source;
+ }
+
+ return $this->loadSource($source, $this->getLambdaCache());
+ }
+
+ /**
+ * Instantiate and return a Mustache Template instance by source.
+ *
+ * Optionally provide a Mustache\Cache instance. This is used internally by Mustache\Engine::loadLambda to respect
+ * the 'cache_lambda_templates' configuration option.
+ *
+ * @see Mustache\Engine::loadTemplate
+ * @see Mustache\Engine::loadPartial
+ * @see Mustache\Engine::loadLambda
+ *
+ * @param string|Source $source
+ * @param Cache $cache (default: null)
+ *
+ * @return Template
+ */
+ private function loadSource($source, $cache = null)
+ {
+ $className = $this->getTemplateClassName($source);
+
+ if (!isset($this->templates[$className])) {
+ if ($cache === null || !$cache instanceof Cache) {
+ $cache = $this->getCache();
+ }
+
+ if (!class_exists($className, false)) {
+ if (!$cache->load($className)) {
+ $compiled = $this->compile($source);
+ $cache->cache($className, $compiled);
+ }
+ }
+
+ $this->log(
+ Logger::DEBUG,
+ 'Instantiating template: "{className}"',
+ ['className' => $className]
+ );
+
+ $this->templates[$className] = new $className($this);
+ }
+
+ return $this->templates[$className];
+ }
+
+ /**
+ * Helper method to tokenize a Mustache template.
+ *
+ * @see Mustache\Tokenizer::scan
+ *
+ * @param string $source
+ *
+ * @return array Tokens
+ */
+ private function tokenize($source)
+ {
+ return $this->getTokenizer()->scan($source, $this->delimiters);
+ }
+
+ /**
+ * Helper method to parse a Mustache template.
+ *
+ * @see Mustache\Parser::parse
+ *
+ * @param string $source
+ *
+ * @return array Token tree
+ */
+ private function parse($source)
+ {
+ $parser = $this->getParser();
+ $parser->setOptions($this->getOptions());
+ $parser->setPragmas($this->getPragmas());
+
+ return $parser->parse($this->tokenize($source));
+ }
+
+ /**
+ * Helper method to compile a Mustache template.
+ *
+ * @see Mustache\Compiler::compile
+ *
+ * @param string|Source $source
+ *
+ * @return string generated Mustache template class code
+ */
+ private function compile($source)
+ {
+ $name = $this->getTemplateClassName($source);
+
+ $this->log(
+ Logger::INFO,
+ 'Compiling template to "{className}" class',
+ ['className' => $name]
+ );
+
+ if ($source instanceof Source) {
+ $source = $source->getSource();
+ }
+ $tree = $this->parse($source);
+
+ $compiler = $this->getCompiler();
+ $compiler->setOptions($this->getOptions());
+ $compiler->setPragmas($this->getPragmas());
+
+ return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
+ }
+
+ /**
+ * Add a log record if logging is enabled.
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ */
+ private function log($level, $message, array $context = [])
+ {
+ if (isset($this->logger)) {
+ $this->logger->log($level, $message, $context);
+ }
+ }
+}
diff --git a/vendor/mustache/mustache/src/Exception.php b/vendor/mustache/mustache/src/Exception.php
new file mode 100644
index 0000000..c72be7b
--- /dev/null
+++ b/vendor/mustache/mustache/src/Exception.php
@@ -0,0 +1,17 @@
+token = $token;
+ parent::__construct($msg, 0, $previous);
+ }
+
+ /**
+ * @return array
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Exception/UnknownFilterException.php b/vendor/mustache/mustache/src/Exception/UnknownFilterException.php
new file mode 100644
index 0000000..4645c03
--- /dev/null
+++ b/vendor/mustache/mustache/src/Exception/UnknownFilterException.php
@@ -0,0 +1,38 @@
+filterName = $filterName;
+ $message = sprintf('Unknown filter: %s', $filterName);
+ parent::__construct($message, 0, $previous);
+ }
+
+ public function getFilterName()
+ {
+ return $this->filterName;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Exception/UnknownHelperException.php b/vendor/mustache/mustache/src/Exception/UnknownHelperException.php
new file mode 100644
index 0000000..1c8e475
--- /dev/null
+++ b/vendor/mustache/mustache/src/Exception/UnknownHelperException.php
@@ -0,0 +1,38 @@
+helperName = $helperName;
+ $message = sprintf('Unknown helper: %s', $helperName);
+ parent::__construct($message, 0, $previous);
+ }
+
+ public function getHelperName()
+ {
+ return $this->helperName;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Exception/UnknownTemplateException.php b/vendor/mustache/mustache/src/Exception/UnknownTemplateException.php
new file mode 100644
index 0000000..20432cf
--- /dev/null
+++ b/vendor/mustache/mustache/src/Exception/UnknownTemplateException.php
@@ -0,0 +1,38 @@
+templateName = $templateName;
+ $message = sprintf('Unknown template: %s', $templateName);
+ parent::__construct($message, 0, $previous);
+ }
+
+ public function getTemplateName()
+ {
+ return $this->templateName;
+ }
+}
diff --git a/vendor/mustache/mustache/src/HelperCollection.php b/vendor/mustache/mustache/src/HelperCollection.php
new file mode 100644
index 0000000..9b46b9c
--- /dev/null
+++ b/vendor/mustache/mustache/src/HelperCollection.php
@@ -0,0 +1,177 @@
+ $helper` pairs.
+ *
+ * @throws InvalidArgumentException if the $helpers argument isn't an array or \Traversable
+ *
+ * @param array|\Traversable $helpers (default: null)
+ */
+ public function __construct($helpers = null)
+ {
+ if ($helpers === null) {
+ return;
+ }
+
+ if (!is_array($helpers) && !$helpers instanceof \Traversable) {
+ throw new InvalidArgumentException('HelperCollection constructor expects an array of helpers');
+ }
+
+ foreach ($helpers as $name => $helper) {
+ $this->add($name, $helper);
+ }
+ }
+
+ /**
+ * Magic mutator.
+ *
+ * @see Mustache\HelperCollection::add
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function __set($name, $helper)
+ {
+ $this->add($name, $helper);
+ }
+
+ /**
+ * Add a helper to this collection.
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function add($name, $helper)
+ {
+ $this->helpers[$name] = $helper;
+ }
+
+ /**
+ * Magic accessor.
+ *
+ * @see Mustache\HelperCollection::get
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Get a helper by name.
+ *
+ * @throws UnknownHelperException If helper does not exist
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ throw new UnknownHelperException($name);
+ }
+
+ return $this->helpers[$name];
+ }
+
+ /**
+ * Magic isset().
+ *
+ * @see Mustache\HelperCollection::has
+ *
+ * @param string $name
+ *
+ * @return bool True if helper is present
+ */
+ public function __isset($name)
+ {
+ return $this->has($name);
+ }
+
+ /**
+ * Check whether a given helper is present in the collection.
+ *
+ * @param string $name
+ *
+ * @return bool True if helper is present
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->helpers);
+ }
+
+ /**
+ * Magic unset().
+ *
+ * @see Mustache\HelperCollection::remove
+ *
+ * @param string $name
+ */
+ public function __unset($name)
+ {
+ $this->remove($name);
+ }
+
+ /**
+ * Check whether a given helper is present in the collection.
+ *
+ * @throws UnknownHelperException if the requested helper is not present
+ *
+ * @param string $name
+ */
+ public function remove($name)
+ {
+ if (!$this->has($name)) {
+ throw new UnknownHelperException($name);
+ }
+
+ unset($this->helpers[$name]);
+ }
+
+ /**
+ * Clear the helper collection.
+ *
+ * Removes all helpers from this collection
+ */
+ public function clear()
+ {
+ $this->helpers = [];
+ }
+
+ /**
+ * Check whether the helper collection is empty.
+ *
+ * @return bool True if the collection is empty
+ */
+ public function isEmpty()
+ {
+ return empty($this->helpers);
+ }
+}
diff --git a/vendor/mustache/mustache/src/LambdaHelper.php b/vendor/mustache/mustache/src/LambdaHelper.php
new file mode 100644
index 0000000..9ac58cc
--- /dev/null
+++ b/vendor/mustache/mustache/src/LambdaHelper.php
@@ -0,0 +1,96 @@
+ =}}`. (default: null)
+ */
+ public function __construct(Engine $mustache, Context $context, $delims = null)
+ {
+ $this->mustache = $mustache;
+ $this->context = $context;
+ $this->delims = $delims;
+ }
+
+ /**
+ * Render a string as a Mustache template with the current rendering context.
+ *
+ * @param string $string
+ *
+ * @return string Rendered template
+ */
+ public function render($string)
+ {
+ $value = $this->mustache
+ ->loadLambda((string) $string, $this->delims)
+ ->renderInternal($this->context);
+
+ return $this->mustache->getDoubleRenderLambdas() ? $value : $this->preventRender($value);
+ }
+
+ /**
+ * Prevent rendering of a string as a Mustache template.
+ *
+ * This is useful for returning a raw string from a lambda without processing it as a Mustache template.
+ *
+ * @see RenderedString
+ *
+ * @param string $value The raw string value to return
+ *
+ * @return RenderedString A RenderedString instance containing the raw value
+ */
+ public function preventRender($value)
+ {
+ return new RenderedString($value);
+ }
+
+ /**
+ * Render a string as a Mustache template with the current rendering context.
+ *
+ * @param string $string
+ *
+ * @return string Rendered template
+ */
+ public function __invoke($string)
+ {
+ return $this->render($string);
+ }
+
+ /**
+ * Get a Lambda Helper with custom delimiters.
+ *
+ * @param string $delims Custom delimiters, in the format `{{= <% %> =}}`
+ *
+ * @return LambdaHelper
+ */
+ public function withDelimiters($delims)
+ {
+ return new self($this->mustache, $this->context, $delims);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader.php b/vendor/mustache/mustache/src/Loader.php
new file mode 100644
index 0000000..ef3c780
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader.php
@@ -0,0 +1,28 @@
+ '{{ bar }}',
+ * 'baz' => 'Hey {{ qux }}!'
+ * );
+ *
+ * $tpl = $loader->load('foo'); // '{{ bar }}'
+ *
+ * The ArrayLoader is used internally as a partials loader by Mustache\Engine instance when an array of partials
+ * is set. It can also be used as a quick-and-dirty Template loader.
+ */
+class ArrayLoader implements Loader, MutableLoader
+{
+ private $templates;
+
+ /**
+ * ArrayLoader constructor.
+ *
+ * @param array $templates Associative array of Template source (default: [])
+ */
+ public function __construct(array $templates = [])
+ {
+ $this->templates = $templates;
+ }
+
+ /**
+ * Load a Template.
+ *
+ * @throws UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ if (!isset($this->templates[$name])) {
+ throw new UnknownTemplateException($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Set an associative array of Template sources for this loader.
+ */
+ public function setTemplates(array $templates)
+ {
+ $this->templates = $templates;
+ }
+
+ /**
+ * Set a Template source by name.
+ *
+ * @param string $name
+ * @param string $template Mustache Template source
+ */
+ public function setTemplate($name, $template)
+ {
+ $this->templates[$name] = $template;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader/CascadingLoader.php b/vendor/mustache/mustache/src/Loader/CascadingLoader.php
new file mode 100644
index 0000000..cecb2d8
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader/CascadingLoader.php
@@ -0,0 +1,72 @@
+loaders = [];
+ foreach ($loaders as $loader) {
+ $this->addLoader($loader);
+ }
+ }
+
+ /**
+ * Add a Loader instance.
+ */
+ public function addLoader(Loader $loader)
+ {
+ $this->loaders[] = $loader;
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * @throws UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ foreach ($this->loaders as $loader) {
+ try {
+ return $loader->load($name);
+ } catch (UnknownTemplateException $e) {
+ // do nothing, check the next loader.
+ }
+ }
+
+ throw new UnknownTemplateException($name);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader/FilesystemLoader.php b/vendor/mustache/mustache/src/Loader/FilesystemLoader.php
new file mode 100644
index 0000000..f30f720
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader/FilesystemLoader.php
@@ -0,0 +1,141 @@
+load('foo'); // equivalent to `file_get_contents(__DIR__.'/views/foo.mustache');
+ *
+ * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
+ *
+ * $m = new \Mustache\Engine([
+ * 'loader' => new FilesystemLoader(__DIR__.'/views'),
+ * 'partials_loader' => new FilesystemLoader(__DIR__.'/views/partials'),
+ * ]);
+ */
+class FilesystemLoader implements Loader
+{
+ private $baseDir;
+ private $extension = '.mustache';
+ private $templates = [];
+
+ /**
+ * Mustache filesystem Loader constructor.
+ *
+ * Passing an $options array allows overriding certain Loader options during instantiation:
+ *
+ * $options = [
+ * // The filename extension used for Mustache templates. Defaults to '.mustache'
+ * 'extension' => '.ms',
+ * ];
+ *
+ * @throws RuntimeException if $baseDir does not exist
+ *
+ * @param string $baseDir Base directory containing Mustache template files
+ * @param array $options Loader options (default: [])
+ */
+ public function __construct($baseDir, array $options = [])
+ {
+ $this->baseDir = $baseDir;
+
+ if (strpos($this->baseDir, '://') === false) {
+ $this->baseDir = realpath($this->baseDir);
+ }
+
+ if ($this->shouldCheckPath() && !is_dir($this->baseDir)) {
+ throw new RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir));
+ }
+
+ if (array_key_exists('extension', $options)) {
+ if (empty($options['extension'])) {
+ $this->extension = '';
+ } else {
+ $this->extension = '.' . ltrim($options['extension'], '.');
+ }
+ }
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * $loader = new FilesystemLoader(__DIR__.'/views');
+ * $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache";
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ if (!isset($this->templates[$name])) {
+ $this->templates[$name] = $this->loadFile($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Helper function for loading a Mustache file by name.
+ *
+ * @throws UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ protected function loadFile($name)
+ {
+ $fileName = $this->getFileName($name);
+
+ if ($this->shouldCheckPath() && !file_exists($fileName)) {
+ throw new UnknownTemplateException($name);
+ }
+
+ return file_get_contents($fileName);
+ }
+
+ /**
+ * Helper function for getting a Mustache template file name.
+ *
+ * @param string $name
+ *
+ * @return string Template file name
+ */
+ protected function getFileName($name)
+ {
+ $fileName = $this->baseDir . '/' . $name;
+ if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
+ $fileName .= $this->extension;
+ }
+
+ return $fileName;
+ }
+
+ /**
+ * Only check if baseDir is a directory and requested templates are files if
+ * baseDir is using the filesystem stream wrapper.
+ *
+ * @return bool Whether to check `is_dir` and `file_exists`
+ */
+ protected function shouldCheckPath()
+ {
+ return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader/InlineLoader.php b/vendor/mustache/mustache/src/Loader/InlineLoader.php
new file mode 100644
index 0000000..97e12d0
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader/InlineLoader.php
@@ -0,0 +1,129 @@
+load('hello');
+ * $goodbye = $loader->load('goodbye');
+ *
+ * __halt_compiler();
+ *
+ * @@ hello
+ * Hello, {{ planet }}!
+ *
+ * @@ goodbye
+ * Goodbye, cruel {{ planet }}
+ *
+ * Templates are deliniated by lines containing only `@@ name`.
+ *
+ * The InlineLoader is well-suited to micro-frameworks such as Silex:
+ *
+ * $app->register(new MustacheServiceProvider, [
+ * 'mustache.loader' => new InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
+ * ]);
+ *
+ * $app->get('/{name}', function ($name) use ($app) {
+ * return $app['mustache']->render('hello', compact('name'));
+ * })
+ * ->value('name', 'world');
+ *
+ * // ...
+ *
+ * __halt_compiler();
+ *
+ * @@ hello
+ * Hello, {{ name }}!
+ */
+class InlineLoader implements Loader
+{
+ protected $fileName;
+ protected $offset;
+ protected $templates;
+
+ /**
+ * The InlineLoader requires a filename and offset to process templates.
+ *
+ * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually
+ * perfectly suited to the job:
+ *
+ * $loader = new InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
+ *
+ * Note that this only works if the loader is instantiated inside the same
+ * file as the inline templates. If the templates are located in another
+ * file, it would be necessary to manually specify the filename and offset.
+ *
+ * @param string $fileName The file to parse for inline templates
+ * @param int $offset A string offset for the start of the templates.
+ * This usually coincides with the `__halt_compiler`
+ * call, and the `__COMPILER_HALT_OFFSET__`
+ */
+ public function __construct($fileName, $offset)
+ {
+ if (!is_file($fileName)) {
+ throw new InvalidArgumentException('InlineLoader expects a valid filename.');
+ }
+
+ if (!is_int($offset) || $offset < 0) {
+ throw new InvalidArgumentException('InlineLoader expects a valid file offset.');
+ }
+
+ $this->fileName = $fileName;
+ $this->offset = $offset;
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * @throws UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ $this->loadTemplates();
+
+ if (!array_key_exists($name, $this->templates)) {
+ throw new UnknownTemplateException($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Parse and load templates from the end of a source file.
+ */
+ protected function loadTemplates()
+ {
+ if ($this->templates === null) {
+ $this->templates = [];
+ $data = file_get_contents($this->fileName, false, null, $this->offset);
+ foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
+ if (trim($chunk) !== '') {
+ list($name, $content) = explode("\n", $chunk, 2);
+ $this->templates[trim($name)] = trim($content);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader/MutableLoader.php b/vendor/mustache/mustache/src/Loader/MutableLoader.php
new file mode 100644
index 0000000..218b570
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader/MutableLoader.php
@@ -0,0 +1,28 @@
+ '.ms',
+ * 'stat_props' => ['size', 'mtime'],
+ * ];
+ *
+ * Specifying 'stat_props' overrides the stat properties used to invalidate the template cache. By default, this
+ * uses 'mtime' and 'size', but this can be set to any of the properties supported by stat():
+ *
+ * http://php.net/manual/en/function.stat.php
+ *
+ * You can also disable filesystem stat entirely:
+ *
+ * $options = ['stat_props' => null];
+ *
+ * But with great power comes great responsibility. Namely, if you disable stat-based cache invalidation,
+ * YOU MUST CLEAR THE TEMPLATE CACHE YOURSELF when your templates change. Make it part of your build or deploy
+ * process so you don't forget!
+ *
+ * @throws RuntimeException if $baseDir does not exist
+ *
+ * @param string $baseDir base directory containing Mustache template files
+ * @param array $options Loader options (default: [])
+ */
+ public function __construct($baseDir, array $options = [])
+ {
+ parent::__construct($baseDir, $options);
+
+ if (array_key_exists('stat_props', $options)) {
+ if (empty($options['stat_props'])) {
+ $this->statProps = [];
+ } else {
+ $this->statProps = $options['stat_props'];
+ }
+ } else {
+ $this->statProps = ['size', 'mtime'];
+ }
+ }
+
+ /**
+ * Helper function for loading a Mustache file by name.
+ *
+ * @throws UnknownTemplateException if a template file is not found
+ *
+ * @param string $name
+ *
+ * @return Source Mustache Template source
+ */
+ protected function loadFile($name)
+ {
+ $fileName = $this->getFileName($name);
+
+ if (!file_exists($fileName)) {
+ throw new UnknownTemplateException($name);
+ }
+
+ return new FilesystemSource($fileName, $this->statProps);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Loader/StringLoader.php b/vendor/mustache/mustache/src/Loader/StringLoader.php
new file mode 100644
index 0000000..4d82639
--- /dev/null
+++ b/vendor/mustache/mustache/src/Loader/StringLoader.php
@@ -0,0 +1,43 @@
+load('{{ foo }}'); // '{{ foo }}'
+ *
+ * This is the default Template Loader instance used by Mustache:
+ *
+ * $m = new \Mustache\Engine;
+ * $tpl = $m->loadTemplate('{{ foo }}');
+ * echo $tpl->render(['foo' => 'bar']); // "bar"
+ */
+class StringLoader implements Loader
+{
+ /**
+ * Load a Template by source.
+ *
+ * @param string $name Mustache Template source
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ return $name;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Logger.php b/vendor/mustache/mustache/src/Logger.php
new file mode 100644
index 0000000..c36eb78
--- /dev/null
+++ b/vendor/mustache/mustache/src/Logger.php
@@ -0,0 +1,102 @@
+log(Logger::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ */
+ public function alert($message, array $context = [])
+ {
+ $this->log(Logger::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ */
+ public function critical($message, array $context = [])
+ {
+ $this->log(Logger::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ */
+ public function error($message, array $context = [])
+ {
+ $this->log(Logger::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ */
+ public function warning($message, array $context = [])
+ {
+ $this->log(Logger::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ */
+ public function notice($message, array $context = [])
+ {
+ $this->log(Logger::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ */
+ public function info($message, array $context = [])
+ {
+ $this->log(Logger::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ */
+ public function debug($message, array $context = [])
+ {
+ $this->log(Logger::DEBUG, $message, $context);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Logger/StreamLogger.php b/vendor/mustache/mustache/src/Logger/StreamLogger.php
new file mode 100644
index 0000000..1dec1ca
--- /dev/null
+++ b/vendor/mustache/mustache/src/Logger/StreamLogger.php
@@ -0,0 +1,199 @@
+ 100,
+ self::INFO => 200,
+ self::NOTICE => 250,
+ self::WARNING => 300,
+ self::ERROR => 400,
+ self::CRITICAL => 500,
+ self::ALERT => 550,
+ self::EMERGENCY => 600,
+ ];
+
+ protected $level;
+ protected $stream = null;
+ protected $url = null;
+
+ /**
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param resource|string $stream Resource instance or URL
+ * @param int $level The minimum logging level at which this handler will be triggered
+ */
+ public function __construct($stream, $level = Logger::ERROR)
+ {
+ $this->setLevel($level);
+
+ if (is_resource($stream)) {
+ $this->stream = $stream;
+ } else {
+ $this->url = $stream;
+ }
+ }
+
+ /**
+ * Close stream resources.
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /**
+ * Set the minimum logging level.
+ *
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param int $level The minimum logging level which will be written
+ */
+ public function setLevel($level)
+ {
+ if (!array_key_exists($level, self::$levels)) {
+ throw new InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
+ }
+
+ $this->level = $level;
+ }
+
+ /**
+ * Get the current minimum logging level.
+ *
+ * @return int
+ */
+ public function getLevel()
+ {
+ return $this->level;
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param mixed $level
+ * @param string $message
+ */
+ public function log($level, $message, array $context = [])
+ {
+ if (!array_key_exists($level, self::$levels)) {
+ throw new InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
+ }
+
+ if (self::$levels[$level] >= self::$levels[$this->level]) {
+ $this->writeLog($level, $message, $context);
+ }
+ }
+
+ /**
+ * Write a record to the log.
+ *
+ * @throws LogicException If neither a stream resource nor url is present
+ * @throws RuntimeException If the stream url cannot be opened
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ */
+ protected function writeLog($level, $message, array $context = [])
+ {
+ if (!is_resource($this->stream)) {
+ if (!isset($this->url)) {
+ throw new LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
+ }
+
+ $this->stream = fopen($this->url, 'a');
+ if (!is_resource($this->stream)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url));
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ fwrite($this->stream, self::formatLine($level, $message, $context));
+ }
+
+ /**
+ * Gets the name of the logging level.
+ *
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param int $level
+ *
+ * @return string
+ */
+ protected static function getLevelName($level)
+ {
+ return strtoupper($level);
+ }
+
+ /**
+ * Format a log line for output.
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ *
+ * @return string
+ */
+ protected static function formatLine($level, $message, array $context = [])
+ {
+ return sprintf(
+ "%s: %s\n",
+ self::getLevelName($level),
+ self::interpolateMessage($message, $context)
+ );
+ }
+
+ /**
+ * Interpolate context values into the message placeholders.
+ *
+ * @param string $message
+ *
+ * @return string
+ */
+ protected static function interpolateMessage($message, array $context = [])
+ {
+ if (strpos($message, '{') === false) {
+ return $message;
+ }
+
+ // build a replacement array with braces around the context keys
+ $replace = [];
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ // interpolate replacement values into the the message and return
+ return strtr($message, $replace);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Parser.php b/vendor/mustache/mustache/src/Parser.php
new file mode 100644
index 0000000..c08dc10
--- /dev/null
+++ b/vendor/mustache/mustache/src/Parser.php
@@ -0,0 +1,392 @@
+lineNum = -1;
+ $this->lineTokens = 0;
+ $this->pragmas = $this->defaultPragmas;
+
+ $this->pragmaFilters = isset($this->pragmas[Engine::PRAGMA_FILTERS]);
+
+ return $this->buildTree($tokens);
+ }
+
+ /**
+ * Disable optional Mustache specs.
+ *
+ * @internal Users should set options in Mustache\Engine, not here :)
+ *
+ * @param bool[] $options
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['dynamic_names'])) {
+ $this->dynamicNames = $options['dynamic_names'] !== false;
+ }
+
+ if (isset($options['inheritance'])) {
+ $this->inheritance = $options['inheritance'] !== false;
+ }
+ }
+
+ /**
+ * Enable pragmas across all templates, regardless of the presence of pragma
+ * tags in the individual templates.
+ *
+ * @internal Users should set global pragmas in Mustache\Engine, not here :)
+ *
+ * @param string[] $pragmas
+ */
+ public function setPragmas(array $pragmas)
+ {
+ $this->pragmas = [];
+ foreach ($pragmas as $pragma) {
+ $this->enablePragma($pragma);
+ }
+ $this->defaultPragmas = $this->pragmas;
+ }
+
+ /**
+ * Helper method for recursively building a parse tree.
+ *
+ * @throws SyntaxException when nesting errors or mismatched section tags are encountered
+ *
+ * @param array &$tokens Set of Mustache tokens
+ * @param array $parent Parent token (default: null)
+ *
+ * @return array Mustache Token parse tree
+ */
+ private function buildTree(array &$tokens, $parent = null)
+ {
+ $nodes = [];
+
+ while (!empty($tokens)) {
+ $token = array_shift($tokens);
+
+ if ($token[Tokenizer::LINE] === $this->lineNum) {
+ $this->lineTokens++;
+ } else {
+ $this->lineNum = $token[Tokenizer::LINE];
+ $this->lineTokens = 0;
+ }
+
+ if ($token[Tokenizer::TYPE] !== Tokenizer::T_COMMENT) {
+ if (isset($token[Tokenizer::NAME])) {
+ list($name, $isDynamic) = $this->getDynamicName($token);
+ if ($isDynamic) {
+ $token[Tokenizer::NAME] = $name;
+ $token[Tokenizer::DYNAMIC] = true;
+ }
+ }
+
+ if ($this->pragmaFilters && isset($token[Tokenizer::NAME])) {
+ list($name, $filters) = $this->getNameAndFilters($token[Tokenizer::NAME]);
+ if (!empty($filters)) {
+ $token[Tokenizer::NAME] = $name;
+ $token[Tokenizer::FILTERS] = $filters;
+ }
+ }
+ }
+
+ switch ($token[Tokenizer::TYPE]) {
+ case Tokenizer::T_DELIM_CHANGE:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $this->clearStandaloneLines($nodes, $tokens);
+ break;
+
+ case Tokenizer::T_SECTION:
+ case Tokenizer::T_INVERTED:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $this->buildTree($tokens, $token);
+ break;
+
+ case Tokenizer::T_END_SECTION:
+ if (!isset($parent)) {
+ $msg = sprintf(
+ 'Unexpected closing tag: /%s on line %d',
+ $token[Tokenizer::NAME],
+ $token[Tokenizer::LINE]
+ );
+ throw new SyntaxException($msg, $token);
+ }
+
+ $sameName = $token[Tokenizer::NAME] !== $parent[Tokenizer::NAME];
+ $tokenDynamic = isset($token[Tokenizer::DYNAMIC]) && $token[Tokenizer::DYNAMIC];
+ $parentDynamic = isset($parent[Tokenizer::DYNAMIC]) && $parent[Tokenizer::DYNAMIC];
+
+ if ($sameName || ($tokenDynamic !== $parentDynamic)) {
+ $msg = sprintf(
+ 'Nesting error: %s%s (on line %d) vs. %s%s (on line %d)',
+ $parentDynamic ? '*' : '',
+ $parent[Tokenizer::NAME],
+ $parent[Tokenizer::LINE],
+ $tokenDynamic ? '*' : '',
+ $token[Tokenizer::NAME],
+ $token[Tokenizer::LINE]
+ );
+ throw new SyntaxException($msg, $token);
+ }
+
+ $this->clearStandaloneLines($nodes, $tokens);
+ $parent[Tokenizer::END] = $token[Tokenizer::INDEX];
+ $parent[Tokenizer::NODES] = $nodes;
+
+ return $parent;
+
+ case Tokenizer::T_PARTIAL:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ //store the whitespace prefix for laters!
+ if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
+ $token[Tokenizer::INDENT] = $indent[Tokenizer::VALUE];
+ }
+ $nodes[] = $token;
+ break;
+
+ case Tokenizer::T_PARENT:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $nodes[] = $this->buildTree($tokens, $token);
+ break;
+
+ case Tokenizer::T_BLOCK_VAR:
+ if ($this->inheritance) {
+ if (isset($parent) && $parent[Tokenizer::TYPE] === Tokenizer::T_PARENT) {
+ $token[Tokenizer::TYPE] = Tokenizer::T_BLOCK_ARG;
+ }
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $this->buildTree($tokens, $token);
+ } else {
+ // pretend this was just a normal "escaped" token...
+ $token[Tokenizer::TYPE] = Tokenizer::T_ESCAPED;
+ // TODO: figure out how to figure out if there was a space after this dollar:
+ $token[Tokenizer::NAME] = '$' . $token[Tokenizer::NAME];
+ $nodes[] = $token;
+ }
+ break;
+
+ case Tokenizer::T_PRAGMA:
+ $this->enablePragma($token[Tokenizer::NAME]);
+ // no break
+
+ case Tokenizer::T_COMMENT:
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $token;
+ break;
+
+ default:
+ $nodes[] = $token;
+ break;
+ }
+ }
+
+ if (isset($parent)) {
+ $msg = sprintf(
+ 'Missing closing tag: %s opened on line %d',
+ $parent[Tokenizer::NAME],
+ $parent[Tokenizer::LINE]
+ );
+ throw new SyntaxException($msg, $parent);
+ }
+
+ return $nodes;
+ }
+
+ /**
+ * Clear standalone line tokens.
+ *
+ * Returns a whitespace token for indenting partials, if applicable.
+ *
+ * @param array $nodes Parsed nodes
+ * @param array $tokens Tokens to be parsed
+ *
+ * @return array|null Resulting indent token, if any
+ */
+ private function clearStandaloneLines(array &$nodes, array &$tokens)
+ {
+ if ($this->lineTokens > 1) {
+ // this is the third or later node on this line, so it can't be standalone
+ return;
+ }
+
+ $prev = null;
+ if ($this->lineTokens === 1) {
+ // this is the second node on this line, so it can't be standalone
+ // unless the previous node is whitespace.
+ if ($prev = end($nodes)) {
+ if (!$this->tokenIsWhitespace($prev)) {
+ return;
+ }
+ }
+ }
+
+ if ($next = reset($tokens)) {
+ // If we're on a new line, bail.
+ if ($next[Tokenizer::LINE] !== $this->lineNum) {
+ return;
+ }
+
+ // If the next token isn't whitespace, bail.
+ if (!$this->tokenIsWhitespace($next)) {
+ return;
+ }
+
+ if (count($tokens) !== 1) {
+ // Unless it's the last token in the template, the next token
+ // must end in newline for this to be standalone.
+ if (substr($next[Tokenizer::VALUE], -1) !== "\n") {
+ return;
+ }
+ }
+
+ // Discard the whitespace suffix
+ array_shift($tokens);
+ }
+
+ if ($prev) {
+ // Return the whitespace prefix, if any
+ return array_pop($nodes);
+ }
+ }
+
+ /**
+ * Check whether token is a whitespace token.
+ *
+ * True if token type is T_TEXT and value is all whitespace characters.
+ *
+ * @return bool True if token is a whitespace token
+ */
+ private function tokenIsWhitespace(array $token)
+ {
+ if ($token[Tokenizer::TYPE] === Tokenizer::T_TEXT) {
+ return preg_match('/^\s*$/', $token[Tokenizer::VALUE]);
+ }
+
+ return false;
+ }
+
+ /**
+ * Check whether a token is allowed inside a parent tag.
+ *
+ * @throws SyntaxException if an invalid token is found inside a parent tag
+ *
+ * @param array|null $parent
+ */
+ private function checkIfTokenIsAllowedInParent($parent, array $token)
+ {
+ if (isset($parent) && $parent[Tokenizer::TYPE] === Tokenizer::T_PARENT) {
+ throw new SyntaxException('Illegal content in < parent tag', $token);
+ }
+ }
+
+ /**
+ * Parse dynamic names.
+ *
+ * @throws SyntaxException when a tag does not allow *
+ * @throws SyntaxException on multiple *s, or dots or filters with *
+ */
+ private function getDynamicName(array $token)
+ {
+ $name = $token[Tokenizer::NAME];
+ $isDynamic = false;
+
+ if ($this->dynamicNames && preg_match('/^\s*\*\s*/', $name)) {
+ $this->ensureTagAllowsDynamicNames($token);
+ $name = preg_replace('/^\s*\*\s*/', '', $name);
+ $isDynamic = true;
+ }
+
+ return [$name, $isDynamic];
+ }
+
+ /**
+ * Check whether the given token supports dynamic tag names.
+ *
+ * @throws SyntaxException when a tag does not allow *
+ */
+ private function ensureTagAllowsDynamicNames(array $token)
+ {
+ switch ($token[Tokenizer::TYPE]) {
+ case Tokenizer::T_PARTIAL:
+ case Tokenizer::T_PARENT:
+ case Tokenizer::T_END_SECTION:
+ return;
+ }
+
+ $msg = sprintf(
+ 'Invalid dynamic name: %s in %s tag',
+ $token[Tokenizer::NAME],
+ Tokenizer::getTagName($token[Tokenizer::TYPE])
+ );
+
+ throw new SyntaxException($msg, $token);
+ }
+
+ /**
+ * Split a tag name into name and filters.
+ *
+ * @param string $name
+ *
+ * @return array [Tag name, Array of filters]
+ */
+ private function getNameAndFilters($name)
+ {
+ $filters = array_map('trim', explode('|', $name));
+ $name = array_shift($filters);
+
+ return [$name, $filters];
+ }
+
+ /**
+ * Enable a pragma.
+ *
+ * @param string $name
+ */
+ private function enablePragma($name)
+ {
+ $this->pragmas[$name] = true;
+
+ switch ($name) {
+ case Engine::PRAGMA_FILTERS:
+ $this->pragmaFilters = true;
+ break;
+ }
+ }
+}
diff --git a/vendor/mustache/mustache/src/RenderedString.php b/vendor/mustache/mustache/src/RenderedString.php
new file mode 100644
index 0000000..3c98911
--- /dev/null
+++ b/vendor/mustache/mustache/src/RenderedString.php
@@ -0,0 +1,51 @@
+value = (string) $value;
+ }
+
+ public function __toString()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get the rendered string value.
+ *
+ * @return string The rendered string value
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Source.php b/vendor/mustache/mustache/src/Source.php
new file mode 100644
index 0000000..205d1cd
--- /dev/null
+++ b/vendor/mustache/mustache/src/Source.php
@@ -0,0 +1,39 @@
+fileName = $fileName;
+ $this->statProps = $statProps;
+ }
+
+ /**
+ * Get the Source key (used to generate the compiled class name).
+ *
+ * @throws RuntimeException when a source file cannot be read
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ $chunks = [
+ 'fileName' => $this->fileName,
+ ];
+
+ if (!empty($this->statProps)) {
+ if (!isset($this->stat)) {
+ $this->stat = @stat($this->fileName);
+ }
+
+ if ($this->stat === false) {
+ throw new RuntimeException(sprintf('Failed to read source file "%s".', $this->fileName));
+ }
+
+ foreach ($this->statProps as $prop) {
+ $chunks[$prop] = $this->stat[$prop];
+ }
+ }
+
+ return json_encode($chunks);
+ }
+
+ /**
+ * Get the template Source.
+ *
+ * @return string
+ */
+ public function getSource()
+ {
+ return file_get_contents($this->fileName);
+ }
+}
diff --git a/vendor/mustache/mustache/src/Template.php b/vendor/mustache/mustache/src/Template.php
new file mode 100644
index 0000000..2d9219f
--- /dev/null
+++ b/vendor/mustache/mustache/src/Template.php
@@ -0,0 +1,193 @@
+mustache = $mustache;
+ }
+
+ /**
+ * Mustache Template instances can be treated as a function and rendered by simply calling them.
+ *
+ * $m = new \Mustache\Engine;
+ * $tpl = $m->loadTemplate('Hello, {{ name }}!');
+ * echo $tpl(['name' => 'World']); // "Hello, World!"
+ *
+ * @see \Mustache\Template::render
+ *
+ * @param mixed $context Array or object rendering context (default: [])
+ *
+ * @return string Rendered template
+ */
+ public function __invoke($context = [])
+ {
+ return $this->render($context);
+ }
+
+ /**
+ * Render this template given the rendering context.
+ *
+ * @param mixed $context Array or object rendering context (default: [])
+ *
+ * @return string Rendered template
+ */
+ public function render($context = [])
+ {
+ return $this->renderInternal(
+ $this->prepareContextStack($context)
+ );
+ }
+
+ /**
+ * Internal rendering method implemented by Mustache Template concrete subclasses.
+ *
+ * This is where the magic happens :)
+ *
+ * NOTE: This method is not part of the Mustache.php public API.
+ *
+ * @param string $indent (default: '')
+ *
+ * @return string Rendered template
+ */
+ abstract public function renderInternal(Context $context, $indent = '');
+
+ /**
+ * Tests whether a value should be iterated over (e.g. in a section context).
+ *
+ * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
+ * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
+ * Java, Python, etc.
+ *
+ * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
+ * between between a list of things (numeric, normalized array) and a set of variables to be used as section context
+ * (associative array). In other words, this will be iterated over:
+ *
+ * $items = [
+ * ['name' => 'foo'],
+ * ['name' => 'bar'],
+ * ['name' => 'baz'],
+ * ];
+ *
+ * ... but this will be used as a section context block:
+ *
+ * $items = [
+ * 1 => ['name' => 'foo'],
+ * 'banana' => ['name' => 'bar'],
+ * 42 => ['name' => 'baz'],
+ * ];
+ *
+ * @param mixed $value
+ *
+ * @return bool True if the value is 'iterable'
+ */
+ protected function isIterable($value)
+ {
+ switch (gettype($value)) {
+ case 'object':
+ return $value instanceof \Traversable;
+
+ case 'array':
+ $i = 0;
+ foreach ($value as $k => $v) {
+ if ($k !== $i++) {
+ return false;
+ }
+ }
+
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Helper method to prepare the Context stack.
+ *
+ * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
+ *
+ * @param mixed $context Optional first context frame (default: null)
+ *
+ * @return Context
+ */
+ protected function prepareContextStack($context = null)
+ {
+ $stack = new Context(null, $this->mustache->getBuggyPropertyShadowing());
+
+ $helpers = $this->mustache->getHelpers();
+ if (!$helpers->isEmpty()) {
+ $stack->push($helpers);
+ }
+
+ if (!empty($context)) {
+ $stack->push($context);
+ }
+
+ return $stack;
+ }
+
+ /**
+ * Resolve a context value.
+ *
+ * Invoke the value if it is callable, otherwise return the value.
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ protected function resolveValue($value, Context $context)
+ {
+ if (!$this->lambdas) {
+ return $value;
+ }
+
+ if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) {
+ $result = call_user_func($value);
+
+ if (is_string($result)) {
+ return $this->mustache
+ ->loadLambda($result)
+ ->renderInternal($context);
+ }
+
+ return $result;
+ }
+
+ return $value;
+ }
+}
diff --git a/vendor/mustache/mustache/src/Tokenizer.php b/vendor/mustache/mustache/src/Tokenizer.php
new file mode 100644
index 0000000..4b4821c
--- /dev/null
+++ b/vendor/mustache/mustache/src/Tokenizer.php
@@ -0,0 +1,412 @@
+';
+ const T_PARENT = '<';
+ const T_DELIM_CHANGE = '=';
+ const T_ESCAPED = '_v';
+ const T_UNESCAPED = '{';
+ const T_UNESCAPED_2 = '&';
+ const T_TEXT = '_t';
+ const T_PRAGMA = '%';
+ const T_BLOCK_VAR = '$';
+ const T_BLOCK_ARG = '$arg';
+
+ // Valid token types
+ private static $tagTypes = [
+ self::T_SECTION => true,
+ self::T_INVERTED => true,
+ self::T_END_SECTION => true,
+ self::T_COMMENT => true,
+ self::T_PARTIAL => true,
+ self::T_PARENT => true,
+ self::T_DELIM_CHANGE => true,
+ self::T_ESCAPED => true,
+ self::T_UNESCAPED => true,
+ self::T_UNESCAPED_2 => true,
+ self::T_PRAGMA => true,
+ self::T_BLOCK_VAR => true,
+ ];
+
+ private static $tagNames = [
+ self::T_SECTION => 'section',
+ self::T_INVERTED => 'inverted section',
+ self::T_END_SECTION => 'section end',
+ self::T_COMMENT => 'comment',
+ self::T_PARTIAL => 'partial',
+ self::T_PARENT => 'parent',
+ self::T_DELIM_CHANGE => 'set delimiter',
+ self::T_ESCAPED => 'variable',
+ self::T_UNESCAPED => 'unescaped variable',
+ self::T_UNESCAPED_2 => 'unescaped variable',
+ self::T_PRAGMA => 'pragma',
+ self::T_BLOCK_VAR => 'block variable',
+ self::T_BLOCK_ARG => 'block variable',
+ ];
+
+ // Token properties
+ const TYPE = 'type';
+ const NAME = 'name';
+ const DYNAMIC = 'dynamic';
+ const OTAG = 'otag';
+ const CTAG = 'ctag';
+ const LINE = 'line';
+ const INDEX = 'index';
+ const END = 'end';
+ const INDENT = 'indent';
+ const NODES = 'nodes';
+ const VALUE = 'value';
+ const FILTERS = 'filters';
+
+ private $state;
+ private $tagType;
+ private $buffer;
+ private $tokens;
+ private $seenTag;
+ private $line;
+
+ private $otag;
+ private $otagChar;
+ private $otagLen;
+
+ private $ctag;
+ private $ctagChar;
+ private $ctagLen;
+
+ /**
+ * Scan and tokenize template source.
+ *
+ * @throws SyntaxException when mismatched section tags are encountered
+ * @throws InvalidArgumentException when $delimiters string is invalid
+ *
+ * @param string $text Mustache template source to tokenize
+ * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: empty string)
+ *
+ * @return array Set of Mustache tokens
+ */
+ public function scan($text, $delimiters = '')
+ {
+ // Setting mbstring.func_overload makes things *really* slow.
+ // Let's do everyone a favor and scan this string as ASCII instead.
+ //
+ // The INI directive was removed in PHP 8.0 so we don't need to check there (and can drop it
+ // when we remove support for older versions of PHP).
+ //
+ // @codeCoverageIgnoreStart
+ $encoding = null;
+ if (version_compare(PHP_VERSION, '8.0.0', '<')) {
+ if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
+ $encoding = mb_internal_encoding();
+ mb_internal_encoding('ASCII');
+ }
+ }
+ // @codeCoverageIgnoreEnd
+
+ $this->reset();
+
+ if (is_string($delimiters) && ($delimiters = trim($delimiters)) !== '') {
+ $this->setDelimiters($delimiters);
+ }
+
+ $len = strlen($text);
+ for ($i = 0; $i < $len; $i++) {
+ switch ($this->state) {
+ case self::IN_TEXT:
+ $char = $text[$i];
+ // Test whether it's time to change tags.
+ if ($char === $this->otagChar && substr($text, $i, $this->otagLen) === $this->otag) {
+ $i--;
+ $this->flushBuffer();
+ $this->state = self::IN_TAG_TYPE;
+ } else {
+ $this->buffer .= $char;
+ if ($char === "\n") {
+ $this->flushBuffer();
+ $this->line++;
+ }
+ }
+ break;
+
+ case self::IN_TAG_TYPE:
+ $i += $this->otagLen - 1;
+ $char = $text[$i + 1];
+ if (isset(self::$tagTypes[$char])) {
+ $tag = $char;
+ $this->tagType = $tag;
+ } else {
+ $tag = null;
+ $this->tagType = self::T_ESCAPED;
+ }
+
+ if ($this->tagType === self::T_DELIM_CHANGE) {
+ $i = $this->changeDelimiters($text, $i);
+ $this->state = self::IN_TEXT;
+ } elseif ($this->tagType === self::T_PRAGMA) {
+ $i = $this->addPragma($text, $i);
+ $this->state = self::IN_TEXT;
+ } else {
+ if ($tag !== null) {
+ $i++;
+ }
+ $this->state = self::IN_TAG;
+ }
+ $this->seenTag = $i;
+ break;
+
+ default:
+ $char = $text[$i];
+ // Test whether it's time to change tags.
+ if ($char === $this->ctagChar && substr($text, $i, $this->ctagLen) === $this->ctag) {
+ $token = [
+ self::TYPE => $this->tagType,
+ self::NAME => trim($this->buffer),
+ self::OTAG => $this->otag,
+ self::CTAG => $this->ctag,
+ self::LINE => $this->line,
+ self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen,
+ ];
+
+ if ($this->tagType === self::T_UNESCAPED) {
+ // Clean up `{{{ tripleStache }}}` style tokens.
+ if ($this->ctag === '}}') {
+ if (($i + 2 < $len) && $text[$i + 2] === '}') {
+ $i++;
+ } else {
+ $msg = sprintf(
+ 'Mismatched tag delimiters: %s on line %d',
+ $token[self::NAME],
+ $token[self::LINE]
+ );
+
+ throw new SyntaxException($msg, $token);
+ }
+ } else {
+ $lastName = $token[self::NAME];
+ if (substr($lastName, -1) === '}') {
+ $token[self::NAME] = trim(substr($lastName, 0, -1));
+ } else {
+ $msg = sprintf(
+ 'Mismatched tag delimiters: %s on line %d',
+ $token[self::NAME],
+ $token[self::LINE]
+ );
+
+ throw new SyntaxException($msg, $token);
+ }
+ }
+ }
+
+ $this->buffer = '';
+ $i += $this->ctagLen - 1;
+ $this->state = self::IN_TEXT;
+ $this->tokens[] = $token;
+ } else {
+ $this->buffer .= $char;
+ }
+ break;
+ }
+ }
+
+ if ($this->state !== self::IN_TEXT) {
+ $this->throwUnclosedTagException();
+ }
+
+ $this->flushBuffer();
+
+ // Restore the user's encoding...
+ // @codeCoverageIgnoreStart
+ if ($encoding) {
+ mb_internal_encoding($encoding);
+ }
+ // @codeCoverageIgnoreEnd
+
+ return $this->tokens;
+ }
+
+ /**
+ * Helper function to reset tokenizer internal state.
+ */
+ private function reset()
+ {
+ $this->state = self::IN_TEXT;
+ $this->tagType = null;
+ $this->buffer = '';
+ $this->tokens = [];
+ $this->seenTag = false;
+ $this->line = 0;
+
+ $this->otag = '{{';
+ $this->otagChar = '{';
+ $this->otagLen = 2;
+
+ $this->ctag = '}}';
+ $this->ctagChar = '}';
+ $this->ctagLen = 2;
+ }
+
+ /**
+ * Flush the current buffer to a token.
+ */
+ private function flushBuffer()
+ {
+ if (strlen($this->buffer) > 0) {
+ $this->tokens[] = [
+ self::TYPE => self::T_TEXT,
+ self::LINE => $this->line,
+ self::VALUE => $this->buffer,
+ ];
+ $this->buffer = '';
+ }
+ }
+
+ /**
+ * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
+ *
+ * @throws SyntaxException when delimiter string is invalid
+ *
+ * @param string $text Mustache template source
+ * @param int $index Current tokenizer index
+ *
+ * @return int New index value
+ */
+ private function changeDelimiters($text, $index)
+ {
+ $startIndex = strpos($text, '=', $index) + 1;
+ $close = '=' . $this->ctag;
+ $closeIndex = strpos($text, $close, $index);
+
+ if ($closeIndex === false) {
+ $this->throwUnclosedTagException();
+ }
+
+ $token = [
+ self::TYPE => self::T_DELIM_CHANGE,
+ self::LINE => $this->line,
+ ];
+
+ try {
+ $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
+ } catch (InvalidArgumentException $e) {
+ throw new SyntaxException($e->getMessage(), $token);
+ }
+
+ $this->tokens[] = $token;
+
+ return $closeIndex + strlen($close) - 1;
+ }
+
+ /**
+ * Set the current Mustache `otag` and `ctag` delimiters.
+ *
+ * @throws InvalidArgumentException when delimiter string is invalid
+ *
+ * @param string $delimiters
+ */
+ private function setDelimiters($delimiters)
+ {
+ if (!preg_match('/^\s*(\S+)\s+(\S+)\s*$/', $delimiters, $matches)) {
+ throw new InvalidArgumentException(sprintf('Invalid delimiters: %s', $delimiters));
+ }
+
+ list($_, $otag, $ctag) = $matches;
+
+ $this->otag = $otag;
+ $this->otagChar = $otag[0];
+ $this->otagLen = strlen($otag);
+
+ $this->ctag = $ctag;
+ $this->ctagChar = $ctag[0];
+ $this->ctagLen = strlen($ctag);
+ }
+
+ /**
+ * Add pragma token.
+ *
+ * Pragmas are hoisted to the front of the template, so all pragma tokens
+ * will appear at the front of the token list.
+ *
+ * @param string $text
+ * @param int $index
+ *
+ * @return int New index value
+ */
+ private function addPragma($text, $index)
+ {
+ $end = strpos($text, $this->ctag, $index);
+ if ($end === false) {
+ $this->throwUnclosedTagException();
+ }
+
+ $pragma = trim(substr($text, $index + 2, $end - $index - 2));
+
+ // Pragmas are hoisted to the front of the template.
+ array_unshift($this->tokens, [
+ self::TYPE => self::T_PRAGMA,
+ self::NAME => $pragma,
+ self::LINE => 0,
+ ]);
+
+ return $end + $this->ctagLen - 1;
+ }
+
+ private function throwUnclosedTagException()
+ {
+ $name = trim($this->buffer);
+ if ($name !== '') {
+ $msg = sprintf('Unclosed tag: %s on line %d', $name, $this->line);
+ } else {
+ $msg = sprintf('Unclosed tag on line %d', $this->line);
+ }
+
+ throw new SyntaxException($msg, [
+ self::TYPE => $this->tagType,
+ self::NAME => $name,
+ self::OTAG => $this->otag,
+ self::CTAG => $this->ctag,
+ self::LINE => $this->line,
+ self::INDEX => $this->seenTag - $this->otagLen,
+ ]);
+ }
+
+ /**
+ * Get the human readable name for a tag type.
+ *
+ * @param string $tagType One of the tokenizer T_* constants
+ *
+ * @return string
+ */
+ public static function getTagName($tagType)
+ {
+ return isset(self::$tagNames[$tagType]) ? self::$tagNames[$tagType] : 'unknown';
+ }
+}
diff --git a/vendor/mustache/mustache/src/compat.php b/vendor/mustache/mustache/src/compat.php
new file mode 100644
index 0000000..99a2b24
--- /dev/null
+++ b/vendor/mustache/mustache/src/compat.php
@@ -0,0 +1,282 @@
+