Refactor: Replace sidebar with horizontal navigation bar
- Remove sidebar and toggle functionality - Add Bootstrap navbar with dropdown menus - Move navigation to top between header and content - Update menu rendering for Bootstrap dropdowns - Clean up unused files (header.mustache, sidebar.mustache, sidebar.js) - Add guide link with book icon in footer - Simplify layout structure - Remove duplicate code and fix syntax errors - Add .gitignore for node_modules and other temp files
This commit is contained in:
parent
0f1c7234b8
commit
a86809c243
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -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
|
||||||
5
composer.json
Normal file
5
composer.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"mustache/mustache": "^3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
72
composer.lock
generated
Normal file
72
composer.lock
generated
Normal file
@ -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"
|
||||||
|
}
|
||||||
BIN
composer.phar
Executable file
BIN
composer.phar
Executable file
Binary file not shown.
16
config.php
16
config.php
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
'site_title' => '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'
|
|
||||||
];
|
|
||||||
246
engine/assets/css/style.scss
Normal file
246
engine/assets/css/style.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'site_title' => 'CodePress',
|
'site_title' => 'CodePress',
|
||||||
'site_description' => 'A simple PHP CMS',
|
|
||||||
'base_url' => '/',
|
|
||||||
'content_dir' => __DIR__ . '/../../content',
|
'content_dir' => __DIR__ . '/../../content',
|
||||||
'templates_dir' => __DIR__ . '/../templates',
|
'templates_dir' => __DIR__ . '/../templates',
|
||||||
'cache_dir' => __DIR__ . '/../../cache',
|
'default_page' => 'home'
|
||||||
'default_page' => 'home',
|
|
||||||
'error_404' => '404',
|
|
||||||
'markdown_enabled' => true,
|
|
||||||
'php_enabled' => true,
|
|
||||||
'bootstrap_version' => '5.3.0',
|
|
||||||
'jquery_version' => '3.7.1'
|
|
||||||
];
|
];
|
||||||
@ -2,6 +2,52 @@
|
|||||||
|
|
||||||
require_once 'config.php';
|
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';
|
$config = include 'config.php';
|
||||||
|
|
||||||
class CodePressCMS {
|
class CodePressCMS {
|
||||||
@ -104,6 +150,16 @@ class CodePressCMS {
|
|||||||
return $this->getSearchResults();
|
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 = $_GET['page'] ?? $this->config['default_page'];
|
||||||
$page = preg_replace('/\.[^.]+$/', '', $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() {
|
private function getError404() {
|
||||||
return [
|
return [
|
||||||
'title' => 'Page Not Found',
|
'title' => 'Page Not Found',
|
||||||
@ -364,27 +460,59 @@ private function autoLinkPageTitles($content) {
|
|||||||
$menu = $this->getMenu();
|
$menu = $this->getMenu();
|
||||||
$breadcrumb = $this->getBreadcrumb();
|
$breadcrumb = $this->getBreadcrumb();
|
||||||
|
|
||||||
$template = file_get_contents($this->config['templates_dir'] . '/layout.html');
|
// Prepare template data
|
||||||
|
$templateData = [
|
||||||
$template = str_replace('{{site_title}}', $this->config['site_title'], $template);
|
'site_title' => $this->config['site_title'],
|
||||||
$template = str_replace('{{page_title}}', $page['title'], $template);
|
'page_title' => htmlspecialchars($page['title']),
|
||||||
$template = str_replace('{{content}}', $page['content'], $template);
|
'content' => $page['content'],
|
||||||
$template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template);
|
'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
|
||||||
$template = str_replace('{{breadcrumb}}', $breadcrumb, $template);
|
'menu' => $this->renderMenu($menu),
|
||||||
|
'breadcrumb' => $breadcrumb,
|
||||||
|
'default_page' => $this->config['default_page']
|
||||||
|
];
|
||||||
|
|
||||||
// File info for footer
|
// File info for footer
|
||||||
$fileInfo = '';
|
|
||||||
if (isset($page['file_info'])) {
|
if (isset($page['file_info'])) {
|
||||||
$fileInfo = '<i class="bi bi-file-text"></i> Created: ' . htmlspecialchars($page['file_info']['created']) .
|
$templateData['file_info'] = 'Created: ' . htmlspecialchars($page['file_info']['created']) .
|
||||||
' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
|
' | 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('/<a href="[^"]*" class="site-title-link">\s*<h1[^>]*>(.*?)<\/h1>\s*<\/a>/', '<h1 class="h3 mb-0">$1</h1>', $headerContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$footerContent = file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache');
|
||||||
|
if (!$hasContent) {
|
||||||
|
// Remove guide link from footer when no content
|
||||||
|
$footerContent = preg_replace('/<span class="file-details">\s*\|\s*<a href="\?guide"[^>]*>Handleiding<\/a><\/span>/', '', $footerContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$partials = [
|
||||||
|
'navigation' => file_get_contents($this->config['templates_dir'] . '/assets/navigation.mustache'),
|
||||||
|
'footer' => file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache')
|
||||||
|
];
|
||||||
|
|
||||||
|
// Replace partials in template
|
||||||
|
$template = file_get_contents($this->config['templates_dir'] . '/layout.mustache');
|
||||||
|
$template = str_replace('{{>navigation}}', $partials['navigation'], $template);
|
||||||
|
$template = str_replace('{{>footer}}', $partials['footer'], $template);
|
||||||
|
|
||||||
|
// Render template with data
|
||||||
|
echo SimpleTemplate::render($template, $templateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getBreadcrumb() {
|
private function getBreadcrumb() {
|
||||||
@ -423,33 +551,27 @@ private function autoLinkPageTitles($content) {
|
|||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if ($item['type'] === 'folder') {
|
if ($item['type'] === 'folder') {
|
||||||
$hasChildren = !empty($item['children']);
|
$hasChildren = !empty($item['children']);
|
||||||
$html .= '<li class="nav-item">';
|
|
||||||
|
|
||||||
if ($hasChildren) {
|
if ($hasChildren) {
|
||||||
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
||||||
|
$isExpanded = $this->folderContainsActivePage($item['children']);
|
||||||
// Check if this folder contains the active page
|
$html .= '<li class="nav-item dropdown">';
|
||||||
$containsActive = $this->folderContainsActivePage($item['children']);
|
$html .= '<a class="nav-link dropdown-toggle" href="#" id="' . $folderId . '" role="button" data-bs-toggle="dropdown" aria-expanded="' . ($isExpanded ? 'true' : 'false') . '">';
|
||||||
$ariaExpanded = $containsActive ? 'true' : 'false';
|
$html .= htmlspecialchars($item['title']);
|
||||||
$collapseClass = $containsActive ? 'collapse show' : 'collapse';
|
$html .= '</a>';
|
||||||
|
$html .= '<ul class="dropdown-menu" aria-labelledby="' . $folderId . '">';
|
||||||
$html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="' . $ariaExpanded . '">';
|
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
|
||||||
$html .= '</span>';
|
|
||||||
$html .= '<ul class="nav flex-column ms-2 ' . $collapseClass . '" id="' . $folderId . '">';
|
|
||||||
$html .= $this->renderMenu($item['children'], $level + 1);
|
$html .= $this->renderMenu($item['children'], $level + 1);
|
||||||
$html .= '</ul>';
|
$html .= '</ul>';
|
||||||
|
$html .= '</li>';
|
||||||
} else {
|
} else {
|
||||||
$html .= '<span class="nav-link folder-disabled" disabled>';
|
$html .= '<li class="nav-item">';
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
$html .= '<span class="nav-link text-muted">' . htmlspecialchars($item['title']) . '</span>';
|
||||||
$html .= '</span>';
|
$html .= '</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= '</li>';
|
|
||||||
} else {
|
} else {
|
||||||
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
||||||
$html .= '<li class="nav-item">';
|
$html .= '<li class="nav-item">';
|
||||||
$html .= '<a class="nav-link page-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
$html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||||
$html .= '</li>';
|
$html .= '</li>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,7 +592,4 @@ private function autoLinkPageTitles($content) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$cms = new CodePressCMS($config);
|
|
||||||
$cms->render();
|
|
||||||
@ -2,6 +2,52 @@
|
|||||||
|
|
||||||
require_once 'config.php';
|
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';
|
$config = include 'config.php';
|
||||||
|
|
||||||
class CodePressCMS {
|
class CodePressCMS {
|
||||||
@ -104,6 +150,16 @@ class CodePressCMS {
|
|||||||
return $this->getSearchResults();
|
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 = $_GET['page'] ?? $this->config['default_page'];
|
||||||
$page = preg_replace('/\.[^.]+$/', '', $page);
|
$page = preg_replace('/\.[^.]+$/', '', $page);
|
||||||
|
|
||||||
@ -261,15 +317,6 @@ private function autoLinkPageTitles($content) {
|
|||||||
$content = preg_replace_callback($pattern, $replacement, $content);
|
$content = preg_replace_callback($pattern, $replacement, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<a href="?page=' . $pagePath . '" class="auto-link" title="Ga naar ' . htmlspecialchars($pageTitle) . '">' . $matches[0] . '</a>';
|
|
||||||
};
|
|
||||||
|
|
||||||
$content = preg_replace_callback($pattern, $replacement, $content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,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() {
|
private function getError404() {
|
||||||
return [
|
return [
|
||||||
'title' => 'Page Not Found',
|
'title' => 'Page Not Found',
|
||||||
@ -373,27 +460,59 @@ private function autoLinkPageTitles($content) {
|
|||||||
$menu = $this->getMenu();
|
$menu = $this->getMenu();
|
||||||
$breadcrumb = $this->getBreadcrumb();
|
$breadcrumb = $this->getBreadcrumb();
|
||||||
|
|
||||||
$template = file_get_contents($this->config['templates_dir'] . '/layout.html');
|
// Prepare template data
|
||||||
|
$templateData = [
|
||||||
$template = str_replace('{{site_title}}', $this->config['site_title'], $template);
|
'site_title' => $this->config['site_title'],
|
||||||
$template = str_replace('{{page_title}}', $page['title'], $template);
|
'page_title' => htmlspecialchars($page['title']),
|
||||||
$template = str_replace('{{content}}', $page['content'], $template);
|
'content' => $page['content'],
|
||||||
$template = str_replace('{{search_query}}', isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '', $template);
|
'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
|
||||||
$template = str_replace('{{breadcrumb}}', $breadcrumb, $template);
|
'menu' => $this->renderMenu($menu),
|
||||||
|
'breadcrumb' => $breadcrumb,
|
||||||
|
'default_page' => $this->config['default_page']
|
||||||
|
];
|
||||||
|
|
||||||
// File info for footer
|
// File info for footer
|
||||||
$fileInfo = '';
|
|
||||||
if (isset($page['file_info'])) {
|
if (isset($page['file_info'])) {
|
||||||
$fileInfo = '<i class="bi bi-file-text"></i> Created: ' . htmlspecialchars($page['file_info']['created']) .
|
$templateData['file_info'] = 'Created: ' . htmlspecialchars($page['file_info']['created']) .
|
||||||
' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
|
' | 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('/<a href="[^"]*" class="site-title-link">\s*<h1[^>]*>(.*?)<\/h1>\s*<\/a>/', '<h1 class="h3 mb-0">$1</h1>', $headerContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$footerContent = file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache');
|
||||||
|
if (!$hasContent) {
|
||||||
|
// Remove guide link from footer when no content
|
||||||
|
$footerContent = preg_replace('/<span class="file-details">\s*\|\s*<a href="\?guide"[^>]*>Handleiding<\/a><\/span>/', '', $footerContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$partials = [
|
||||||
|
'navigation' => file_get_contents($this->config['templates_dir'] . '/assets/navigation.mustache'),
|
||||||
|
'footer' => file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache')
|
||||||
|
];
|
||||||
|
|
||||||
|
// Replace partials in template
|
||||||
|
$template = file_get_contents($this->config['templates_dir'] . '/layout.mustache');
|
||||||
|
$template = str_replace('{{>navigation}}', $partials['navigation'], $template);
|
||||||
|
$template = str_replace('{{>footer}}', $partials['footer'], $template);
|
||||||
|
|
||||||
|
// Render template with data
|
||||||
|
echo SimpleTemplate::render($template, $templateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getBreadcrumb() {
|
private function getBreadcrumb() {
|
||||||
@ -432,21 +551,41 @@ private function autoLinkPageTitles($content) {
|
|||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if ($item['type'] === 'folder') {
|
if ($item['type'] === 'folder') {
|
||||||
$hasChildren = !empty($item['children']);
|
$hasChildren = !empty($item['children']);
|
||||||
$html .= '<li class="nav-item">';
|
|
||||||
|
|
||||||
if ($hasChildren) {
|
if ($hasChildren) {
|
||||||
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
||||||
$html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="false">';
|
$isExpanded = $this->folderContainsActivePage($item['children']);
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
$html .= '<li class="nav-item dropdown">';
|
||||||
$html .= '</span>';
|
$html .= '<a class="nav-link dropdown-toggle" href="#" id="' . $folderId . '" role="button" data-bs-toggle="dropdown" aria-expanded="' . ($isExpanded ? 'true' : 'false') . '">';
|
||||||
$html .= '<ul class="nav flex-column ms-2 collapse" id="' . $folderId . '">';
|
$html .= htmlspecialchars($item['title']);
|
||||||
|
$html .= '</a>';
|
||||||
|
$html .= '<ul class="dropdown-menu" aria-labelledby="' . $folderId . '">';
|
||||||
$html .= $this->renderMenu($item['children'], $level + 1);
|
$html .= $this->renderMenu($item['children'], $level + 1);
|
||||||
$html .= '</ul>';
|
$html .= '</ul>';
|
||||||
|
$html .= '</li>';
|
||||||
} else {
|
} else {
|
||||||
$html .= '<span class="nav-link folder-disabled" disabled>';
|
$html .= '<li class="nav-item">';
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
$html .= '<span class="nav-link text-muted">' . htmlspecialchars($item['title']) . '</span>';
|
||||||
$html .= '</span>';
|
$html .= '</li>';
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
||||||
|
$html .= '<li class="nav-item">';
|
||||||
|
$html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||||
|
$html .= '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
||||||
|
$html .= '<li class="nav-item">';
|
||||||
|
$html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||||
|
$html .= '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
$html .= '</li>';
|
$html .= '</li>';
|
||||||
} else {
|
} else {
|
||||||
@ -458,7 +597,19 @@ private function autoLinkPageTitles($content) {
|
|||||||
}
|
}
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private function folderContainsActivePage($children) {
|
||||||
$cms = new CodePressCMS($config);
|
foreach ($children as $child) {
|
||||||
$cms->render();
|
if ($child['type'] === 'folder') {
|
||||||
|
if (!empty($child['children']) && $this->folderContainsActivePage($child['children'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isset($_GET['page']) && $_GET['page'] === $child['path']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
engine/templates/assets/footer.mustache
Normal file
26
engine/templates/assets/footer.mustache
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<footer class="bg-light border-top py-3">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="file-info">
|
||||||
|
<i class="bi bi-file-text"></i>
|
||||||
|
<span class="page-title" title="{{page_title}}">{{page_title}}</span>
|
||||||
|
{{#file_info}}
|
||||||
|
<span class="file-details"> | {{{file_info}}}</span>
|
||||||
|
{{/file_info}}
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<small class="text-muted">
|
||||||
|
<a href="?guide" class="guide-link" title="Handleiding">
|
||||||
|
<i class="bi bi-book"></i>
|
||||||
|
</a>
|
||||||
|
<span class="ms-2">|</span>
|
||||||
|
Powered by <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
23
engine/templates/assets/navigation.mustache
Normal file
23
engine/templates/assets/navigation.mustache
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="?page={{default_page}}">
|
||||||
|
<img src="/assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
||||||
|
{{site_title}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
{{{menu}}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<form class="d-flex" method="GET" action="">
|
||||||
|
<input class="form-control me-2" type="search" name="search" placeholder="Search..." value="{{search_query}}">
|
||||||
|
<button class="btn btn-outline-light" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@ -1,440 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{page_title}} - {{site_title}}</title>
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/engine/assets/favicon.svg">
|
|
||||||
<link href="/engine/assets/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="/engine/assets/css/bootstrap-icons.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
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); /* Minus header height */
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
position: fixed;
|
|
||||||
top: 70px;
|
|
||||||
left: 0;
|
|
||||||
height: calc(100vh - 140px); /* 70px header + 70px footer */
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
.sidebar-toggle:hover {
|
|
||||||
color: #0d6efd;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
.sidebar-toggle-inner {
|
|
||||||
/* Toggle inside sidebar */
|
|
||||||
}
|
|
||||||
.sidebar-toggle-outer {
|
|
||||||
position: fixed;
|
|
||||||
top: 90px;
|
|
||||||
left: 20px;
|
|
||||||
z-index: 1001;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.sidebar.collapsed .sidebar-toggle-inner {
|
|
||||||
right: auto;
|
|
||||||
left: 15px;
|
|
||||||
}
|
|
||||||
.sidebar.collapsed ~ .main-content .sidebar-toggle-outer {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
.sidebar:not(.collapsed) ~ .main-content .sidebar-toggle-outer {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.sidebar-toggle:hover {
|
|
||||||
background-color: #0a58ca;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin-left: 250px;
|
|
||||||
}
|
|
||||||
.sidebar.collapsed ~ .main-content {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
transition: margin-left 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
.folder-toggle:hover {
|
|
||||||
background-color: #e9ecef !important;
|
|
||||||
}
|
|
||||||
.folder-toggle[aria-expanded="true"] {
|
|
||||||
background-color: #dee2e6 !important;
|
|
||||||
color: #212529 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
/* Progressive background colors for nested folders */
|
|
||||||
.nav .nav .folder-toggle {
|
|
||||||
background-color: #f1f3f4 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #eaedee !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #e3e7e8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #dce1e2 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #d5dbdd !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #ced5d8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #c7cfd3 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #c0c9ce !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #b9c3c9 !important;
|
|
||||||
}
|
|
||||||
.folder-toggle .arrow {
|
|
||||||
margin-right: 8px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
.folder-toggle[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;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
/* Progressive background colors for nested pages */
|
|
||||||
.nav .nav .page-link {
|
|
||||||
background-color: #fafbfc !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .page-link {
|
|
||||||
background-color: #f5f7f8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #f0f3f4 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #ebefef !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #e6eaea !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #e1e5e5 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #dce0e0 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #d7dbdb !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #d2d6d6 !important;
|
|
||||||
}
|
|
||||||
.page-link:hover {
|
|
||||||
color: #212529 !important;
|
|
||||||
background-color: #f8f9fa !important;
|
|
||||||
}
|
|
||||||
.nav-link.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;
|
|
||||||
}
|
|
||||||
.site-info a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.auto-link {
|
|
||||||
color: #0d6efd;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 2px dashed #0d6efd;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.auto-link: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;
|
|
||||||
}
|
|
||||||
.card-title a: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;
|
|
||||||
}
|
|
||||||
.folder-disabled .arrow {
|
|
||||||
margin-right: 8px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.sidebar {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="bg-primary text-white py-3">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<img src="/engine/assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
|
||||||
<h1 class="h3 mb-0">{{site_title}}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<form class="d-flex" method="GET" action="">
|
|
||||||
<input class="form-control me-2" type="search" name="search" placeholder="Search..." value="{{search_query}}">
|
|
||||||
<button class="btn btn-outline-light" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="main-wrapper">
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<nav class="sidebar" id="sidebar">
|
|
||||||
<div class="sidebar-toggle sidebar-toggle-inner" id="sidebarToggleInner">
|
|
||||||
<i class="bi bi-list"></i>
|
|
||||||
</div>
|
|
||||||
<div class="pt-3">
|
|
||||||
<ul class="nav flex-column">
|
|
||||||
{{menu}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="main-content">
|
|
||||||
<div class="sidebar-toggle sidebar-toggle-outer" id="sidebarToggleOuter" style="display: none;">
|
|
||||||
<i class="bi bi-list"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{breadcrumb}}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
||||||
<h2>{{page_title}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{{content}}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="bg-light border-top py-3" style="position: fixed; bottom: 0; left: 0; right: 0; z-index: 998;">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="file-info">
|
|
||||||
{{file_info}}
|
|
||||||
</div>
|
|
||||||
<div class="site-info">
|
|
||||||
<small class="text-muted">Powered by <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a></small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script src="/engine/assets/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Sidebar toggle functionality
|
|
||||||
const sidebarToggleInner = document.getElementById('sidebarToggleInner');
|
|
||||||
const sidebarToggleOuter = document.getElementById('sidebarToggleOuter');
|
|
||||||
const sidebar = document.getElementById('sidebar');
|
|
||||||
|
|
||||||
// Initialize sidebar state (open by default)
|
|
||||||
sidebar.classList.remove('collapsed');
|
|
||||||
const innerIcon = sidebarToggleInner.querySelector('i');
|
|
||||||
const outerIcon = sidebarToggleOuter.querySelector('i');
|
|
||||||
innerIcon.classList.remove('bi-list');
|
|
||||||
innerIcon.classList.add('bi-x');
|
|
||||||
outerIcon.classList.remove('bi-list');
|
|
||||||
outerIcon.classList.add('bi-x');
|
|
||||||
|
|
||||||
function toggleSidebar() {
|
|
||||||
sidebar.classList.toggle('collapsed');
|
|
||||||
|
|
||||||
// Change icons
|
|
||||||
if (sidebar.classList.contains('collapsed')) {
|
|
||||||
innerIcon.classList.remove('bi-x');
|
|
||||||
innerIcon.classList.add('bi-list');
|
|
||||||
outerIcon.classList.remove('bi-x');
|
|
||||||
outerIcon.classList.add('bi-list');
|
|
||||||
} else {
|
|
||||||
innerIcon.classList.remove('bi-list');
|
|
||||||
innerIcon.classList.add('bi-x');
|
|
||||||
outerIcon.classList.remove('bi-list');
|
|
||||||
outerIcon.classList.add('bi-x');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sidebarToggleInner.addEventListener('click', toggleSidebar);
|
|
||||||
sidebarToggleOuter.addEventListener('click', toggleSidebar);
|
|
||||||
|
|
||||||
// Folders are now automatically expanded by PHP if they contain the active page
|
|
||||||
|
|
||||||
// Close other folders when opening a new one
|
|
||||||
const folderToggles = document.querySelectorAll('.folder-toggle');
|
|
||||||
folderToggles.forEach(toggle => {
|
|
||||||
toggle.addEventListener('click', function(e) {
|
|
||||||
const targetId = this.getAttribute('data-bs-target');
|
|
||||||
const targetCollapse = document.querySelector(targetId);
|
|
||||||
const isExpanded = this.getAttribute('aria-expanded') === 'true';
|
|
||||||
|
|
||||||
if (!isExpanded && targetCollapse) {
|
|
||||||
// Close all other folders first
|
|
||||||
folderToggles.forEach(otherToggle => {
|
|
||||||
if (otherToggle !== this) {
|
|
||||||
const otherTargetId = otherToggle.getAttribute('data-bs-target');
|
|
||||||
if (otherTargetId) {
|
|
||||||
const otherCollapse = document.querySelector(otherTargetId);
|
|
||||||
if (otherCollapse) {
|
|
||||||
otherCollapse.classList.remove('show');
|
|
||||||
otherToggle.setAttribute('aria-expanded', 'false');
|
|
||||||
// Reset arrow
|
|
||||||
const otherArrow = otherToggle.querySelector('.arrow');
|
|
||||||
if (otherArrow) {
|
|
||||||
otherArrow.style.transform = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open this folder
|
|
||||||
targetCollapse.classList.add('show');
|
|
||||||
this.setAttribute('aria-expanded', 'true');
|
|
||||||
// Rotate arrow
|
|
||||||
const arrow = this.querySelector('.arrow');
|
|
||||||
if (arrow) {
|
|
||||||
arrow.style.transform = 'rotate(90deg)';
|
|
||||||
}
|
|
||||||
} else if (isExpanded && targetCollapse) {
|
|
||||||
// Close this folder
|
|
||||||
targetCollapse.classList.remove('show');
|
|
||||||
this.setAttribute('aria-expanded', 'false');
|
|
||||||
// Reset arrow
|
|
||||||
const arrow = this.querySelector('.arrow');
|
|
||||||
if (arrow) {
|
|
||||||
arrow.style.transform = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
39
engine/templates/layout.mustache
Normal file
39
engine/templates/layout.mustache
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{page_title}} - {{site_title}}</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
|
||||||
|
<link href="/assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/assets/css/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
<link href="/assets/css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{>header}}
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
{{>navigation}}
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="content-inner">
|
||||||
|
<div>
|
||||||
|
{{{breadcrumb}}}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
|
<h2>{{page_title}}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
{{{content}}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{>footer}}
|
||||||
|
|
||||||
|
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/assets/js/sidebar.js"></script>
|
||||||
|
<script src="/assets/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
engine/templates/markdown_content.mustache
Normal file
3
engine/templates/markdown_content.mustache
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{#content}}
|
||||||
|
<p>{{{.}}}</p>
|
||||||
|
{{/content}}
|
||||||
1
engine/templates/php_content.mustache
Normal file
1
engine/templates/php_content.mustache
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{{content}}}
|
||||||
171
guide/en.md
Normal file
171
guide/en.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# CodePress CMS Guide
|
||||||
|
|
||||||
|
## Welcome to CodePress CMS
|
||||||
|
|
||||||
|
CodePress is a lightweight, file-based Content Management System built with PHP and Bootstrap.
|
||||||
|
|
||||||
|
### Table of Contents
|
||||||
|
|
||||||
|
1. [Getting Started](#getting-started)
|
||||||
|
2. [Content Management](#content-management)
|
||||||
|
3. [Templates](#templates)
|
||||||
|
4. [Configuration](#configuration)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- PHP 8.4+
|
||||||
|
- Web server (Apache/Nginx)
|
||||||
|
- Modern web browser
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
1. Clone or download the CodePress files
|
||||||
|
2. Upload to your web server
|
||||||
|
3. Make sure the `content/` directory is writable
|
||||||
|
4. Navigate to your website in the browser
|
||||||
|
|
||||||
|
### Basic Configuration
|
||||||
|
The most important settings are in `engine/core/config.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$config = [
|
||||||
|
'site_title' => 'My Website',
|
||||||
|
'default_page' => 'home',
|
||||||
|
'content_dir' => __DIR__ . '/../../content',
|
||||||
|
'templates_dir' => __DIR__ . '/../templates'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Management
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```
|
||||||
|
content/
|
||||||
|
├── home.md # Home page
|
||||||
|
├── blog/
|
||||||
|
│ ├── index.md # Blog overview
|
||||||
|
│ ├── article-1.md # Blog article
|
||||||
|
│ └── category/
|
||||||
|
│ └── article.md # Article in category
|
||||||
|
└── about-us/
|
||||||
|
└── info.md # About us page
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Types
|
||||||
|
CodePress supports three content types:
|
||||||
|
|
||||||
|
#### Markdown (`.md`)
|
||||||
|
```markdown
|
||||||
|
# Page Title
|
||||||
|
|
||||||
|
This is the page content in **Markdown** format.
|
||||||
|
|
||||||
|
## Subsection
|
||||||
|
|
||||||
|
- List item 1
|
||||||
|
- List item 2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PHP (`.php`)
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$title = "Dynamic Page";
|
||||||
|
?>
|
||||||
|
<h1><?php echo $title; ?></h1>
|
||||||
|
<p>This is dynamic content with PHP.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HTML (`.html`)
|
||||||
|
```html
|
||||||
|
<h1>HTML Page</h1>
|
||||||
|
<p>This is static HTML content.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Linking
|
||||||
|
CodePress automatically creates links to other pages when you mention page names in your content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
### Template Structure
|
||||||
|
CodePress uses Mustache-compatible templates:
|
||||||
|
|
||||||
|
- `layout.mustache` - Main template
|
||||||
|
- `assets/header.mustache` - Header component
|
||||||
|
- `assets/sidebar.mustache` - Sidebar navigation
|
||||||
|
- `assets/footer.mustache` - Footer component
|
||||||
|
|
||||||
|
### Template Variables
|
||||||
|
Available variables in templates:
|
||||||
|
- `{{site_title}}` - Website title
|
||||||
|
- `{{page_title}}` - Current page title
|
||||||
|
- `{{content}}` - Page content
|
||||||
|
- `{{menu}}` - Navigation menu
|
||||||
|
- `{{breadcrumb}}` - Breadcrumb navigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Basic Settings
|
||||||
|
Edit `engine/core/config.php` for your website:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$config = [
|
||||||
|
'site_title' => 'Your Website Name',
|
||||||
|
'default_page' => 'home', // Default start page
|
||||||
|
'content_dir' => __DIR__ . '/../../content',
|
||||||
|
'templates_dir' => __DIR__ . '/../templates'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEO Friendly URLs
|
||||||
|
CodePress automatically generates clean URLs:
|
||||||
|
- `home.md` → `/home`
|
||||||
|
- `blog/article.md` → `/blog/article`
|
||||||
|
|
||||||
|
### Search Functionality
|
||||||
|
The built-in search function searches through:
|
||||||
|
- File names
|
||||||
|
- Content of Markdown/PHP/HTML files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips and Tricks
|
||||||
|
|
||||||
|
### Page Organization
|
||||||
|
- Use subdirectories for categories
|
||||||
|
- Give each directory an `index.md` for an overview page
|
||||||
|
- Keep file names short and descriptive
|
||||||
|
|
||||||
|
### Content Optimization
|
||||||
|
- Use clear headings (H1, H2, H3)
|
||||||
|
- Add descriptive meta information
|
||||||
|
- Use internal links for better navigation
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Keep your CodePress installation updated
|
||||||
|
- Restrict write permissions on the `content/` directory
|
||||||
|
- Use HTTPS when possible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
- **Empty pages**: Check file permissions
|
||||||
|
- **Template errors**: Verify template syntax
|
||||||
|
- **404 errors**: Check file names and paths
|
||||||
|
|
||||||
|
### More Information
|
||||||
|
- Documentation: [CodePress GitHub](https://git.noorlander.info/E.Noorlander/CodePress.git)
|
||||||
|
- Issues and feature requests: GitHub Issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This guide is part of CodePress CMS and is automatically displayed when no content is available.*
|
||||||
171
guide/nl.md
Normal file
171
guide/nl.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# CodePress CMS Handleiding
|
||||||
|
|
||||||
|
## Welkom bij CodePress CMS
|
||||||
|
|
||||||
|
CodePress is een lichtgewicht, op bestanden gebaseerd Content Management Systeem gebouwd met PHP en Bootstrap.
|
||||||
|
|
||||||
|
### Inhoudsopgave
|
||||||
|
|
||||||
|
1. [Getting Started](#getting-started)
|
||||||
|
2. [Content Management](#content-management)
|
||||||
|
3. [Templates](#templates)
|
||||||
|
4. [Configuration](#configuration)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Vereisten
|
||||||
|
- PHP 8.4+
|
||||||
|
- Webserver (Apache/Nginx)
|
||||||
|
- Modern web browser
|
||||||
|
|
||||||
|
### Installatie
|
||||||
|
1. Clone of download de CodePress bestanden
|
||||||
|
2. Upload naar je webserver
|
||||||
|
3. Zorg dat de `content/` map schrijfbaar is
|
||||||
|
4. Navigeer naar je website in de browser
|
||||||
|
|
||||||
|
### Basis Configuratie
|
||||||
|
De belangrijkste instellingen vind je in `engine/core/config.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$config = [
|
||||||
|
'site_title' => 'Mijn Website',
|
||||||
|
'default_page' => 'home',
|
||||||
|
'content_dir' => __DIR__ . '/../../content',
|
||||||
|
'templates_dir' => __DIR__ . '/../templates'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Management
|
||||||
|
|
||||||
|
### Bestandsstructuur
|
||||||
|
```
|
||||||
|
content/
|
||||||
|
├── home.md # Home pagina
|
||||||
|
├── blog/
|
||||||
|
│ ├── index.md # Blog overzicht
|
||||||
|
│ ├── artikel-1.md # Blog artikel
|
||||||
|
│ └── categorie/
|
||||||
|
│ └── artikel.md # Artikel in categorie
|
||||||
|
└── over-ons/
|
||||||
|
└── info.md # Over ons pagina
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Types
|
||||||
|
CodePress ondersteunt drie content types:
|
||||||
|
|
||||||
|
#### Markdown (`.md`)
|
||||||
|
```markdown
|
||||||
|
# Pagina Titel
|
||||||
|
|
||||||
|
Dit is de inhoud van de pagina in **Markdown** formaat.
|
||||||
|
|
||||||
|
## Subsectie
|
||||||
|
|
||||||
|
- Lijst item 1
|
||||||
|
- Lijst item 2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PHP (`.php`)
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$title = "Dynamische Pagina";
|
||||||
|
?>
|
||||||
|
<h1><?php echo $title; ?></h1>
|
||||||
|
<p>Dit is dynamische content met PHP.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### HTML (`.html`)
|
||||||
|
```html
|
||||||
|
<h1>HTML Pagina</h1>
|
||||||
|
<p>Dit is statische HTML content.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatische Linking
|
||||||
|
CodePress maakt automatisch links naar andere pagina's wanneer je paginanamen in je content noemt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
### Template Structuur
|
||||||
|
CodePress gebruikt Mustache-compatible templates:
|
||||||
|
|
||||||
|
- `layout.mustache` - Hoofdtemplate
|
||||||
|
- `assets/header.mustache` - Header component
|
||||||
|
- `assets/sidebar.mustache` - Sidebar navigatie
|
||||||
|
- `assets/footer.mustache` - Footer component
|
||||||
|
|
||||||
|
### Template Variabelen
|
||||||
|
Beschikbare variabelen in templates:
|
||||||
|
- `{{site_title}}` - Website titel
|
||||||
|
- `{{page_title}}` - Huidige pagina titel
|
||||||
|
- `{{content}}` - Pagina inhoud
|
||||||
|
- `{{menu}}` - Navigatie menu
|
||||||
|
- `{{breadcrumb}}` - Broodkruimel navigatie
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Basis Instellingen
|
||||||
|
Pas `engine/core/config.php` aan voor jouw website:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$config = [
|
||||||
|
'site_title' => 'Jouw Website Naam',
|
||||||
|
'default_page' => 'home', // Standaard startpagina
|
||||||
|
'content_dir' => __DIR__ . '/../../content',
|
||||||
|
'templates_dir' => __DIR__ . '/../templates'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEO Vriendelijke URLs
|
||||||
|
CodePress genereert automatisch schone URLs:
|
||||||
|
- `home.md` → `/home`
|
||||||
|
- `blog/artikel.md` → `/blog/artikel`
|
||||||
|
|
||||||
|
### Zoekfunctionaliteit
|
||||||
|
De ingebouwde zoekfunctie doorzoekt:
|
||||||
|
- Bestandsnamen
|
||||||
|
- Content van Markdown/PHP/HTML bestanden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips en Tricks
|
||||||
|
|
||||||
|
### Pagina Organisatie
|
||||||
|
- Gebruik submappen voor categoriën
|
||||||
|
- Geef elke map een `index.md` voor een overzichtspagina
|
||||||
|
- Houd bestandsnamen kort en beschrijvend
|
||||||
|
|
||||||
|
### Content Optimalisatie
|
||||||
|
- Gebruik duidelijke koppen (H1, H2, H3)
|
||||||
|
- Voeg beschrijvende meta-informatie toe
|
||||||
|
- Gebruik interne links voor betere navigatie
|
||||||
|
|
||||||
|
### Veiligheid
|
||||||
|
- Houd je CodePress installatie bijgewerkt
|
||||||
|
- Beperk schrijfrechten op de `content/` map
|
||||||
|
- Gebruik HTTPS indien mogelijk
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ondersteuning
|
||||||
|
|
||||||
|
### Problemen Oplossen
|
||||||
|
- **Lege pagina's**: Controleer bestandsrechten
|
||||||
|
- **Template fouten**: Verifieer template syntax
|
||||||
|
- **404 fouten**: Controleer bestandsnamen en paden
|
||||||
|
|
||||||
|
### Meer Informatie
|
||||||
|
- Documentatie: [CodePress GitHub](https://git.noorlander.info/E.Noorlander/CodePress.git)
|
||||||
|
- Issues en feature requests: GitHub Issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Deze handleiding is onderdeel van CodePress CMS en wordt automatisch getoond wanneer er geen content beschikbaar is.*
|
||||||
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "codepress",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A lightweight, file-based Content Management System built with PHP and Bootstrap.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build:css": "npx sass engine/assets/css/style.scss public/assets/css/style.css --no-source-map",
|
||||||
|
"watch:css": "npx sass engine/assets/css/style.scss public/assets/css/style.css --no-source-map --watch",
|
||||||
|
"build": "npm run build:css",
|
||||||
|
"clean": "rm -rf node_modules package-lock.json",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.noorlander.info/E.Noorlander/CodePress.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"sass": "^1.94.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
316
public/assets/css/style.css
Normal file
316
public/assets/css/style.css
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 250px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
overflow-y: auto; /* Only sidebar scrolls when needed */
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.sidebar-toggle: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;
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
}
|
||||||
|
.sidebar-toggle-outer: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;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed .sidebar-toggle-outer {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(.sidebar-collapsed) .sidebar-toggle-outer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override inline styles */
|
||||||
|
.sidebar-toggle-outer[style*="display: none"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed .sidebar-toggle-outer[style*="display: none"] {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden; /* Main content container doesn't scroll */
|
||||||
|
transition: margin-left 0.3s ease;
|
||||||
|
margin-left: 250px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-inner {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto; /* Only content-inner scrolls when needed */
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.folder-toggle:hover {
|
||||||
|
background-color: #e9ecef !important;
|
||||||
|
}
|
||||||
|
.folder-toggle[aria-expanded=true] {
|
||||||
|
background-color: #dee2e6 !important;
|
||||||
|
color: #212529 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.folder-toggle .arrow {
|
||||||
|
margin-right: 8px;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
.folder-toggle[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;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
.page-link:hover {
|
||||||
|
color: #212529 !important;
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
}
|
||||||
|
.page-link.active {
|
||||||
|
background-color: rgba(13, 110, 253, 0.1) !important;
|
||||||
|
color: #0d6efd !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; /* Important for text truncation in flexbox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6c757d;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; /* Important for text truncation in flexbox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
color: #6c757d;
|
||||||
|
max-width: 250px !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title:hover {
|
||||||
|
color: #495057;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-details {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-info a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-link {
|
||||||
|
color: white !important;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title-link {
|
||||||
|
color: white !important;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.8) !important;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: auto; /* Push footer to bottom */
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.site-info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-link {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 2px dashed #0d6efd;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.auto-link: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;
|
||||||
|
}
|
||||||
|
.card-title a: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;
|
||||||
|
}
|
||||||
|
.folder-disabled .arrow {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
width: 250px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
overflow-y: auto; /* Only sidebar scrolls when needed */
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/assets/css/style.css.map
Normal file
1
public/assets/css/style.css.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAAA;EACI;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;;AAIR;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAOJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKJ;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;;;AAOJ;EACI;;;AAKZ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;;;AAQI;EACI;;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AAMhB;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EACI;EACA;EACA;;;AAQI;EACI;;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;AADJ;EACI;;;AAMhB;EACI;EACA;;;AAIA;EACI;EACA;;AAEA;EACI;;;AAKZ;EACI;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;;;AAIR;EACI;;;AAIA;EACI;EACA;;AAEA;EACI;;;AAKZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;;;AAIR;EACI;IACI","file":"style.css"}
|
||||||
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
7
public/assets/js/app.js
Normal file
7
public/assets/js/app.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Main application JavaScript
|
||||||
|
// This file contains general application functionality
|
||||||
|
|
||||||
|
// Initialize application when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('CodePress CMS initialized');
|
||||||
|
});
|
||||||
@ -1 +0,0 @@
|
|||||||
../engine
|
|
||||||
436
public/index.php
436
public/index.php
@ -1,442 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once __DIR__ . '/../engine/core/config.php';
|
require_once __DIR__ . '/../engine/core/config.php';
|
||||||
|
require_once __DIR__ . '/../engine/core/index.php';
|
||||||
|
|
||||||
$config = include __DIR__ . '/../engine/core/config.php';
|
$config = include __DIR__ . '/../engine/core/config.php';
|
||||||
|
|
||||||
class CodePressCMS {
|
|
||||||
private $config;
|
|
||||||
private $menu = [];
|
|
||||||
private $searchResults = [];
|
|
||||||
|
|
||||||
public function __construct($config) {
|
|
||||||
$this->config = $config;
|
|
||||||
$this->buildMenu();
|
|
||||||
|
|
||||||
if (isset($_GET['search'])) {
|
|
||||||
$this->performSearch($_GET['search']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildMenu() {
|
|
||||||
$this->menu = $this->scanDirectory($this->config['content_dir'], '');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function scanDirectory($dir, $prefix) {
|
|
||||||
if (!is_dir($dir)) return [];
|
|
||||||
|
|
||||||
$items = scandir($dir);
|
|
||||||
sort($items);
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item[0] === '.') continue;
|
|
||||||
|
|
||||||
$path = $dir . '/' . $item;
|
|
||||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
|
||||||
|
|
||||||
if (is_dir($path)) {
|
|
||||||
$result[] = [
|
|
||||||
'type' => 'folder',
|
|
||||||
'title' => ucfirst($item),
|
|
||||||
'path' => $relativePath,
|
|
||||||
'children' => $this->scanDirectory($path, $relativePath)
|
|
||||||
];
|
|
||||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
|
||||||
$title = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
|
||||||
$result[] = [
|
|
||||||
'type' => 'file',
|
|
||||||
'title' => $title,
|
|
||||||
'path' => $relativePath,
|
|
||||||
'url' => '?page=' . $relativePath
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function performSearch($query) {
|
|
||||||
$this->searchResults = [];
|
|
||||||
$this->searchInDirectory($this->config['content_dir'], '', $query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function searchInDirectory($dir, $prefix, $query) {
|
|
||||||
if (!is_dir($dir)) return;
|
|
||||||
|
|
||||||
$items = scandir($dir);
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item[0] === '.') continue;
|
|
||||||
|
|
||||||
$path = $dir . '/' . $item;
|
|
||||||
$relativePath = $prefix ? $prefix . '/' . $item : $item;
|
|
||||||
|
|
||||||
if (is_dir($path)) {
|
|
||||||
$this->searchInDirectory($path, $relativePath, $query);
|
|
||||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
|
||||||
$content = file_get_contents($path);
|
|
||||||
if (stripos($content, $query) !== false || stripos($item, $query) !== false) {
|
|
||||||
$title = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
|
||||||
$this->searchResults[] = [
|
|
||||||
'title' => $title,
|
|
||||||
'path' => $relativePath,
|
|
||||||
'url' => '?page=' . $relativePath,
|
|
||||||
'snippet' => $this->createSnippet($content, $query)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createSnippet($content, $query) {
|
|
||||||
$content = strip_tags($content);
|
|
||||||
$pos = stripos($content, $query);
|
|
||||||
if ($pos === false) return substr($content, 0, 100) . '...';
|
|
||||||
|
|
||||||
$start = max(0, $pos - 50);
|
|
||||||
$snippet = substr($content, $start, 150);
|
|
||||||
return '...' . $snippet . '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPage() {
|
|
||||||
if (isset($_GET['search'])) {
|
|
||||||
return $this->getSearchResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
$page = $_GET['page'] ?? $this->config['default_page'];
|
|
||||||
$page = preg_replace('/\.[^.]+$/', '', $page);
|
|
||||||
|
|
||||||
$filePath = $this->config['content_dir'] . '/' . $page;
|
|
||||||
$actualFilePath = null;
|
|
||||||
|
|
||||||
if (file_exists($filePath . '.md')) {
|
|
||||||
$actualFilePath = $filePath . '.md';
|
|
||||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
|
||||||
} elseif (file_exists($filePath . '.php')) {
|
|
||||||
$actualFilePath = $filePath . '.php';
|
|
||||||
$result = $this->parsePHP($actualFilePath);
|
|
||||||
} elseif (file_exists($filePath . '.html')) {
|
|
||||||
$actualFilePath = $filePath . '.html';
|
|
||||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
|
||||||
} elseif (is_dir($filePath)) {
|
|
||||||
// Check for index files in directory
|
|
||||||
if (file_exists($filePath . '/index.md')) {
|
|
||||||
$actualFilePath = $filePath . '/index.md';
|
|
||||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
|
||||||
} elseif (file_exists($filePath . '/index.php')) {
|
|
||||||
$actualFilePath = $filePath . '/index.php';
|
|
||||||
$result = $this->parsePHP($actualFilePath);
|
|
||||||
} elseif (file_exists($filePath . '/index.html')) {
|
|
||||||
$actualFilePath = $filePath . '/index.html';
|
|
||||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
|
||||||
} else {
|
|
||||||
// Generate directory listing
|
|
||||||
return $this->generateDirectoryListing($filePath, $page);
|
|
||||||
}
|
|
||||||
} elseif (file_exists($filePath)) {
|
|
||||||
$actualFilePath = $filePath;
|
|
||||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
|
||||||
if ($extension === 'md') {
|
|
||||||
$result = $this->parseMarkdown(file_get_contents($actualFilePath));
|
|
||||||
} elseif ($extension === 'php') {
|
|
||||||
$result = $this->parsePHP($actualFilePath);
|
|
||||||
} elseif ($extension === 'html') {
|
|
||||||
$result = $this->parseHTML(file_get_contents($actualFilePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($result) && $actualFilePath) {
|
|
||||||
$result['file_info'] = $this->getFileInfo($actualFilePath);
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getError404();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getFileInfo($filePath) {
|
|
||||||
if (!file_exists($filePath)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stats = stat($filePath);
|
|
||||||
$created = date('d-m-Y H:i', $stats['ctime']);
|
|
||||||
$modified = date('d-m-Y H:i', $stats['mtime']);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'created' => $created,
|
|
||||||
'modified' => $modified,
|
|
||||||
'size' => $this->formatFileSize($stats['size'])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatFileSize($bytes) {
|
|
||||||
$units = ['B', 'KB', 'MB', 'GB'];
|
|
||||||
$bytes = max($bytes, 0);
|
|
||||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
|
||||||
$pow = min($pow, count($units) - 1);
|
|
||||||
|
|
||||||
$bytes /= pow(1024, $pow);
|
|
||||||
|
|
||||||
return round($bytes, 2) . ' ' . $units[$pow];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generateDirectoryListing($dirPath, $urlPath) {
|
|
||||||
$title = ucfirst(basename($dirPath));
|
|
||||||
$content = '<div class="row">';
|
|
||||||
|
|
||||||
$items = scandir($dirPath);
|
|
||||||
sort($items);
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item[0] === '.') continue;
|
|
||||||
|
|
||||||
$path = $dirPath . '/' . $item;
|
|
||||||
$relativePath = $urlPath . '/' . $item;
|
|
||||||
$itemName = ucfirst(pathinfo($item, PATHINFO_FILENAME));
|
|
||||||
|
|
||||||
if (is_dir($path)) {
|
|
||||||
$content .= '<div class="col-md-6 mb-4">';
|
|
||||||
$content .= '<div class="card h-100 border-0 rounded-0 bg-light">';
|
|
||||||
$content .= '<div class="card-body">';
|
|
||||||
$content .= '<h3 class="h5 card-title"><a href="?page=' . $relativePath . '" class="text-decoration-none text-dark"><i class="bi bi-folder me-2"></i> ' . $itemName . '</a></h3>';
|
|
||||||
$content .= '</div></div></div>';
|
|
||||||
} elseif (preg_match('/\.(md|php|html)$/', $item)) {
|
|
||||||
// Remove extension from URL for cleaner links
|
|
||||||
$cleanPath = preg_replace('/\.[^.]+$/', '', $relativePath);
|
|
||||||
|
|
||||||
// Get preview content
|
|
||||||
$preview = '';
|
|
||||||
$fileContent = file_get_contents($path);
|
|
||||||
|
|
||||||
// Extract title if possible
|
|
||||||
$fileTitle = $itemName;
|
|
||||||
if (preg_match('/^#\s+(.+)$/m', $fileContent, $matches)) {
|
|
||||||
$fileTitle = trim($matches[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract preview text (first paragraph)
|
|
||||||
$fileContent = strip_tags($this->parseMarkdown($fileContent)['content']);
|
|
||||||
$preview = substr($fileContent, 0, 150) . '...';
|
|
||||||
|
|
||||||
$content .= '<div class="col-md-6 mb-4">';
|
|
||||||
$content .= '<div class="card h-100 border rounded-0">';
|
|
||||||
$content .= '<div class="card-body">';
|
|
||||||
$content .= '<h3 class="h5 card-title"><a href="?page=' . $cleanPath . '" class="text-decoration-none text-primary">' . $fileTitle . '</a></h3>';
|
|
||||||
$content .= '<p class="card-text text-muted small">' . $preview . '</p>';
|
|
||||||
$content .= '<a href="?page=' . $cleanPath . '" class="btn btn-sm btn-outline-primary rounded-0">Lees meer</a>';
|
|
||||||
$content .= '</div></div></div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$content .= '</div>';
|
|
||||||
|
|
||||||
return [
|
|
||||||
'title' => $title,
|
|
||||||
'content' => $content
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSearchResults() {
|
|
||||||
$query = $_GET['search'];
|
|
||||||
$content = '<h2>Search Results for: "' . htmlspecialchars($query) . '"</h2>';
|
|
||||||
|
|
||||||
if (empty($this->searchResults)) {
|
|
||||||
$content .= '<p>No results found.</p>';
|
|
||||||
} else {
|
|
||||||
$content .= '<p>Found ' . count($this->searchResults) . ' results:</p>';
|
|
||||||
foreach ($this->searchResults as $result) {
|
|
||||||
$content .= '<div class="card mb-3">';
|
|
||||||
$content .= '<div class="card-body">';
|
|
||||||
$content .= '<h5 class="card-title"><a href="' . htmlspecialchars($result['url']) . '">' . htmlspecialchars($result['title']) . '</a></h5>';
|
|
||||||
$content .= '<p class="card-text text-muted">' . htmlspecialchars($result['path']) . '</p>';
|
|
||||||
$content .= '<p class="card-text">' . htmlspecialchars($result['snippet']) . '</p>';
|
|
||||||
$content .= '</div></div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'title' => 'Search Results',
|
|
||||||
'content' => $content
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseMarkdown($content) {
|
|
||||||
$lines = explode("\n", $content);
|
|
||||||
$title = '';
|
|
||||||
$body = '';
|
|
||||||
$inBody = false;
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
if (!$inBody && preg_match('/^#\s+(.+)$/', $line, $matches)) {
|
|
||||||
$title = $matches[1];
|
|
||||||
$inBody = true;
|
|
||||||
} elseif ($inBody || trim($line) !== '') {
|
|
||||||
$body .= $line . "\n";
|
|
||||||
$inBody = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = preg_replace('/### (.+)/', '<h3>$1</h3>', $body);
|
|
||||||
$body = preg_replace('/## (.+)/', '<h2>$1</h2>', $body);
|
|
||||||
$body = preg_replace('/# (.+)/', '<h1>$1</h1>', $body);
|
|
||||||
$body = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $body);
|
|
||||||
$body = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $body);
|
|
||||||
|
|
||||||
// Convert Markdown links to HTML links
|
|
||||||
$body = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $body);
|
|
||||||
|
|
||||||
// Convert relative internal links to CMS format
|
|
||||||
$body = preg_replace('/href="\/blog\/([^"]+)"/', 'href="?page=blog/$1"', $body);
|
|
||||||
$body = preg_replace('/href="\/([^"]+)"/', 'href="?page=$1"', $body);
|
|
||||||
|
|
||||||
$body = preg_replace('/\n\n/', '</p><p>', $body);
|
|
||||||
$body = '<p>' . $body . '</p>';
|
|
||||||
$body = preg_replace('/<p><\/p>/', '', $body);
|
|
||||||
$body = preg_replace('/<p>(<h[1-6]>)/', '$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>(.*?)<\/title>/i', $content, $matches)) {
|
|
||||||
$title = strip_tags($matches[1]);
|
|
||||||
} elseif (preg_match('/<h1[^>]*>(.*?)<\/h1>/i', $content, $matches)) {
|
|
||||||
$title = strip_tags($matches[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'title' => $title,
|
|
||||||
'content' => $content
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getError404() {
|
|
||||||
return [
|
|
||||||
'title' => 'Page Not Found',
|
|
||||||
'content' => '<h1>404 - Page Not Found</h1><p>The page you are looking for does not exist.</p>'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = '<i class="bi bi-file-text"></i> 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 '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li><li class="breadcrumb-item active">Search</li></ol></nav>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$page = $_GET['page'] ?? $this->config['default_page'];
|
|
||||||
$page = preg_replace('/\.[^.]+$/', '', $page);
|
|
||||||
|
|
||||||
if ($page === $this->config['default_page']) {
|
|
||||||
return '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item active">Home</li></ol></nav>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$parts = explode('/', $page);
|
|
||||||
$breadcrumb = '<nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="?page=' . $this->config['default_page'] . '">Home</a></li>';
|
|
||||||
|
|
||||||
$path = '';
|
|
||||||
foreach ($parts as $i => $part) {
|
|
||||||
$path .= ($path ? '/' : '') . $part;
|
|
||||||
$title = ucfirst($part);
|
|
||||||
|
|
||||||
if ($i === count($parts) - 1) {
|
|
||||||
$breadcrumb .= '<li class="breadcrumb-item active">' . $title . '</li>';
|
|
||||||
} else {
|
|
||||||
// Check if directory has index file
|
|
||||||
$dirPath = $this->config['content_dir'] . '/' . $path;
|
|
||||||
$hasIndex = file_exists($dirPath . '/index.md') || file_exists($dirPath . '/index.php') || file_exists($dirPath . '/index.html');
|
|
||||||
|
|
||||||
// Always make breadcrumb items clickable, CMS will generate index if missing
|
|
||||||
$breadcrumb .= '<li class="breadcrumb-item"><a href="?page=' . $path . '">' . $title . '</a></li>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$breadcrumb .= '</ol></nav>';
|
|
||||||
return $breadcrumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderMenu($items, $level = 0) {
|
|
||||||
$html = '';
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item['type'] === 'folder') {
|
|
||||||
$hasChildren = !empty($item['children']);
|
|
||||||
$html .= '<li class="nav-item">';
|
|
||||||
|
|
||||||
if ($hasChildren) {
|
|
||||||
$folderId = 'folder-' . str_replace('/', '-', $item['path']);
|
|
||||||
$html .= '<span class="nav-link folder-toggle" data-bs-toggle="collapse" data-bs-target="#' . $folderId . '" aria-expanded="false">';
|
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
|
||||||
$html .= '</span>';
|
|
||||||
$html .= '<ul class="nav flex-column ms-2 collapse" id="' . $folderId . '">';
|
|
||||||
$html .= $this->renderMenu($item['children'], $level + 1);
|
|
||||||
$html .= '</ul>';
|
|
||||||
} else {
|
|
||||||
$html .= '<span class="nav-link folder-disabled" disabled>';
|
|
||||||
$html .= '<i class="arrow bi bi-chevron-right"></i> ' . htmlspecialchars($item['title']);
|
|
||||||
$html .= '</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$html .= '</li>';
|
|
||||||
} else {
|
|
||||||
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
|
|
||||||
$html .= '<li class="nav-item">';
|
|
||||||
$html .= '<a class="nav-link page-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
|
||||||
$html .= '</li>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block direct access to content files
|
// Block direct access to content files
|
||||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||||
if (strpos($requestUri, '/content/') !== false) {
|
if (strpos($requestUri, '/content/') !== false) {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
[Wed Nov 19 17:58:28 2025] Failed to listen on localhost:8080 (reason: Address already in use)
|
|
||||||
@ -1,381 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{{page_title}} - {{site_title}}</title>
|
|
||||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
.sidebar.collapsed {
|
|
||||||
transform: translateX(-250px);
|
|
||||||
}
|
|
||||||
.sidebar-toggle {
|
|
||||||
position: fixed;
|
|
||||||
top: 80px;
|
|
||||||
left: 10px;
|
|
||||||
z-index: 1001;
|
|
||||||
background-color: #0d6efd;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
.sidebar-toggle:hover {
|
|
||||||
background-color: #0a58ca;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
.sidebar-toggle.shifted {
|
|
||||||
left: 270px;
|
|
||||||
}
|
|
||||||
.main-content.shifted {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
transition: margin-left 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
.folder-toggle:hover {
|
|
||||||
background-color: #e9ecef !important;
|
|
||||||
}
|
|
||||||
.folder-toggle[aria-expanded="true"] {
|
|
||||||
background-color: #dee2e6 !important;
|
|
||||||
color: #212529 !important;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
/* Progressive background colors for nested folders */
|
|
||||||
.nav .nav .folder-toggle {
|
|
||||||
background-color: #f1f3f4 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #eaedee !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #e3e7e8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #dce1e2 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #d5dbdd !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #ced5d8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #c7cfd3 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #c0c9ce !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .folder-toggle {
|
|
||||||
background-color: #b9c3c9 !important;
|
|
||||||
}
|
|
||||||
.folder-toggle .arrow {
|
|
||||||
margin-right: 8px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
.folder-toggle[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;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
/* Progressive background colors for nested pages */
|
|
||||||
.nav .nav .page-link {
|
|
||||||
background-color: #fafbfc !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .page-link {
|
|
||||||
background-color: #f5f7f8 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #f0f3f4 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #ebefef !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #e6eaea !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #e1e5e5 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #dce0e0 !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #d7dbdb !important;
|
|
||||||
}
|
|
||||||
.nav .nav .nav .nav .nav .nav .nav .nav .nav .nav .page-link {
|
|
||||||
background-color: #d2d6d6 !important;
|
|
||||||
}
|
|
||||||
.page-link:hover {
|
|
||||||
color: #212529 !important;
|
|
||||||
background-color: #f8f9fa !important;
|
|
||||||
}
|
|
||||||
.nav-link.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;
|
|
||||||
}
|
|
||||||
.site-info a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.auto-link {
|
|
||||||
color: #0d6efd;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 2px dashed #0d6efd;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.auto-link: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;
|
|
||||||
}
|
|
||||||
.card-title a: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;
|
|
||||||
}
|
|
||||||
.folder-disabled .arrow {
|
|
||||||
margin-right: 8px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.sidebar {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="bg-primary text-white py-3">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<img src="assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
|
||||||
<h1 class="h3 mb-0">{{site_title}}</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<form class="d-flex" method="GET" action="">
|
|
||||||
<input class="form-control me-2" type="search" name="search" placeholder="Search..." value="{{search_query}}">
|
|
||||||
<button class="btn btn-outline-light" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="main-wrapper">
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<button class="sidebar-toggle" id="sidebarToggle">
|
|
||||||
<i class="bi bi-list"></i>
|
|
||||||
</button>
|
|
||||||
<nav class="sidebar" id="sidebar">
|
|
||||||
<div class="pt-3">
|
|
||||||
<ul class="nav flex-column">
|
|
||||||
{{menu}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="main-content">
|
|
||||||
<div>
|
|
||||||
{{breadcrumb}}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
||||||
<h2>{{page_title}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{{content}}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="bg-light border-top py-3">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="file-info">
|
|
||||||
{{file_info}}
|
|
||||||
</div>
|
|
||||||
<div class="site-info">
|
|
||||||
<small class="text-muted">Powered by <a href="https://git.noorlander.info/E.Noorlander/CodePress.git" target="_blank" rel="noopener">CodePress CMS</a></small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Sidebar toggle functionality
|
|
||||||
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
||||||
const sidebar = document.getElementById('sidebar');
|
|
||||||
const mainContent = document.querySelector('.main-content');
|
|
||||||
|
|
||||||
sidebarToggle.addEventListener('click', function() {
|
|
||||||
sidebar.classList.toggle('collapsed');
|
|
||||||
mainContent.classList.toggle('shifted');
|
|
||||||
sidebarToggle.classList.toggle('shifted');
|
|
||||||
|
|
||||||
// Change icon
|
|
||||||
const icon = this.querySelector('i');
|
|
||||||
if (sidebar.classList.contains('collapsed')) {
|
|
||||||
icon.classList.remove('bi-list');
|
|
||||||
icon.classList.add('bi-chevron-right');
|
|
||||||
} else {
|
|
||||||
icon.classList.remove('bi-chevron-right');
|
|
||||||
icon.classList.add('bi-list');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open folders that contain the current active page
|
|
||||||
const activeLink = document.querySelector('.nav-link.active');
|
|
||||||
if (activeLink) {
|
|
||||||
let parent = activeLink.closest('.collapse');
|
|
||||||
while (parent) {
|
|
||||||
const toggle = document.querySelector('[data-bs-target="#' + parent.id + '"]');
|
|
||||||
if (toggle) {
|
|
||||||
const collapse = new bootstrap.Collapse(parent, {
|
|
||||||
show: true
|
|
||||||
});
|
|
||||||
toggle.setAttribute('aria-expanded', 'true');
|
|
||||||
}
|
|
||||||
parent = parent.parentElement.closest('.collapse');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close other folders when opening a new one
|
|
||||||
const folderToggles = document.querySelectorAll('.folder-toggle');
|
|
||||||
folderToggles.forEach(toggle => {
|
|
||||||
toggle.addEventListener('click', function(e) {
|
|
||||||
const targetId = this.getAttribute('data-bs-target');
|
|
||||||
const isExpanded = this.getAttribute('aria-expanded') === 'true';
|
|
||||||
|
|
||||||
if (!isExpanded) {
|
|
||||||
// Close all other folders
|
|
||||||
folderToggles.forEach(otherToggle => {
|
|
||||||
if (otherToggle !== this) {
|
|
||||||
const otherTargetId = otherToggle.getAttribute('data-bs-target');
|
|
||||||
if (otherTargetId) {
|
|
||||||
const otherCollapse = document.querySelector(otherTargetId);
|
|
||||||
if (otherCollapse) {
|
|
||||||
const bsCollapse = bootstrap.Collapse.getInstance(otherCollapse);
|
|
||||||
if (bsCollapse) {
|
|
||||||
bsCollapse.hide();
|
|
||||||
} else {
|
|
||||||
new bootstrap.Collapse(otherCollapse, {
|
|
||||||
hide: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
otherToggle.setAttribute('aria-expanded', 'false');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
22
vendor/autoload.php
vendored
Normal file
22
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 50600) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, $err);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException($err);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInit071586d19f5409de22b3235d85d8476c::getLoader();
|
||||||
579
vendor/composer/ClassLoader.php
vendored
Normal file
579
vendor/composer/ClassLoader.php
vendored
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* 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 <fabien@symfony.com>
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
* @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<string, array<string, int>>
|
||||||
|
*/
|
||||||
|
private $prefixLengthsPsr4 = array();
|
||||||
|
/**
|
||||||
|
* @var array<string, list<string>>
|
||||||
|
*/
|
||||||
|
private $prefixDirsPsr4 = array();
|
||||||
|
/**
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
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<string, array<string, list<string>>>
|
||||||
|
*/
|
||||||
|
private $prefixesPsr0 = array();
|
||||||
|
/**
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
private $fallbackDirsPsr0 = array();
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $useIncludePath = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
private $classMap = array();
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $classMapAuthoritative = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, bool>
|
||||||
|
*/
|
||||||
|
private $missingClasses = array();
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $apcuPrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, self>
|
||||||
|
*/
|
||||||
|
private static $registeredLoaders = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $vendorDir
|
||||||
|
*/
|
||||||
|
public function __construct($vendorDir = null)
|
||||||
|
{
|
||||||
|
$this->vendorDir = $vendorDir;
|
||||||
|
self::initializeIncludeClosure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, list<string>>
|
||||||
|
*/
|
||||||
|
public function getPrefixes()
|
||||||
|
{
|
||||||
|
if (!empty($this->prefixesPsr0)) {
|
||||||
|
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, list<string>>
|
||||||
|
*/
|
||||||
|
public function getPrefixesPsr4()
|
||||||
|
{
|
||||||
|
return $this->prefixDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public function getFallbackDirs()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public function getFallbackDirsPsr4()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string> Array of classname => path
|
||||||
|
*/
|
||||||
|
public function getClassMap()
|
||||||
|
{
|
||||||
|
return $this->classMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $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>|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>|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>|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>|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<string, self>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
396
vendor/composer/InstalledVersions.php
vendored
Normal file
396
vendor/composer/InstalledVersions.php
vendored
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||||
|
*/
|
||||||
|
private static $installed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $installedIsLocalDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool|null
|
||||||
|
*/
|
||||||
|
private static $canGetVendors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
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<string>
|
||||||
|
*/
|
||||||
|
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<string>
|
||||||
|
*/
|
||||||
|
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||||
|
*/
|
||||||
|
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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||||
|
$required = require __DIR__ . '/installed.php';
|
||||||
|
self::$installed = $required;
|
||||||
|
} else {
|
||||||
|
self::$installed = array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||||
|
$installed[] = self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
vendor/composer/LICENSE
vendored
Normal file
21
vendor/composer/LICENSE
vendored
Normal file
@ -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.
|
||||||
|
|
||||||
43
vendor/composer/autoload_classmap.php
vendored
Normal file
43
vendor/composer/autoload_classmap.php
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_classmap.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(__DIR__);
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Composer\\InstalledVersions' => $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',
|
||||||
|
);
|
||||||
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_namespaces.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(__DIR__);
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
);
|
||||||
10
vendor/composer/autoload_psr4.php
vendored
Normal file
10
vendor/composer/autoload_psr4.php
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_psr4.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(__DIR__);
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Mustache\\' => array($vendorDir . '/mustache/mustache/src'),
|
||||||
|
);
|
||||||
38
vendor/composer/autoload_real.php
vendored
Normal file
38
vendor/composer/autoload_real.php
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_real.php @generated by Composer
|
||||||
|
|
||||||
|
class ComposerAutoloaderInit071586d19f5409de22b3235d85d8476c
|
||||||
|
{
|
||||||
|
private static $loader;
|
||||||
|
|
||||||
|
public static function loadClassLoader($class)
|
||||||
|
{
|
||||||
|
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||||
|
require __DIR__ . '/ClassLoader.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Composer\Autoload\ClassLoader
|
||||||
|
*/
|
||||||
|
public static function getLoader()
|
||||||
|
{
|
||||||
|
if (null !== self::$loader) {
|
||||||
|
return self::$loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/platform_check.php';
|
||||||
|
|
||||||
|
spl_autoload_register(array('ComposerAutoloaderInit071586d19f5409de22b3235d85d8476c', 'loadClassLoader'), true, true);
|
||||||
|
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||||
|
spl_autoload_unregister(array('ComposerAutoloaderInit071586d19f5409de22b3235d85d8476c', 'loadClassLoader'));
|
||||||
|
|
||||||
|
require __DIR__ . '/autoload_static.php';
|
||||||
|
call_user_func(\Composer\Autoload\ComposerStaticInit071586d19f5409de22b3235d85d8476c::getInitializer($loader));
|
||||||
|
|
||||||
|
$loader->register(true);
|
||||||
|
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
vendor/composer/autoload_static.php
vendored
Normal file
69
vendor/composer/autoload_static.php
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_static.php @generated by Composer
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
class ComposerStaticInit071586d19f5409de22b3235d85d8476c
|
||||||
|
{
|
||||||
|
public static $prefixLengthsPsr4 = array (
|
||||||
|
'M' =>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
vendor/composer/installed.json
vendored
Normal file
62
vendor/composer/installed.json
vendored
Normal file
@ -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": []
|
||||||
|
}
|
||||||
32
vendor/composer/installed.php
vendored
Normal file
32
vendor/composer/installed.php
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php return array(
|
||||||
|
'root' => 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
25
vendor/composer/platform_check.php
vendored
Normal file
25
vendor/composer/platform_check.php
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// platform_check.php @generated by Composer
|
||||||
|
|
||||||
|
$issues = array();
|
||||||
|
|
||||||
|
if (!(PHP_VERSION_ID >= 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
47
vendor/mustache/mustache/.github/workflows/tests.yml
vendored
Normal file
47
vendor/mustache/mustache/.github/workflows/tests.yml
vendored
Normal file
@ -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
|
||||||
20
vendor/mustache/mustache/.php-cs-fixer.php
vendored
Normal file
20
vendor/mustache/mustache/.php-cs-fixer.php
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use PhpCsFixer\Config;
|
||||||
|
|
||||||
|
$config = new Config();
|
||||||
|
|
||||||
|
$config->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;
|
||||||
21
vendor/mustache/mustache/LICENSE
vendored
Normal file
21
vendor/mustache/mustache/LICENSE
vendored
Normal file
@ -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.
|
||||||
94
vendor/mustache/mustache/README.md
vendored
Normal file
94
vendor/mustache/mustache/README.md
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Mustache.php
|
||||||
|
|
||||||
|
A [Mustache][mustache] implementation in PHP.
|
||||||
|
|
||||||
|
[][packagist]
|
||||||
|
[][packagist]
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
composer require mustache/mustache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
A quick example:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$m = new \Mustache\Engine(['entity_flags' => 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
|
||||||
|
<?php
|
||||||
|
class Chris {
|
||||||
|
public $name = "Chris";
|
||||||
|
public $value = 10000;
|
||||||
|
|
||||||
|
public function taxed_value() {
|
||||||
|
return $this->value - ($this->value * 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public $in_ca = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
And render it:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
$m = new \Mustache\Engine(['entity_flags' => 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
|
||||||
38
vendor/mustache/mustache/composer.json
vendored
Normal file
38
vendor/mustache/mustache/composer.json
vendored
Normal file
@ -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/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
vendor/mustache/mustache/src/Cache.php
vendored
Normal file
46
vendor/mustache/mustache/src/Cache.php
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Cache interface.
|
||||||
|
*
|
||||||
|
* Interface for caching and loading Template classes generated by the Compiler.
|
||||||
|
*/
|
||||||
|
interface Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load a compiled Template class from cache.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool indicates successfully class load
|
||||||
|
*/
|
||||||
|
public function load($key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache and load a compiled Template class.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function cache($key, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a logger instance.
|
||||||
|
*
|
||||||
|
* @param Logger|LoggerInterface $logger
|
||||||
|
*/
|
||||||
|
public function setLogger($logger = null);
|
||||||
|
}
|
||||||
68
vendor/mustache/mustache/src/Cache/AbstractCache.php
vendored
Normal file
68
vendor/mustache/mustache/src/Cache/AbstractCache.php
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Cache;
|
||||||
|
|
||||||
|
use Mustache\Cache;
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Logger;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract Mustache Cache class.
|
||||||
|
*
|
||||||
|
* Provides logging support to child implementations.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
abstract class AbstractCache implements Cache
|
||||||
|
{
|
||||||
|
private $logger = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current logger instance.
|
||||||
|
*
|
||||||
|
* @return Logger|LoggerInterface
|
||||||
|
*/
|
||||||
|
public function getLogger()
|
||||||
|
{
|
||||||
|
return $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
166
vendor/mustache/mustache/src/Cache/FilesystemCache.php
vendored
Normal file
166
vendor/mustache/mustache/src/Cache/FilesystemCache.php
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Cache;
|
||||||
|
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Cache filesystem implementation.
|
||||||
|
*
|
||||||
|
* A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
|
||||||
|
*
|
||||||
|
* $cache = new FilesystemCache(__DIR__.'/cache');
|
||||||
|
* $cache->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
|
||||||
|
}
|
||||||
|
}
|
||||||
51
vendor/mustache/mustache/src/Cache/NoopCache.php
vendored
Normal file
51
vendor/mustache/mustache/src/Cache/NoopCache.php
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Cache;
|
||||||
|
|
||||||
|
use Mustache\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Cache in-memory implementation.
|
||||||
|
*
|
||||||
|
* The in-memory cache is used for uncached lambda section templates. It's also useful during development, but is not
|
||||||
|
* recommended for production use.
|
||||||
|
*/
|
||||||
|
class NoopCache extends AbstractCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Loads nothing. Move along.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function load($key)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the compiled Mustache Template class without caching.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function cache($key, $value)
|
||||||
|
{
|
||||||
|
$this->log(
|
||||||
|
Logger::WARNING,
|
||||||
|
'Template cache disabled, evaluating "{className}" class at runtime',
|
||||||
|
['className' => $key]
|
||||||
|
);
|
||||||
|
eval('?>' . $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
807
vendor/mustache/mustache/src/Compiler.php
vendored
Normal file
807
vendor/mustache/mustache/src/Compiler.php
vendored
Normal file
@ -0,0 +1,807 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\SyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Compiler class.
|
||||||
|
*
|
||||||
|
* This class is responsible for turning a Mustache token parse tree into normal PHP source code.
|
||||||
|
*/
|
||||||
|
class Compiler
|
||||||
|
{
|
||||||
|
private $pragmas;
|
||||||
|
private $defaultPragmas = [];
|
||||||
|
private $sections;
|
||||||
|
private $blocks;
|
||||||
|
private $source;
|
||||||
|
private $indentNextLine;
|
||||||
|
private $customEscape;
|
||||||
|
private $entityFlags;
|
||||||
|
private $charset;
|
||||||
|
private $strictCallables;
|
||||||
|
|
||||||
|
// Optional Mustache specs
|
||||||
|
private $lambdas = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile a Mustache token parse tree into PHP source code.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if the FILTERS pragma is set but lambdas are not enabled
|
||||||
|
*
|
||||||
|
* @param string $source Mustache Template source code
|
||||||
|
* @param array $tree Parse tree of Mustache tokens
|
||||||
|
* @param string $name Mustache Template class name
|
||||||
|
* @param bool $customEscape (default: false)
|
||||||
|
* @param string $charset (default: 'UTF-8')
|
||||||
|
* @param bool $strictCallables (default: false)
|
||||||
|
* @param int $entityFlags (default: ENT_COMPAT)
|
||||||
|
*
|
||||||
|
* @return string Generated PHP source code
|
||||||
|
*/
|
||||||
|
public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
|
||||||
|
{
|
||||||
|
$this->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 = '<?php
|
||||||
|
|
||||||
|
class %s extends \\Mustache\\Template
|
||||||
|
{
|
||||||
|
private $lambdaHelper;%s%s
|
||||||
|
|
||||||
|
public function renderInternal(\\Mustache\\Context $context, $indent = \'\')
|
||||||
|
{
|
||||||
|
$this->lambdaHelper = new \\Mustache\\LambdaHelper($this->mustache, $context);
|
||||||
|
$buffer = \'\';
|
||||||
|
%s
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
}';
|
||||||
|
|
||||||
|
const KLASS_NO_LAMBDAS = '<?php
|
||||||
|
|
||||||
|
class %s extends \\Mustache\\Template
|
||||||
|
{%s%s
|
||||||
|
public function renderInternal(\\Mustache\\Context $context, $indent = \'\')
|
||||||
|
{
|
||||||
|
$buffer = \'\';
|
||||||
|
%s
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
const STRICT_CALLABLE = 'protected $strictCallables = true;';
|
||||||
|
|
||||||
|
const NO_LAMBDAS = 'protected $lambdas = false;';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Mustache Template class PHP source.
|
||||||
|
*
|
||||||
|
* @param array $tree Parse tree of Mustache tokens
|
||||||
|
* @param string $name Mustache Template class name
|
||||||
|
*
|
||||||
|
* @return string Generated PHP source code
|
||||||
|
*/
|
||||||
|
private function writeCode(array $tree, $name)
|
||||||
|
{
|
||||||
|
$code = $this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
277
vendor/mustache/mustache/src/Context.php
vendored
Normal file
277
vendor/mustache/mustache/src/Context.php
vendored
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template rendering Context.
|
||||||
|
*/
|
||||||
|
class Context
|
||||||
|
{
|
||||||
|
private $stack = [];
|
||||||
|
private $blockStack = [];
|
||||||
|
|
||||||
|
private $buggyPropertyShadowing = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache rendering Context constructor.
|
||||||
|
*
|
||||||
|
* @param mixed $context Default rendering context (default: null)
|
||||||
|
* @param bool $buggyPropertyShadowing See Engine::getBuggyPropertyShadowing (default: false)
|
||||||
|
*/
|
||||||
|
public function __construct($context = null, $buggyPropertyShadowing = false)
|
||||||
|
{
|
||||||
|
if ($context !== null) {
|
||||||
|
$this->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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
963
vendor/mustache/mustache/src/Engine.php
vendored
Normal file
963
vendor/mustache/mustache/src/Engine.php
vendored
Normal file
@ -0,0 +1,963 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Cache\FilesystemCache;
|
||||||
|
use Mustache\Cache\NoopCache;
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Loader\ArrayLoader;
|
||||||
|
use Mustache\Loader\MutableLoader;
|
||||||
|
use Mustache\Loader\StringLoader;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Mustache implementation in PHP.
|
||||||
|
*
|
||||||
|
* {@link https://mustache.github.io}
|
||||||
|
*
|
||||||
|
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
|
||||||
|
* logic from template files. In fact, it is not even possible to embed logic in the template.
|
||||||
|
*
|
||||||
|
* This is very, very rad.
|
||||||
|
*
|
||||||
|
* @author Justin Hileman {@link http://justinhileman.com}
|
||||||
|
*/
|
||||||
|
class Engine
|
||||||
|
{
|
||||||
|
const VERSION = '3.0.0';
|
||||||
|
const SPEC_VERSION = '1.4.3';
|
||||||
|
|
||||||
|
const PRAGMA_FILTERS = 'FILTERS';
|
||||||
|
const PRAGMA_ANCHORED_DOT = 'ANCHORED-DOT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated PRAGMA_BLOCKS is now part of the Mustache spec, and is enabled by default
|
||||||
|
*/
|
||||||
|
const PRAGMA_BLOCKS = 'BLOCKS';
|
||||||
|
|
||||||
|
// Known pragmas
|
||||||
|
private static $knownPragmas = [
|
||||||
|
self::PRAGMA_FILTERS => 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
vendor/mustache/mustache/src/Exception.php
vendored
Normal file
17
vendor/mustache/mustache/src/Exception.php
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
interface Exception
|
||||||
|
{
|
||||||
|
// This space intentionally left blank.
|
||||||
|
}
|
||||||
22
vendor/mustache/mustache/src/Exception/InvalidArgumentException.php
vendored
Normal file
22
vendor/mustache/mustache/src/Exception/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid argument exception.
|
||||||
|
*/
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException implements Exception
|
||||||
|
{
|
||||||
|
// This space intentionally left blank.
|
||||||
|
}
|
||||||
22
vendor/mustache/mustache/src/Exception/LogicException.php
vendored
Normal file
22
vendor/mustache/mustache/src/Exception/LogicException.php
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logic exception.
|
||||||
|
*/
|
||||||
|
class LogicException extends \LogicException implements Exception
|
||||||
|
{
|
||||||
|
// This space intentionally left blank.
|
||||||
|
}
|
||||||
22
vendor/mustache/mustache/src/Exception/RuntimeException.php
vendored
Normal file
22
vendor/mustache/mustache/src/Exception/RuntimeException.php
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime exception.
|
||||||
|
*/
|
||||||
|
class RuntimeException extends \RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
// This space intentionally left blank.
|
||||||
|
}
|
||||||
40
vendor/mustache/mustache/src/Exception/SyntaxException.php
vendored
Normal file
40
vendor/mustache/mustache/src/Exception/SyntaxException.php
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache syntax exception.
|
||||||
|
*/
|
||||||
|
class SyntaxException extends LogicException implements Exception
|
||||||
|
{
|
||||||
|
protected $token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $msg
|
||||||
|
* @param Exception $previous
|
||||||
|
*/
|
||||||
|
public function __construct($msg, array $token, $previous = null)
|
||||||
|
{
|
||||||
|
$this->token = $token;
|
||||||
|
parent::__construct($msg, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getToken()
|
||||||
|
{
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
vendor/mustache/mustache/src/Exception/UnknownFilterException.php
vendored
Normal file
38
vendor/mustache/mustache/src/Exception/UnknownFilterException.php
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown filter exception.
|
||||||
|
*/
|
||||||
|
class UnknownFilterException extends \UnexpectedValueException implements Exception
|
||||||
|
{
|
||||||
|
protected $filterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filterName
|
||||||
|
* @param Exception $previous
|
||||||
|
*/
|
||||||
|
public function __construct($filterName, $previous = null)
|
||||||
|
{
|
||||||
|
$this->filterName = $filterName;
|
||||||
|
$message = sprintf('Unknown filter: %s', $filterName);
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilterName()
|
||||||
|
{
|
||||||
|
return $this->filterName;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
vendor/mustache/mustache/src/Exception/UnknownHelperException.php
vendored
Normal file
38
vendor/mustache/mustache/src/Exception/UnknownHelperException.php
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown helper exception.
|
||||||
|
*/
|
||||||
|
class UnknownHelperException extends InvalidArgumentException implements Exception
|
||||||
|
{
|
||||||
|
protected $helperName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $helperName
|
||||||
|
* @param Exception $previous
|
||||||
|
*/
|
||||||
|
public function __construct($helperName, $previous = null)
|
||||||
|
{
|
||||||
|
$this->helperName = $helperName;
|
||||||
|
$message = sprintf('Unknown helper: %s', $helperName);
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHelperName()
|
||||||
|
{
|
||||||
|
return $this->helperName;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
vendor/mustache/mustache/src/Exception/UnknownTemplateException.php
vendored
Normal file
38
vendor/mustache/mustache/src/Exception/UnknownTemplateException.php
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Exception;
|
||||||
|
|
||||||
|
use Mustache\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown template exception.
|
||||||
|
*/
|
||||||
|
class UnknownTemplateException extends InvalidArgumentException implements Exception
|
||||||
|
{
|
||||||
|
protected $templateName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $templateName
|
||||||
|
* @param Exception $previous
|
||||||
|
*/
|
||||||
|
public function __construct($templateName, $previous = null)
|
||||||
|
{
|
||||||
|
$this->templateName = $templateName;
|
||||||
|
$message = sprintf('Unknown template: %s', $templateName);
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateName()
|
||||||
|
{
|
||||||
|
return $this->templateName;
|
||||||
|
}
|
||||||
|
}
|
||||||
177
vendor/mustache/mustache/src/HelperCollection.php
vendored
Normal file
177
vendor/mustache/mustache/src/HelperCollection.php
vendored
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\UnknownHelperException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of helpers for a Mustache instance.
|
||||||
|
*/
|
||||||
|
class HelperCollection
|
||||||
|
{
|
||||||
|
private $helpers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper Collection constructor.
|
||||||
|
*
|
||||||
|
* Optionally accepts an array (or \Traversable) of `$name => $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);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
vendor/mustache/mustache/src/LambdaHelper.php
vendored
Normal file
96
vendor/mustache/mustache/src/LambdaHelper.php
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Lambda Helper.
|
||||||
|
*
|
||||||
|
* Passed as the second argument to section lambdas (higher order sections),
|
||||||
|
* giving them access to a `render` method for rendering a string with the
|
||||||
|
* current context.
|
||||||
|
*/
|
||||||
|
class LambdaHelper
|
||||||
|
{
|
||||||
|
private $mustache;
|
||||||
|
private $context;
|
||||||
|
private $delims;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Lambda Helper constructor.
|
||||||
|
*
|
||||||
|
* @param Engine $mustache Mustache engine instance
|
||||||
|
* @param Context $context Rendering context
|
||||||
|
* @param string $delims Optional custom delimiters, in the format `{{= <% %> =}}`. (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);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
vendor/mustache/mustache/src/Loader.php
vendored
Normal file
28
vendor/mustache/mustache/src/Loader.php
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
|
||||||
|
interface Loader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Load a Template by name.
|
||||||
|
*
|
||||||
|
* @throws UnknownTemplateException If a template file is not found
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return string|Source Mustache Template source
|
||||||
|
*/
|
||||||
|
public function load($name);
|
||||||
|
}
|
||||||
82
vendor/mustache/mustache/src/Loader/ArrayLoader.php
vendored
Normal file
82
vendor/mustache/mustache/src/Loader/ArrayLoader.php
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template array Loader implementation.
|
||||||
|
*
|
||||||
|
* An ArrayLoader instance loads Mustache Template source by name from an initial array:
|
||||||
|
*
|
||||||
|
* $loader = new ArrayLoader(
|
||||||
|
* 'foo' => '{{ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
72
vendor/mustache/mustache/src/Loader/CascadingLoader.php
vendored
Normal file
72
vendor/mustache/mustache/src/Loader/CascadingLoader.php
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Mustache Template cascading loader implementation, which delegates to other
|
||||||
|
* Loader instances.
|
||||||
|
*/
|
||||||
|
class CascadingLoader implements Loader
|
||||||
|
{
|
||||||
|
private $loaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a CascadingLoader with an array of loaders.
|
||||||
|
*
|
||||||
|
* $loader = new CascadingLoader([
|
||||||
|
* new InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__),
|
||||||
|
* new FilesystemLoader(__DIR__.'/templates')
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* @param Loader[] $loaders
|
||||||
|
*/
|
||||||
|
public function __construct(array $loaders = [])
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
141
vendor/mustache/mustache/src/Loader/FilesystemLoader.php
vendored
Normal file
141
vendor/mustache/mustache/src/Loader/FilesystemLoader.php
vendored
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template filesystem Loader implementation.
|
||||||
|
*
|
||||||
|
* A FilesystemLoader instance loads Mustache Template source from the filesystem by name:
|
||||||
|
*
|
||||||
|
* $loader = new FilesystemLoader(__DIR__.'/views');
|
||||||
|
* $tpl = $loader->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
vendor/mustache/mustache/src/Loader/InlineLoader.php
vendored
Normal file
129
vendor/mustache/mustache/src/Loader/InlineLoader.php
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Mustache Template loader for inline templates.
|
||||||
|
*
|
||||||
|
* With the InlineLoader, templates can be defined at the end of any PHP source
|
||||||
|
* file:
|
||||||
|
*
|
||||||
|
* $loader = new InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
|
||||||
|
* $hello = $loader->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
vendor/mustache/mustache/src/Loader/MutableLoader.php
vendored
Normal file
28
vendor/mustache/mustache/src/Loader/MutableLoader.php
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
interface MutableLoader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Set an associative array of Template sources for this loader.
|
||||||
|
*/
|
||||||
|
public function setTemplates(array $templates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Template source by name.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $template Mustache Template source
|
||||||
|
*/
|
||||||
|
public function setTemplate($name, $template);
|
||||||
|
}
|
||||||
93
vendor/mustache/mustache/src/Loader/ProductionFilesystemLoader.php
vendored
Normal file
93
vendor/mustache/mustache/src/Loader/ProductionFilesystemLoader.php
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Exception\UnknownTemplateException;
|
||||||
|
use Mustache\Source;
|
||||||
|
use Mustache\Source\FilesystemSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template production filesystem Loader implementation.
|
||||||
|
*
|
||||||
|
* A production-ready FilesystemLoader, which doesn't require reading a file if it already exists in the template cache.
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
class ProductionFilesystemLoader extends FilesystemLoader
|
||||||
|
{
|
||||||
|
private $statProps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache production 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',
|
||||||
|
* '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);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
vendor/mustache/mustache/src/Loader/StringLoader.php
vendored
Normal file
43
vendor/mustache/mustache/src/Loader/StringLoader.php
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Loader;
|
||||||
|
|
||||||
|
use Mustache\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template string Loader implementation.
|
||||||
|
*
|
||||||
|
* A StringLoader instance is essentially a noop. It simply passes the 'name' argument straight through:
|
||||||
|
*
|
||||||
|
* $loader = new StringLoader;
|
||||||
|
* $tpl = $loader->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
vendor/mustache/mustache/src/Logger.php
vendored
Normal file
102
vendor/mustache/mustache/src/Logger.php
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
interface Logger
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Psr\Log compatible log levels.
|
||||||
|
*/
|
||||||
|
const EMERGENCY = 'emergency';
|
||||||
|
const ALERT = 'alert';
|
||||||
|
const CRITICAL = 'critical';
|
||||||
|
const ERROR = 'error';
|
||||||
|
const WARNING = 'warning';
|
||||||
|
const NOTICE = 'notice';
|
||||||
|
const INFO = 'info';
|
||||||
|
const DEBUG = 'debug';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System is unusable.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function emergency($message, array $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 = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Critical conditions.
|
||||||
|
*
|
||||||
|
* Example: Application component unavailable, unexpected exception.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function critical($message, array $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 = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal but significant events.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function notice($message, array $context = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interesting events.
|
||||||
|
*
|
||||||
|
* Example: User logs in, SQL logs.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function info($message, array $context = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detailed debug information.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function debug($message, array $context = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs with an arbitrary level.
|
||||||
|
*
|
||||||
|
* @param mixed $level
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function log($level, $message, array $context = []);
|
||||||
|
}
|
||||||
117
vendor/mustache/mustache/src/Logger/AbstractLogger.php
vendored
Normal file
117
vendor/mustache/mustache/src/Logger/AbstractLogger.php
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Logger;
|
||||||
|
|
||||||
|
use Mustache\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple Logger implementation that other Loggers can inherit from.
|
||||||
|
*
|
||||||
|
* This is identical to the Psr\Log\AbstractLogger.
|
||||||
|
*
|
||||||
|
* It simply delegates all log-level-specific methods to the `log` method to
|
||||||
|
* reduce boilerplate code that a simple Logger that does the same thing with
|
||||||
|
* messages regardless of the error level has to implement.
|
||||||
|
*/
|
||||||
|
abstract class AbstractLogger implements Logger
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* System is unusable.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
*/
|
||||||
|
public function emergency($message, array $context = [])
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
199
vendor/mustache/mustache/src/Logger/StreamLogger.php
vendored
Normal file
199
vendor/mustache/mustache/src/Logger/StreamLogger.php
vendored
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Logger;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\LogicException;
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Mustache Stream Logger.
|
||||||
|
*
|
||||||
|
* The Stream Logger wraps a file resource instance (such as a stream) or a
|
||||||
|
* stream URL. All log messages over the threshold level will be appended to
|
||||||
|
* this stream.
|
||||||
|
*
|
||||||
|
* Hint: Try `php://stderr` for your stream URL.
|
||||||
|
*/
|
||||||
|
class StreamLogger extends AbstractLogger
|
||||||
|
{
|
||||||
|
protected static $levels = [
|
||||||
|
self::DEBUG => 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
392
vendor/mustache/mustache/src/Parser.php
vendored
Normal file
392
vendor/mustache/mustache/src/Parser.php
vendored
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\SyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Parser class.
|
||||||
|
*
|
||||||
|
* This class is responsible for turning a set of Mustache tokens into a parse tree.
|
||||||
|
*/
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
private $lineNum;
|
||||||
|
private $lineTokens;
|
||||||
|
private $pragmas;
|
||||||
|
private $defaultPragmas = [];
|
||||||
|
|
||||||
|
// Optional Mustache specs
|
||||||
|
private $dynamicNames = true;
|
||||||
|
private $inheritance = true;
|
||||||
|
|
||||||
|
private $pragmaFilters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an array of Mustache tokens and convert them into a parse tree.
|
||||||
|
*
|
||||||
|
* @param array $tokens Set of Mustache tokens
|
||||||
|
*
|
||||||
|
* @return array Mustache token parse tree
|
||||||
|
*/
|
||||||
|
public function parse(array $tokens = [])
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
vendor/mustache/mustache/src/RenderedString.php
vendored
Normal file
51
vendor/mustache/mustache/src/RenderedString.php
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing a rendered string in Mustache.
|
||||||
|
*
|
||||||
|
* This is primarily used to prevent re-rendering of strings that have already
|
||||||
|
* been processed in higher-order sections.
|
||||||
|
*
|
||||||
|
* @see LambdaHelper::render()
|
||||||
|
* @see LambdaHelper::preventRender()
|
||||||
|
*/
|
||||||
|
class RenderedString
|
||||||
|
{
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RenderedString constructor.
|
||||||
|
*
|
||||||
|
* @param string $value The rendered string value
|
||||||
|
*/
|
||||||
|
public function __construct($value)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
vendor/mustache/mustache/src/Source.php
vendored
Normal file
39
vendor/mustache/mustache/src/Source.php
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
interface Source
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the Source key (used to generate the compiled class name).
|
||||||
|
*
|
||||||
|
* This must return a distinct key for each template source. For example, an
|
||||||
|
* MD5 hash of the template contents would probably do the trick. The
|
||||||
|
* ProductionFilesystemLoader uses mtime and file path. If your production
|
||||||
|
* source directory is under version control, you could use the current Git
|
||||||
|
* rev and the file path...
|
||||||
|
*
|
||||||
|
* @throws RuntimeException when a source file cannot be read
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the template Source.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException when a source file cannot be read
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSource();
|
||||||
|
}
|
||||||
81
vendor/mustache/mustache/src/Source/FilesystemSource.php
vendored
Normal file
81
vendor/mustache/mustache/src/Source/FilesystemSource.php
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache\Source;
|
||||||
|
|
||||||
|
use Mustache\Exception\RuntimeException;
|
||||||
|
use Mustache\Source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache template Filesystem Source.
|
||||||
|
*
|
||||||
|
* This template Source uses stat() to generate the Source key, so that using
|
||||||
|
* pre-compiled templates doesn't require hitting the disk to read the source.
|
||||||
|
* It is more suitable for production use, and is used by default in the
|
||||||
|
* ProductionFilesystemLoader.
|
||||||
|
*/
|
||||||
|
class FilesystemSource implements Source
|
||||||
|
{
|
||||||
|
private $fileName;
|
||||||
|
private $statProps;
|
||||||
|
private $stat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filesystem Source constructor.
|
||||||
|
*
|
||||||
|
* @param string $fileName
|
||||||
|
*/
|
||||||
|
public function __construct($fileName, array $statProps)
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
193
vendor/mustache/mustache/src/Template.php
vendored
Normal file
193
vendor/mustache/mustache/src/Template.php
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract Mustache Template class.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
abstract class Template
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Engine
|
||||||
|
*/
|
||||||
|
protected $mustache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $strictCallables = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $lambdas = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Template constructor.
|
||||||
|
*/
|
||||||
|
public function __construct(Engine $mustache)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
412
vendor/mustache/mustache/src/Tokenizer.php
vendored
Normal file
412
vendor/mustache/mustache/src/Tokenizer.php
vendored
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Mustache;
|
||||||
|
|
||||||
|
use Mustache\Exception\InvalidArgumentException;
|
||||||
|
use Mustache\Exception\SyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mustache Tokenizer class.
|
||||||
|
*
|
||||||
|
* This class is responsible for turning raw template source into a set of Mustache tokens.
|
||||||
|
*/
|
||||||
|
class Tokenizer
|
||||||
|
{
|
||||||
|
// Finite state machine states
|
||||||
|
const IN_TEXT = 0;
|
||||||
|
const IN_TAG_TYPE = 1;
|
||||||
|
const IN_TAG = 2;
|
||||||
|
|
||||||
|
// Token types
|
||||||
|
const T_SECTION = '#';
|
||||||
|
const T_INVERTED = '^';
|
||||||
|
const T_END_SECTION = '/';
|
||||||
|
const T_COMMENT = '!';
|
||||||
|
const T_PARTIAL = '>';
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
||||||
282
vendor/mustache/mustache/src/compat.php
vendored
Normal file
282
vendor/mustache/mustache/src/compat.php
vendored
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2010-2025 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class_alias(\Mustache\Cache::class, \Mustache_Cache::class);
|
||||||
|
class_alias(\Mustache\Cache\AbstractCache::class, \Mustache_Cache_AbstractCache::class);
|
||||||
|
class_alias(\Mustache\Cache\FilesystemCache::class, \Mustache_Cache_FilesystemCache::class);
|
||||||
|
class_alias(\Mustache\Cache\NoopCache::class, \Mustache_Cache_NoopCache::class);
|
||||||
|
class_alias(\Mustache\Compiler::class, \Mustache_Compiler::class);
|
||||||
|
class_alias(\Mustache\Context::class, \Mustache_Context::class);
|
||||||
|
class_alias(\Mustache\Engine::class, \Mustache_Engine::class);
|
||||||
|
class_alias(\Mustache\Exception::class, \Mustache_Exception::class);
|
||||||
|
class_alias(\Mustache\Exception\InvalidArgumentException::class, \Mustache_Exception_InvalidArgumentException::class);
|
||||||
|
class_alias(\Mustache\Exception\LogicException::class, \Mustache_Exception_LogicException::class);
|
||||||
|
class_alias(\Mustache\Exception\RuntimeException::class, \Mustache_Exception_RuntimeException::class);
|
||||||
|
class_alias(\Mustache\Exception\SyntaxException::class, \Mustache_Exception_SyntaxException::class);
|
||||||
|
class_alias(\Mustache\Exception\UnknownFilterException::class, \Mustache_Exception_UnknownFilterException::class);
|
||||||
|
class_alias(\Mustache\Exception\UnknownHelperException::class, \Mustache_Exception_UnknownHelperException::class);
|
||||||
|
class_alias(\Mustache\Exception\UnknownTemplateException::class, \Mustache_Exception_UnknownTemplateException::class);
|
||||||
|
class_alias(\Mustache\HelperCollection::class, \Mustache_HelperCollection::class);
|
||||||
|
class_alias(\Mustache\LambdaHelper::class, \Mustache_LambdaHelper::class);
|
||||||
|
class_alias(\Mustache\Loader::class, \Mustache_Loader::class);
|
||||||
|
class_alias(\Mustache\Loader\ArrayLoader::class, \Mustache_Loader_ArrayLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\CascadingLoader::class, \Mustache_Loader_CascadingLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\FilesystemLoader::class, \Mustache_Loader_FilesystemLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\InlineLoader::class, \Mustache_Loader_InlineLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\MutableLoader::class, \Mustache_Loader_MutableLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\ProductionFilesystemLoader::class, \Mustache_Loader_ProductionFilesystemLoader::class);
|
||||||
|
class_alias(\Mustache\Loader\StringLoader::class, \Mustache_Loader_StringLoader::class);
|
||||||
|
class_alias(\Mustache\Logger::class, \Mustache_Logger::class);
|
||||||
|
class_alias(\Mustache\Logger\AbstractLogger::class, \Mustache_Logger_AbstractLogger::class);
|
||||||
|
class_alias(\Mustache\Logger\StreamLogger::class, \Mustache_Logger_StreamLogger::class);
|
||||||
|
class_alias(\Mustache\Parser::class, \Mustache_Parser::class);
|
||||||
|
class_alias(\Mustache\Source::class, \Mustache_Source::class);
|
||||||
|
class_alias(\Mustache\Source\FilesystemSource::class, \Mustache_Source_FilesystemSource::class);
|
||||||
|
class_alias(\Mustache\Template::class, \Mustache_Template::class);
|
||||||
|
class_alias(\Mustache\Tokenizer::class, \Mustache_Tokenizer::class);
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Engine::class)) {
|
||||||
|
/** @deprecated use Mustache\Engine */
|
||||||
|
class Mustache_Engine extends \Mustache\Engine
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Cache::class)) {
|
||||||
|
/** @deprecated use Mustache\Cache */
|
||||||
|
interface Mustache_Cache extends \Mustache\Cache
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Cache_AbstractCache::class)) {
|
||||||
|
/** @deprecated use Mustache\Cache\AbstractCache */
|
||||||
|
abstract class Mustache_Cache_AbstractCache extends \Mustache\Cache\AbstractCache
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Cache_FilesystemCache::class)) {
|
||||||
|
/** @deprecated use Mustache\Cache\FilesystemCache */
|
||||||
|
class Mustache_Cache_FilesystemCache extends \Mustache\Cache\FilesystemCache
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Cache_NoopCache::class)) {
|
||||||
|
/** @deprecated use Mustache\Cache\NoopCache */
|
||||||
|
class Mustache_Cache_NoopCache extends \Mustache\Cache\NoopCache
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Compiler::class)) {
|
||||||
|
/** @deprecated use Mustache\Compiler */
|
||||||
|
class Mustache_Compiler extends \Mustache\Compiler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Context::class)) {
|
||||||
|
/** @deprecated use Mustache\Context */
|
||||||
|
class Mustache_Context extends \Mustache\Context
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Engine::class)) {
|
||||||
|
/** @deprecated use Mustache\Engine */
|
||||||
|
class Mustache_Engine extends \Mustache\Engine
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Exception::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception */
|
||||||
|
interface Mustache_Exception extends \Mustache\Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_InvalidArgumentException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\InvalidArgumentException */
|
||||||
|
class Mustache_Exception_InvalidArgumentException extends \Mustache\Exception\InvalidArgumentException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_LogicException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\LogicException */
|
||||||
|
class Mustache_Exception_LogicException extends \Mustache\Exception\LogicException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_RuntimeException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\RuntimeException */
|
||||||
|
class Mustache_Exception_RuntimeException extends \Mustache\Exception\RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_SyntaxException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\SyntaxException */
|
||||||
|
class Mustache_Exception_SyntaxException extends \Mustache\Exception\SyntaxException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_UnknownFilterException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\UnknownFilterException */
|
||||||
|
class Mustache_Exception_UnknownFilterException extends \Mustache\Exception\UnknownFilterException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_UnknownHelperException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\UnknownHelperException */
|
||||||
|
class Mustache_Exception_UnknownHelperException extends \Mustache\Exception\UnknownHelperException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Exception_UnknownTemplateException::class)) {
|
||||||
|
/** @deprecated use Mustache\Exception\UnknownTemplateException */
|
||||||
|
class Mustache_Exception_UnknownTemplateException extends \Mustache\Exception\UnknownTemplateException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_HelperCollection::class)) {
|
||||||
|
/** @deprecated use Mustache\HelperCollection */
|
||||||
|
class Mustache_HelperCollection extends \Mustache\HelperCollection
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_LambdaHelper::class)) {
|
||||||
|
/** @deprecated use Mustache\LambdaHelper */
|
||||||
|
class Mustache_LambdaHelper extends \Mustache\LambdaHelper
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Loader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader */
|
||||||
|
interface Mustache_Loader extends \Mustache\Loader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_ArrayLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\ArrayLoader */
|
||||||
|
class Mustache_Loader_ArrayLoader extends \Mustache\Loader\ArrayLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_CascadingLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\CascadingLoader */
|
||||||
|
class Mustache_Loader_CascadingLoader extends \Mustache\Loader\CascadingLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_FilesystemLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\FilesystemLoader */
|
||||||
|
class Mustache_Loader_FilesystemLoader extends \Mustache\Loader\FilesystemLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_InlineLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\InlineLoader */
|
||||||
|
class Mustache_Loader_InlineLoader extends \Mustache\Loader\InlineLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Loader_MutableLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\MutableLoader */
|
||||||
|
interface Mustache_Loader_MutableLoader extends \Mustache\Loader\MutableLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_ProductionFilesystemLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\ProductionFilesystemLoader */
|
||||||
|
class Mustache_Loader_ProductionFilesystemLoader extends \Mustache\Loader\ProductionFilesystemLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Loader_StringLoader::class)) {
|
||||||
|
/** @deprecated use Mustache\Loader\StringLoader */
|
||||||
|
class Mustache_Loader_StringLoader extends \Mustache\Loader\StringLoader
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Logger::class)) {
|
||||||
|
/** @deprecated use Mustache\Logger */
|
||||||
|
interface Mustache_Logger extends \Mustache\Logger
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Logger_AbstractLogger::class)) {
|
||||||
|
/** @deprecated use Mustache\Logger\AbstractLogger */
|
||||||
|
abstract class Mustache_Logger_AbstractLogger extends \Mustache\Logger\AbstractLogger
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Logger_StreamLogger::class)) {
|
||||||
|
/** @deprecated use Mustache\Logger\StreamLogger */
|
||||||
|
class Mustache_Logger_StreamLogger extends \Mustache\Logger\StreamLogger
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Parser::class)) {
|
||||||
|
/** @deprecated use Mustache\Parser */
|
||||||
|
class Mustache_Parser extends \Mustache\Parser
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interface_exists(\Mustache_Source::class)) {
|
||||||
|
/** @deprecated use Mustache\Source */
|
||||||
|
interface Mustache_Source extends \Mustache\Source
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Source_FilesystemSource::class)) {
|
||||||
|
/** @deprecated use Mustache\Source\FilesystemSource */
|
||||||
|
class Mustache_Source_FilesystemSource extends \Mustache\Source\FilesystemSource
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Template::class)) {
|
||||||
|
/** @deprecated use Mustache\Template */
|
||||||
|
abstract class Mustache_Template extends \Mustache\Template
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_exists(\Mustache_Tokenizer::class)) {
|
||||||
|
/** @deprecated use Mustache\Tokenizer */
|
||||||
|
class Mustache_Tokenizer extends \Mustache\Tokenizer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user