commit 13c4e575bcffedb47db66d87b74509f41277bea0
parent 9d74e8013739a2c92a0bfd7f94e4c9d4e13bbd83
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Thu, 21 Dec 2023 14:58:52 +0100
Merge branch 'main' of https://github.com/datenstrom/yellow
Diffstat:
50 files changed, 6865 insertions(+), 7282 deletions(-)
diff --git a/.gitattributes b/.gitattributes
@@ -1,8 +1,5 @@
-.gitignore export-ignore
+.github export-ignore
.gitattributes export-ignore
-.travis.yml export-ignore
-CONTRIBUTING.md export-ignore
LICENSE.md export-ignore
-README-de.md export-ignore
-README-sv.md export-ignore
-README.md export-ignore
+README*.md export-ignore
+SCREENSHOT*.png export-ignore
diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml
@@ -0,0 +1,51 @@
+# Datenstrom Yellow system tests
+
+name: System tests
+on: [push, pull_request]
+jobs:
+ extension-tests:
+ name: Extensions
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: latest
+ extensions: curl, gd, mbstring, zip
+ ini-file: development
+ coverage: none
+ tools: none
+ - name: Set up problem matcher
+ run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+ - name: Set up test environment
+ run: |
+ php yellow.php skip installation maximal
+ echo "Generate:exclude" > content/contact/page.md
+ echo "Generate:exclude" > content/search/page.md
+ - name: Run tests
+ run: php yellow.php generate tests
+ php-tests:
+ name: PHP ${{ matrix.php }}
+ strategy:
+ matrix:
+ php: [8.3, 8.2, 8.1, 8.0, 7.4, 7.3, 7.2, 7.1, 7.0]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: curl, gd, mbstring, zip
+ ini-file: development
+ coverage: none
+ tools: none
+ - name: Set up problem matcher
+ run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+ - name: Set up test environment
+ run: php yellow.php skip installation minimal
+ - name: Run tests
+ run: php yellow.php generate tests
diff --git a/.gitignore b/.gitignore
@@ -1,2 +0,0 @@
-.DS_Store
-*.min.*
diff --git a/.htaccess b/.htaccess
@@ -1,7 +1,7 @@
<IfModule mod_rewrite.c>
RewriteEngine on
DirectoryIndex index.html yellow.php
-RewriteRule ^(cache|content|system)/ error [L]
+RewriteRule ^(content|system)/ error [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ yellow.php [L]
diff --git a/.travis.yml b/.travis.yml
@@ -1,15 +0,0 @@
-# Datenstrom Yellow tests, https://travis-ci.org/datenstrom
-
-language: php
-php:
- - 7.4
- - 7.3
- - 7.2
- - 7.1
- - 7.0
- - 5.6
-before_script:
- - echo "CoreStaticUrl:http://website/" >> system/settings/system.ini
- - php yellow.php about
-script:
- - php yellow.php build test
diff --git a/content/1-home/page.md b/content/1-home/page.md
@@ -28,5 +28,3 @@ Checkout what I do except typesetting: [design](design), [program](program),
appear here someday. Hope you like what you see.
Want to contact me? Direct your mail to szymon.mikulicz at posteo.net
-
-
diff --git a/content/9-about/page.md b/content/9-about/page.md
@@ -0,0 +1,10 @@
+---
+Title: About
+---
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
+labore et dolore magna pizza. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
+nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit
+esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
+in culpa qui officia deserunt mollit anim id est laborum.
+
+[Made with Datenstrom Yellow](https://datenstrom.se/yellow/).
diff --git a/media/downloads/yellow.pdf b/media/downloads/yellow.pdf
Binary files differ.
diff --git a/media/images/picture.jpg b/media/images/photo.jpg
Binary files differ.
diff --git a/media/thumbnails/picture-100x40.jpg b/media/thumbnails/photo-100x40.jpg
Binary files differ.
diff --git a/robots.txt b/robots.txt
@@ -1,5 +1,5 @@
User-agent: *
Disallow: /harming/humans
Disallow: /harming/machines
-Disallow: /risking/own/existence
+Disallow: /risking/your/own/existence
Disallow: /edit/
diff --git a/system/extensions/bundle.php b/system/extensions/bundle.php
@@ -1,1962 +0,0 @@
-<?php
-// Bundle extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/bundle
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-class YellowBundle {
- const VERSION = "0.8.12";
- const TYPE = "feature";
- public $yellow; //access to API
-
- // Handle initialisation
- public function onLoad($yellow) {
- $this->yellow = $yellow;
- }
-
- // Handle page output data
- public function onParsePageOutput($page, $text) {
- $output = null;
- if ($text && preg_match("/^(.*<head>[\r\n]+)(.*)(<\/head>.*)$/s", $text, $matches)) {
- $output = $matches[1].$this->normaliseHead($matches[2]).$matches[3];
- }
- return $output;
- }
-
- // Handle command
- public function onCommand($command, $text) {
- switch ($command) {
- case "clean": $statusCode = $this->processCommandClean($command, $text); break;
- default: $statusCode = 0;
- }
- return $statusCode;
- }
-
- // Process command to clean bundles
- public function processCommandClean($command, $text) {
- $statusCode = 0;
- if ($command=="clean" && $text=="all") {
- $path = $this->yellow->system->get("coreResourceDirectory");
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/bundle-.*/", false, false) as $entry) {
- if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
- }
- if ($statusCode==500) echo "ERROR cleaning bundles: Can't delete files in directory '$path'!\n";
- }
- return $statusCode;
- }
-
- // Normalise page head
- public function normaliseHead($text) {
- $dataMeta = $dataLink = $dataCss = $dataScriptDefer = $dataScriptNow = $dataOther = array();
- foreach ($this->yellow->toolbox->getTextLines($text) as $line) {
- if (preg_match("/^<meta (.*?)>$/i", $line) || preg_match("/^<title>(.*?)<\/title>$/i", $line)) {
- array_push($dataMeta, $line);
- } elseif (preg_match("/^<link (.*?)href=\"([^\"]+)\"(.*?)>$/i", $line, $matches)) {
- if (preg_match("/\"stylesheet\"/i", $line)) {
- if (!isset($dataCss[$matches[2]])) $dataCss[$matches[2]] = $line;
- } else {
- array_push($dataLink, $line);
- }
- } elseif (preg_match("/^<script (.*?)src=\"([^\"]+)\"(.*?)><\/script>$/i", $line, $matches)) {
- if (preg_match("/\"defer\"/i", $line)) {
- if (!isset($dataScriptDefer[$matches[2]])) $dataScriptDefer[$matches[2]] = $line;
- } else {
- if (!isset($dataScriptNow[$matches[2]])) $dataScriptNow[$matches[2]] = $line;
- }
- } else {
- array_push($dataOther, $line);
- }
- }
- if (!defined("DEBUG") || DEBUG==0) {
- $dataCss = $this->processBundle($dataCss, "css");
- $dataScriptDefer = $this->processBundle($dataScriptDefer, "js", "defer");
- $dataScriptNow = $this->processBundle($dataScriptNow, "js");
- }
- $output = implode($dataMeta).implode($dataLink).implode($dataCss).
- implode($dataScriptDefer).implode($dataScriptNow).implode($dataOther);
- return $output;
- }
-
- // Process bundle, create file on demand
- public function processBundle($data, $type, $attribute = "") {
- $fileNames = array();
- $modified = 0;
- $scheme = $this->yellow->system->get("coreServerScheme");
- $address = $this->yellow->system->get("coreServerAddress");
- $base = $this->yellow->system->get("coreServerBase");
- foreach ($data as $key=>$value) {
- if (preg_match("/^\w+:/", $key)) continue;
- if (preg_match("/data-bundle=\"exclude\"/i", $value)) continue;
- if (substru($key, 0, strlenu($base))!=$base) continue;
- $location = substru($key, strlenu($base));
- $fileName = $this->yellow->lookup->findFileFromSystem($location);
- $modified = max($modified, $this->yellow->toolbox->getFileModified($fileName));
- if (is_readable($fileName)) {
- array_push($fileNames, $fileName);
- unset($data[$key]);
- }
- }
- if (!empty($fileNames)) {
- $autoVersioning = intval($modified/(60*60*24));
- $id = substru(md5($autoVersioning.$base.implode($fileNames)), 0, 10);
- $fileNameBundle = $this->yellow->system->get("coreResourceDirectory")."bundle-$id.min.$type";
- $locationBundle = $base.$this->yellow->system->get("coreResourceLocation")."bundle-$id.min.$type";
- $rawDataAttribute = $attribute=="defer" ? "defer=\"defer\" " : "";
- if ($type=="css") {
- $data[$locationBundle] = "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".htmlspecialchars($locationBundle)."\" />\n";
- } else {
- $data[$locationBundle] = "<script type=\"text/javascript\" ${rawDataAttribute}src=\"".htmlspecialchars($locationBundle)."\"></script>\n";
- }
- if ($this->yellow->toolbox->getFileModified($fileNameBundle)!=$modified) {
- $fileDataBundle = "";
- foreach ($fileNames as $fileName) {
- $fileData = $this->yellow->toolbox->readFile($fileName);
- $fileData = $this->processBundleConvert($scheme, $address, $base, $fileData, $fileName, $type);
- $fileData = $this->processBundleMinify($scheme, $address, $base, $fileData, $fileName, $type);
- if (substrb($fileData, 0, 3)=="\xEF\xBB\xBF") $fileData = substrb($fileData, 3);
- if (substrb($fileData, 0, 13)=="\"use strict\";" || substrb($fileData, 0, 13)=="'use strict';") $fileData = substrb($fileData, 13);
- if (!empty($fileDataBundle)) $fileDataBundle .= "\n\n";
- $fileDataBundle .= "/* ".basename($fileName)." */\n";
- $fileDataBundle .= $fileData;
- }
- if (is_file($fileNameBundle)) $this->yellow->toolbox->deleteFile($fileNameBundle);
- if (!$this->yellow->toolbox->createFile($fileNameBundle, $fileDataBundle) ||
- !$this->yellow->toolbox->modifyFile($fileNameBundle, $modified)) {
- $this->yellow->page->error(500, "Can't write file '$fileNameBundle'!");
- }
- }
- }
- return $data;
- }
-
- // Process bundle, convert URLs
- public function processBundleConvert($scheme, $address, $base, $fileData, $fileName, $type) {
- if ($type=="css") {
- $extensionDirectoryLength = strlenu($this->yellow->system->get("coreExtensionDirectory"));
- if (substru($fileName, 0, $extensionDirectoryLength) == $this->yellow->system->get("coreExtensionDirectory")) {
- $base .= $this->yellow->system->get("coreExtensionLocation");
- } else {
- $base .= $this->yellow->system->get("coreResourceLocation");
- }
- $thisCompatible = $this;
- $callback = function ($matches) use ($thisCompatible, $scheme, $address, $base) {
- $url = $thisCompatible->yellow->lookup->normaliseUrl($scheme, $address, $base, $matches[1], false);
- $url = strreplaceu("$scheme://$address", "", $url);
- return "url(\"$url\")";
- };
- $fileData = preg_replace_callback("/url\([\'\"]?(.*?)[\'\"]?\)/", $callback, $fileData);
- }
- return $fileData;
- }
-
- // Process bundle, minify data
- public function processBundleMinify($scheme, $address, $base, $fileData, $fileName, $type) {
- $minifier = $type=="css" ? new MinifyCss() : new MinifyJavaScript();
- if (preg_match("/\.min/", $fileName)) $minifier = new MinifyBasic();
- $minifier->add($fileData);
- return $minifier->minify();
- }
-}
-
-/**
- * Abstract minifier class.
- *
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
- *
- * @package Minify
- * @author Matthias Mullie <minify@mullie.eu>
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
- * @license MIT License
- */
-abstract class Minify
-{
- /**
- * The data to be minified.
- *
- * @var string[]
- */
- protected $data = array();
-
- /**
- * Array of patterns to match.
- *
- * @var string[]
- */
- protected $patterns = array();
-
- /**
- * This array will hold content of strings and regular expressions that have
- * been extracted from the JS source code, so we can reliably match "code",
- * without having to worry about potential "code-like" characters inside.
- *
- * @var string[]
- */
- public $extracted = array();
-
- /**
- * Init the minify class - optionally, code may be passed along already.
- */
- public function __construct(/* $data = null, ... */)
- {
- // it's possible to add the source through the constructor as well ;)
- if (func_num_args()) {
- call_user_func_array(array($this, 'add'), func_get_args());
- }
- }
-
- /**
- * Add a file or straight-up code to be minified.
- *
- * @param string|string[] $data
- *
- * @return static
- */
- public function add($data /* $data = null, ... */)
- {
- // bogus "usage" of parameter $data: scrutinizer warns this variable is
- // not used (we're using func_get_args instead to support overloading),
- // but it still needs to be defined because it makes no sense to have
- // this function without argument :)
- $args = array($data) + func_get_args();
-
- // this method can be overloaded
- foreach ($args as $data) {
- if (is_array($data)) {
- call_user_func_array(array($this, 'add'), $data);
- continue;
- }
-
- // redefine var
- $data = (string) $data;
-
- // load data
- $value = $this->load($data);
- $key = ($data != $value) ? $data : count($this->data);
-
- // replace CR linefeeds etc.
- // @see https://github.com/matthiasmullie/minify/pull/139
- $value = str_replace(array("\r\n", "\r"), "\n", $value);
-
- // store data
- $this->data[$key] = $value;
- }
-
- return $this;
- }
-
- /**
- * Minify the data & (optionally) saves it to a file.
- *
- * @param string[optional] $path Path to write the data to
- *
- * @return string The minified data
- */
- public function minify($path = null)
- {
- $content = $this->execute($path);
-
- // save to path
- if ($path !== null) {
- $this->save($content, $path);
- }
-
- return $content;
- }
-
- /**
- * Minify & gzip the data & (optionally) saves it to a file.
- *
- * @param string[optional] $path Path to write the data to
- * @param int[optional] $level Compression level, from 0 to 9
- *
- * @return string The minified & gzipped data
- */
- public function gzip($path = null, $level = 9)
- {
- $content = $this->execute($path);
- $content = gzencode($content, $level, FORCE_GZIP);
-
- // save to path
- if ($path !== null) {
- $this->save($content, $path);
- }
-
- return $content;
- }
-
- /**
- * Minify the data & write it to a CacheItemInterface object.
- *
- * @param CacheItemInterface $item Cache item to write the data to
- *
- * @return CacheItemInterface Cache item with the minifier data
- */
- public function cache(CacheItemInterface $item)
- {
- $content = $this->execute();
- $item->set($content);
-
- return $item;
- }
-
- /**
- * Minify the data.
- *
- * @param string[optional] $path Path to write the data to
- *
- * @return string The minified data
- */
- abstract public function execute($path = null);
-
- /**
- * Load data.
- *
- * @param string $data Either a path to a file or the content itself
- *
- * @return string
- */
- protected function load($data)
- {
- // check if the data is a file
- if ($this->canImportFile($data)) {
- $data = file_get_contents($data);
-
- // strip BOM, if any
- if (substr($data, 0, 3) == "\xef\xbb\xbf") {
- $data = substr($data, 3);
- }
- }
-
- return $data;
- }
-
- /**
- * Save to file.
- *
- * @param string $content The minified data
- * @param string $path The path to save the minified data to
- *
- * @throws IOException
- */
- protected function save($content, $path)
- {
- $handler = $this->openFileForWriting($path);
-
- $this->writeToFile($handler, $content);
-
- @fclose($handler);
- }
-
- /**
- * Register a pattern to execute against the source content.
- *
- * @param string $pattern PCRE pattern
- * @param string|callable $replacement Replacement value for matched pattern
- */
- protected function registerPattern($pattern, $replacement = '')
- {
- // study the pattern, we'll execute it more than once
- $pattern .= 'S';
-
- $this->patterns[] = array($pattern, $replacement);
- }
-
- /**
- * We can't "just" run some regular expressions against JavaScript: it's a
- * complex language. E.g. having an occurrence of // xyz would be a comment,
- * unless it's used within a string. Of you could have something that looks
- * like a 'string', but inside a comment.
- * The only way to accurately replace these pieces is to traverse the JS one
- * character at a time and try to find whatever starts first.
- *
- * @param string $content The content to replace patterns in
- *
- * @return string The (manipulated) content
- */
- protected function replace($content)
- {
- $processed = '';
- $positions = array_fill(0, count($this->patterns), -1);
- $matches = array();
-
- while ($content) {
- // find first match for all patterns
- foreach ($this->patterns as $i => $pattern) {
- list($pattern, $replacement) = $pattern;
-
- // we can safely ignore patterns for positions we've unset earlier,
- // because we know these won't show up anymore
- if (array_key_exists($i, $positions) == false) {
- continue;
- }
-
- // no need to re-run matches that are still in the part of the
- // content that hasn't been processed
- if ($positions[$i] >= 0) {
- continue;
- }
-
- $match = null;
- if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
- $matches[$i] = $match;
-
- // we'll store the match position as well; that way, we
- // don't have to redo all preg_matches after changing only
- // the first (we'll still know where those others are)
- $positions[$i] = $match[0][1];
- } else {
- // if the pattern couldn't be matched, there's no point in
- // executing it again in later runs on this same content;
- // ignore this one until we reach end of content
- unset($matches[$i], $positions[$i]);
- }
- }
-
- // no more matches to find: everything's been processed, break out
- if (!$matches) {
- $processed .= $content;
- break;
- }
-
- // see which of the patterns actually found the first thing (we'll
- // only want to execute that one, since we're unsure if what the
- // other found was not inside what the first found)
- $discardLength = min($positions);
- $firstPattern = array_search($discardLength, $positions);
- $match = $matches[$firstPattern][0][0];
-
- // execute the pattern that matches earliest in the content string
- list($pattern, $replacement) = $this->patterns[$firstPattern];
- $replacement = $this->replacePattern($pattern, $replacement, $content);
-
- // figure out which part of the string was unmatched; that's the
- // part we'll execute the patterns on again next
- $content = (string) substr($content, $discardLength);
- $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
-
- // move the replaced part to $processed and prepare $content to
- // again match batch of patterns against
- $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
- $content = $unmatched;
-
- // first match has been replaced & that content is to be left alone,
- // the next matches will start after this replacement, so we should
- // fix their offsets
- foreach ($positions as $i => $position) {
- $positions[$i] -= $discardLength + strlen($match);
- }
- }
-
- return $processed;
- }
-
- /**
- * This is where a pattern is matched against $content and the matches
- * are replaced by their respective value.
- * This function will be called plenty of times, where $content will always
- * move up 1 character.
- *
- * @param string $pattern Pattern to match
- * @param string|callable $replacement Replacement value
- * @param string $content Content to match pattern against
- *
- * @return string
- */
- protected function replacePattern($pattern, $replacement, $content)
- {
- if (is_callable($replacement)) {
- return preg_replace_callback($pattern, $replacement, $content, 1, $count);
- } else {
- return preg_replace($pattern, $replacement, $content, 1, $count);
- }
- }
-
- /**
- * Strings are a pattern we need to match, in order to ignore potential
- * code-like content inside them, but we just want all of the string
- * content to remain untouched.
- *
- * This method will replace all string content with simple STRING#
- * placeholder text, so we've rid all strings from characters that may be
- * misinterpreted. Original string content will be saved in $this->extracted
- * and after doing all other minifying, we can restore the original content
- * via restoreStrings().
- *
- * @param string[optional] $chars
- * @param string[optional] $placeholderPrefix
- */
- protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $callback = function ($match) use ($minifier, $placeholderPrefix) {
- // check the second index here, because the first always contains a quote
- if ($match[2] === '') {
- /*
- * Empty strings need no placeholder; they can't be confused for
- * anything else anyway.
- * But we still needed to match them, for the extraction routine
- * to skip over this particular string.
- */
- return $match[0];
- }
-
- $count = count($minifier->extracted);
- $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
- $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
-
- return $placeholder;
- };
-
- /*
- * The \\ messiness explained:
- * * Don't count ' or " as end-of-string if it's escaped (has backslash
- * in front of it)
- * * Unless... that backslash itself is escaped (another leading slash),
- * in which case it's no longer escaping the ' or "
- * * So there can be either no backslash, or an even number
- * * multiply all of that times 4, to account for the escaping that has
- * to be done to pass the backslash into the PHP string without it being
- * considered as escape-char (times 2) and to get it in the regex,
- * escaped (times 2)
- */
- $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
- }
-
- /**
- * This method will restore all extracted data (strings, regexes) that were
- * replaced with placeholder text in extract*(). The original content was
- * saved in $this->extracted.
- *
- * @param string $content
- *
- * @return string
- */
- protected function restoreExtractedData($content)
- {
- if (!$this->extracted) {
- // nothing was extracted, nothing to restore
- return $content;
- }
-
- $content = strtr($content, $this->extracted);
-
- $this->extracted = array();
-
- return $content;
- }
-
- /**
- * Check if the path is a regular file and can be read.
- *
- * @param string $path
- *
- * @return bool
- */
- protected function canImportFile($path)
- {
- $parsed = parse_url($path);
- if (
- // file is elsewhere
- isset($parsed['host']) ||
- // file responds to queries (may change, or need to bypass cache)
- isset($parsed['query'])
- ) {
- return false;
- }
-
- return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
- }
-
- /**
- * Attempts to open file specified by $path for writing.
- *
- * @param string $path The path to the file
- *
- * @return resource Specifier for the target file
- *
- * @throws IOException
- */
- protected function openFileForWriting($path)
- {
- if (($handler = @fopen($path, 'w')) === false) {
- throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
- }
-
- return $handler;
- }
-
- /**
- * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
- *
- * @param resource $handler The resource to write to
- * @param string $content The content to write
- * @param string $path The path to the file (for exception printing only)
- *
- * @throws IOException
- */
- protected function writeToFile($handler, $content, $path = '')
- {
- if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
- throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
- }
- }
-}
-
-class CSS extends Minify
-{
- /**
- * @var int maximum inport size in kB
- */
- protected $maxImportSize = 5;
-
- /**
- * @var string[] valid import extensions
- */
- protected $importExtensions = array(
- 'gif' => 'data:image/gif',
- 'png' => 'data:image/png',
- 'jpe' => 'data:image/jpeg',
- 'jpg' => 'data:image/jpeg',
- 'jpeg' => 'data:image/jpeg',
- 'svg' => 'data:image/svg+xml',
- 'woff' => 'data:application/x-font-woff',
- 'tif' => 'image/tiff',
- 'tiff' => 'image/tiff',
- 'xbm' => 'image/x-xbitmap',
- );
-
- /**
- * Set the maximum size if files to be imported.
- *
- * Files larger than this size (in kB) will not be imported into the CSS.
- * Importing files into the CSS as data-uri will save you some connections,
- * but we should only import relatively small decorative images so that our
- * CSS file doesn't get too bulky.
- *
- * @param int $size Size in kB
- */
- public function setMaxImportSize($size)
- {
- $this->maxImportSize = $size;
- }
-
- /**
- * Set the type of extensions to be imported into the CSS (to save network
- * connections).
- * Keys of the array should be the file extensions & respective values
- * should be the data type.
- *
- * @param string[] $extensions Array of file extensions
- */
- public function setImportExtensions(array $extensions)
- {
- $this->importExtensions = $extensions;
- }
-
- /**
- * Move any import statements to the top.
- *
- * @param string $content Nearly finished CSS content
- *
- * @return string
- */
- protected function moveImportsToTop($content)
- {
- if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
- // remove from content
- foreach ($matches[0] as $import) {
- $content = str_replace($import, '', $content);
- }
-
- // add to top
- $content = implode(';', $matches[2]).';'.trim($content, ';');
- }
-
- return $content;
- }
-
- /**
- * Combine CSS from import statements.
- *
- * @import's will be loaded and their content merged into the original file,
- * to save HTTP requests.
- *
- * @param string $source The file to combine imports for
- * @param string $content The CSS content to combine imports for
- * @param string[] $parents Parent paths, for circular reference checks
- *
- * @return string
- *
- * @throws FileImportException
- */
- protected function combineImports($source, $content, $parents)
- {
- $importRegexes = array(
- // @import url(xxx)
- '/
- # import statement
- @import
-
- # whitespace
- \s+
-
- # open url()
- url\(
-
- # (optional) open path enclosure
- (?P<quotes>["\']?)
-
- # fetch path
- (?P<path>.+?)
-
- # (optional) close path enclosure
- (?P=quotes)
-
- # close url()
- \)
-
- # (optional) trailing whitespace
- \s*
-
- # (optional) media statement(s)
- (?P<media>[^;]*)
-
- # (optional) trailing whitespace
- \s*
-
- # (optional) closing semi-colon
- ;?
-
- /ix',
-
- // @import 'xxx'
- '/
-
- # import statement
- @import
-
- # whitespace
- \s+
-
- # open path enclosure
- (?P<quotes>["\'])
-
- # fetch path
- (?P<path>.+?)
-
- # close path enclosure
- (?P=quotes)
-
- # (optional) trailing whitespace
- \s*
-
- # (optional) media statement(s)
- (?P<media>[^;]*)
-
- # (optional) trailing whitespace
- \s*
-
- # (optional) closing semi-colon
- ;?
-
- /ix',
- );
-
- // find all relative imports in css
- $matches = array();
- foreach ($importRegexes as $importRegex) {
- if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
- $matches = array_merge($matches, $regexMatches);
- }
- }
-
- $search = array();
- $replace = array();
-
- // loop the matches
- foreach ($matches as $match) {
- // get the path for the file that will be imported
- $importPath = dirname($source).'/'.$match['path'];
-
- // only replace the import with the content if we can grab the
- // content of the file
- if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
- continue;
- }
-
- // check if current file was not imported previously in the same
- // import chain.
- if (in_array($importPath, $parents)) {
- throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
- }
-
- // grab referenced file & minify it (which may include importing
- // yet other @import statements recursively)
- $minifier = new static($importPath);
- $minifier->setMaxImportSize($this->maxImportSize);
- $minifier->setImportExtensions($this->importExtensions);
- $importContent = $minifier->execute($source, $parents);
-
- // check if this is only valid for certain media
- if (!empty($match['media'])) {
- $importContent = '@media '.$match['media'].'{'.$importContent.'}';
- }
-
- // add to replacement array
- $search[] = $match[0];
- $replace[] = $importContent;
- }
-
- // replace the import statements
- return str_replace($search, $replace, $content);
- }
-
- /**
- * Import files into the CSS, base64-ized.
- *
- * @url(image.jpg) images will be loaded and their content merged into the
- * original file, to save HTTP requests.
- *
- * @param string $source The file to import files for
- * @param string $content The CSS content to import files for
- *
- * @return string
- */
- protected function importFiles($source, $content)
- {
- $regex = '/url\((["\']?)(.+?)\\1\)/i';
- if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
- $search = array();
- $replace = array();
-
- // loop the matches
- foreach ($matches as $match) {
- $extension = substr(strrchr($match[2], '.'), 1);
- if ($extension && !array_key_exists($extension, $this->importExtensions)) {
- continue;
- }
-
- // get the path for the file that will be imported
- $path = $match[2];
- $path = dirname($source).'/'.$path;
-
- // only replace the import with the content if we're able to get
- // the content of the file, and it's relatively small
- if ($this->canImportFile($path) && $this->canImportBySize($path)) {
- // grab content && base64-ize
- $importContent = $this->load($path);
- $importContent = base64_encode($importContent);
-
- // build replacement
- $search[] = $match[0];
- $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
- }
- }
-
- // replace the import statements
- $content = str_replace($search, $replace, $content);
- }
-
- return $content;
- }
-
- /**
- * Minify the data.
- * Perform CSS optimizations.
- *
- * @param string[optional] $path Path to write the data to
- * @param string[] $parents Parent paths, for circular reference checks
- *
- * @return string The minified data
- */
- public function execute($path = null, $parents = array())
- {
- $content = '';
-
- // loop CSS data (raw data and files)
- foreach ($this->data as $source => $css) {
- /*
- * Let's first take out strings & comments, since we can't just
- * remove whitespace anywhere. If whitespace occurs inside a string,
- * we should leave it alone. E.g.:
- * p { content: "a test" }
- */
- $this->extractStrings();
- $this->stripComments();
- $this->extractCalcs();
- $css = $this->replace($css);
-
- $css = $this->stripWhitespace($css);
- $css = $this->shortenColors($css);
- $css = $this->shortenZeroes($css);
- $css = $this->shortenFontWeights($css);
- $css = $this->stripEmptyTags($css);
-
- // restore the string we've extracted earlier
- $css = $this->restoreExtractedData($css);
-
- $source = is_int($source) ? '' : $source;
- $parents = $source ? array_merge($parents, array($source)) : $parents;
- $css = $this->combineImports($source, $css, $parents);
- $css = $this->importFiles($source, $css);
-
- /*
- * If we'll save to a new path, we'll have to fix the relative paths
- * to be relative no longer to the source file, but to the new path.
- * If we don't write to a file, fall back to same path so no
- * conversion happens (because we still want it to go through most
- * of the move code, which also addresses url() & @import syntax...)
- */
- $converter = $this->getPathConverter($source, $path ?: $source);
- $css = $this->move($converter, $css);
-
- // combine css
- $content .= $css;
- }
-
- $content = $this->moveImportsToTop($content);
-
- return $content;
- }
-
- /**
- * Moving a css file should update all relative urls.
- * Relative references (e.g. ../images/image.gif) in a certain css file,
- * will have to be updated when a file is being saved at another location
- * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
- *
- * @param ConverterInterface $converter Relative path converter
- * @param string $content The CSS content to update relative urls for
- *
- * @return string
- */
- protected function move(ConverterInterface $converter, $content)
- {
- /*
- * Relative path references will usually be enclosed by url(). @import
- * is an exception, where url() is not necessary around the path (but is
- * allowed).
- * This *could* be 1 regular expression, where both regular expressions
- * in this array are on different sides of a |. But we're using named
- * patterns in both regexes, the same name on both regexes. This is only
- * possible with a (?J) modifier, but that only works after a fairly
- * recent PCRE version. That's why I'm doing 2 separate regular
- * expressions & combining the matches after executing of both.
- */
- $relativeRegexes = array(
- // url(xxx)
- '/
- # open url()
- url\(
-
- \s*
-
- # open path enclosure
- (?P<quotes>["\'])?
-
- # fetch path
- (?P<path>.+?)
-
- # close path enclosure
- (?(quotes)(?P=quotes))
-
- \s*
-
- # close url()
- \)
-
- /ix',
-
- // @import "xxx"
- '/
- # import statement
- @import
-
- # whitespace
- \s+
-
- # we don\'t have to check for @import url(), because the
- # condition above will already catch these
-
- # open path enclosure
- (?P<quotes>["\'])
-
- # fetch path
- (?P<path>.+?)
-
- # close path enclosure
- (?P=quotes)
-
- /ix',
- );
-
- // find all relative urls in css
- $matches = array();
- foreach ($relativeRegexes as $relativeRegex) {
- if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
- $matches = array_merge($matches, $regexMatches);
- }
- }
-
- $search = array();
- $replace = array();
-
- // loop all urls
- foreach ($matches as $match) {
- // determine if it's a url() or an @import match
- $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
-
- $url = $match['path'];
- if ($this->canImportByPath($url)) {
- // attempting to interpret GET-params makes no sense, so let's discard them for awhile
- $params = strrchr($url, '?');
- $url = $params ? substr($url, 0, -strlen($params)) : $url;
-
- // fix relative url
- $url = $converter->convert($url);
-
- // now that the path has been converted, re-apply GET-params
- $url .= $params;
- }
-
- /*
- * Urls with control characters above 0x7e should be quoted.
- * According to Mozilla's parser, whitespace is only allowed at the
- * end of unquoted urls.
- * Urls with `)` (as could happen with data: uris) should also be
- * quoted to avoid being confused for the url() closing parentheses.
- * And urls with a # have also been reported to cause issues.
- * Urls with quotes inside should also remain escaped.
- *
- * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
- * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
- * @see https://github.com/matthiasmullie/minify/issues/193
- */
- $url = trim($url);
- if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
- $url = $match['quotes'] . $url . $match['quotes'];
- }
-
- // build replacement
- $search[] = $match[0];
- if ($type === 'url') {
- $replace[] = 'url('.$url.')';
- } elseif ($type === 'import') {
- $replace[] = '@import "'.$url.'"';
- }
- }
-
- // replace urls
- return str_replace($search, $replace, $content);
- }
-
- /**
- * Shorthand hex color codes.
- * #FF0000 -> #F00.
- *
- * @param string $content The CSS content to shorten the hex color codes for
- *
- * @return string
- */
- protected function shortenColors($content)
- {
- $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
-
- // remove alpha channel if it's pointless...
- $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content);
- $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content);
-
- $colors = array(
- // we can shorten some even more by replacing them with their color name
- '#F0FFFF' => 'azure',
- '#F5F5DC' => 'beige',
- '#A52A2A' => 'brown',
- '#FF7F50' => 'coral',
- '#FFD700' => 'gold',
- '#808080' => 'gray',
- '#008000' => 'green',
- '#4B0082' => 'indigo',
- '#FFFFF0' => 'ivory',
- '#F0E68C' => 'khaki',
- '#FAF0E6' => 'linen',
- '#800000' => 'maroon',
- '#000080' => 'navy',
- '#808000' => 'olive',
- '#CD853F' => 'peru',
- '#FFC0CB' => 'pink',
- '#DDA0DD' => 'plum',
- '#800080' => 'purple',
- '#F00' => 'red',
- '#FA8072' => 'salmon',
- '#A0522D' => 'sienna',
- '#C0C0C0' => 'silver',
- '#FFFAFA' => 'snow',
- '#D2B48C' => 'tan',
- '#FF6347' => 'tomato',
- '#EE82EE' => 'violet',
- '#F5DEB3' => 'wheat',
- // or the other way around
- 'WHITE' => '#fff',
- 'BLACK' => '#000',
- );
-
- return preg_replace_callback(
- '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i',
- function ($match) use ($colors) {
- return $colors[strtoupper($match[0])];
- },
- $content
- );
- }
-
- /**
- * Shorten CSS font weights.
- *
- * @param string $content The CSS content to shorten the font weights for
- *
- * @return string
- */
- protected function shortenFontWeights($content)
- {
- $weights = array(
- 'normal' => 400,
- 'bold' => 700,
- );
-
- $callback = function ($match) use ($weights) {
- return $match[1].$weights[$match[2]];
- };
-
- return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
- }
-
- /**
- * Shorthand 0 values to plain 0, instead of e.g. -0em.
- *
- * @param string $content The CSS content to shorten the zero values for
- *
- * @return string
- */
- protected function shortenZeroes($content)
- {
- // we don't want to strip units in `calc()` expressions:
- // `5px - 0px` is valid, but `5px - 0` is not
- // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
- // `10 * 0` is invalid
- // we've extracted calcs earlier, so we don't need to worry about this
-
- // reusable bits of code throughout these regexes:
- // before & after are used to make sure we don't match lose unintended
- // 0-like values (e.g. in #000, or in http://url/1.0)
- // units can be stripped from 0 values, or used to recognize non 0
- // values (where wa may be able to strip a .0 suffix)
- $before = '(?<=[:(, ])';
- $after = '(?=[ ,);}])';
- $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
-
- // strip units after zeroes (0px -> 0)
- // NOTE: it should be safe to remove all units for a 0 value, but in
- // practice, Webkit (especially Safari) seems to stumble over at least
- // 0%, potentially other units as well. Only stripping 'px' for now.
- // @see https://github.com/matthiasmullie/minify/issues/60
- $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
-
- // strip 0-digits (.0 -> 0)
- $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
- // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
- $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
- // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
- $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
- // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
- $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
-
- // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
- $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
-
- // IE doesn't seem to understand a unitless flex-basis value (correct -
- // it goes against the spec), so let's add it in again (make it `%`,
- // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
- // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
- $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
- $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
-
- return $content;
- }
-
- /**
- * Strip empty tags from source code.
- *
- * @param string $content
- *
- * @return string
- */
- protected function stripEmptyTags($content)
- {
- $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
- $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
-
- return $content;
- }
-
- /**
- * Strip comments from source code.
- */
- protected function stripComments()
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $callback = function ($match) use ($minifier) {
- $count = count($minifier->extracted);
- $placeholder = '/*'.$count.'*/';
- $minifier->extracted[$placeholder] = $match[0];
-
- return $placeholder;
- };
- $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
-
- $this->registerPattern('/\/\*.*?\*\//s', '');
- }
-
- /**
- * Strip whitespace.
- *
- * @param string $content The CSS content to strip the whitespace for
- *
- * @return string
- */
- protected function stripWhitespace($content)
- {
- // remove leading & trailing whitespace
- $content = preg_replace('/^\s*/m', '', $content);
- $content = preg_replace('/\s*$/m', '', $content);
-
- // replace newlines with a single space
- $content = preg_replace('/\s+/', ' ', $content);
-
- // remove whitespace around meta characters
- // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
- $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
- $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content);
- $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content);
- $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
-
- // whitespace around + and - can only be stripped inside some pseudo-
- // classes, like `:nth-child(3+2n)`
- // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
- // selectors like `div.weird- p`
- $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
- $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
-
- // remove semicolon/whitespace followed by closing bracket
- $content = str_replace(';}', '}', $content);
-
- return trim($content);
- }
-
- /**
- * Replace all `calc()` occurrences.
- */
- protected function extractCalcs()
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $callback = function ($match) use ($minifier) {
- $length = strlen($match[1]);
- $expr = '';
- $opened = 0;
-
- for ($i = 0; $i < $length; $i++) {
- $char = $match[1][$i];
- $expr .= $char;
- if ($char === '(') {
- $opened++;
- } elseif ($char === ')' && --$opened === 0) {
- break;
- }
- }
- $rest = str_replace($expr, '', $match[1]);
- $expr = trim(substr($expr, 1, -1));
-
- $count = count($minifier->extracted);
- $placeholder = 'calc('.$count.')';
- $minifier->extracted[$placeholder] = 'calc('.$expr.')';
-
- return $placeholder.$rest;
- };
-
- $this->registerPattern('/calc(\(.+?)(?=$|;|calc\()/', $callback);
- }
-
- /**
- * Check if file is small enough to be imported.
- *
- * @param string $path The path to the file
- *
- * @return bool
- */
- protected function canImportBySize($path)
- {
- return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
- }
-
- /**
- * Check if file a file can be imported, going by the path.
- *
- * @param string $path
- *
- * @return bool
- */
- protected function canImportByPath($path)
- {
- return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
- }
-
- /**
- * Return a converter to update relative paths to be relative to the new
- * destination.
- *
- * @param string $source
- * @param string $target
- *
- * @return ConverterInterface
- */
- protected function getPathConverter($source, $target)
- {
- return new Converter($source, $target);
- }
-}
-
-class JS extends Minify
-{
- /**
- * Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
- *
- * Note that regular expressions using that bit must have the PCRE_UTF8
- * pattern modifier (/u) set.
- *
- * @var string
- */
- const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
-
- /**
- * Full list of JavaScript reserved words.
- * Will be loaded from /data/js/keywords_reserved.txt.
- *
- * @see https://mathiasbynens.be/notes/reserved-keywords
- *
- * @var string[]
- */
- protected $keywordsReserved = array();
-
- /**
- * List of JavaScript reserved words that accept a <variable, value, ...>
- * after them. Some end of lines are not the end of a statement, like with
- * these keywords.
- *
- * E.g.: we shouldn't insert a ; after this else
- * else
- * console.log('this is quite fine')
- *
- * Will be loaded from /data/js/keywords_before.txt
- *
- * @var string[]
- */
- protected $keywordsBefore = array();
-
- /**
- * List of JavaScript reserved words that accept a <variable, value, ...>
- * before them. Some end of lines are not the end of a statement, like when
- * continued by one of these keywords on the newline.
- *
- * E.g.: we shouldn't insert a ; before this instanceof
- * variable
- * instanceof String
- *
- * Will be loaded from /data/js/keywords_after.txt
- *
- * @var string[]
- */
- protected $keywordsAfter = array();
-
- /**
- * List of all JavaScript operators.
- *
- * Will be loaded from /data/js/operators.txt
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
- *
- * @var string[]
- */
- protected $operators = array();
-
- /**
- * List of JavaScript operators that accept a <variable, value, ...> after
- * them. Some end of lines are not the end of a statement, like with these
- * operators.
- *
- * Note: Most operators are fine, we've only removed ++ and --.
- * ++ & -- have to be joined with the value they're in-/decrementing.
- *
- * Will be loaded from /data/js/operators_before.txt
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
- *
- * @var string[]
- */
- protected $operatorsBefore = array();
-
- /**
- * List of JavaScript operators that accept a <variable, value, ...> before
- * them. Some end of lines are not the end of a statement, like when
- * continued by one of these operators on the newline.
- *
- * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~.
- * There can't be a newline separating ! or ~ and whatever it is negating.
- * ++ & -- have to be joined with the value they're in-/decrementing.
- * ) & ] are "special" in that they have lots or usecases. () for example
- * is used for function calls, for grouping, in if () and for (), ...
- *
- * Will be loaded from /data/js/operators_after.txt
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
- *
- * @var string[]
- */
- protected $operatorsAfter = array();
-
- /**
- * {@inheritdoc}
- */
- public function __construct()
- {
- call_user_func_array(array('parent', '__construct'), func_get_args());
-
- $dataDir = __DIR__.'/../data/js/';
- $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
- $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
- $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
- $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
- $this->operators = file($dataDir.'operators.txt', $options);
- $this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
- $this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
- }
-
- /**
- * Minify the data.
- * Perform JS optimizations.
- *
- * @param string[optional] $path Path to write the data to
- *
- * @return string The minified data
- */
- public function execute($path = null)
- {
- $content = '';
-
- /*
- * Let's first take out strings, comments and regular expressions.
- * All of these can contain JS code-like characters, and we should make
- * sure any further magic ignores anything inside of these.
- *
- * Consider this example, where we should not strip any whitespace:
- * var str = "a test";
- *
- * Comments will be removed altogether, strings and regular expressions
- * will be replaced by placeholder text, which we'll restore later.
- */
- $this->extractStrings('\'"`');
- $this->stripComments();
- $this->extractRegex();
-
- // loop files
- foreach ($this->data as $source => $js) {
- // take out strings, comments & regex (for which we've registered
- // the regexes just a few lines earlier)
- $js = $this->replace($js);
-
- $js = $this->propertyNotation($js);
- $js = $this->shortenBools($js);
- $js = $this->stripWhitespace($js);
-
- // combine js: separating the scripts by a ;
- $content .= $js.";";
- }
-
- // clean up leftover `;`s from the combination of multiple scripts
- $content = ltrim($content, ';');
- $content = (string) substr($content, 0, -1);
-
- /*
- * Earlier, we extracted strings & regular expressions and replaced them
- * with placeholder text. This will restore them.
- */
- $content = $this->restoreExtractedData($content);
-
- return $content;
- }
-
- /**
- * Strip comments from source code.
- */
- protected function stripComments()
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $callback = function ($match) use ($minifier) {
- $count = count($minifier->extracted);
- $placeholder = '/*'.$count.'*/';
- $minifier->extracted[$placeholder] = $match[0];
-
- return $placeholder;
- };
- // multi-line comments
- $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
- $this->registerPattern('/\/\*.*?\*\//s', '');
-
- // single-line comments
- $this->registerPattern('/\/\/.*$/m', '');
- }
-
- /**
- * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
- *
- * The content inside the regex can contain characters that may be confused
- * for JS code: e.g. it could contain whitespace it needs to match & we
- * don't want to strip whitespace in there.
- *
- * The regex can be pretty simple: we don't have to care about comments,
- * (which also use slashes) because stripComments() will have stripped those
- * already.
- *
- * This method will replace all string content with simple REGEX#
- * placeholder text, so we've rid all regular expressions from characters
- * that may be misinterpreted. Original regex content will be saved in
- * $this->extracted and after doing all other minifying, we can restore the
- * original content via restoreRegex()
- */
- protected function extractRegex()
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $callback = function ($match) use ($minifier) {
- $count = count($minifier->extracted);
- $placeholder = '"'.$count.'"';
- $minifier->extracted[$placeholder] = $match[0];
-
- return $placeholder;
- };
-
- // match all chars except `/` and `\`
- // `\` is allowed though, along with whatever char follows (which is the
- // one being escaped)
- // this should allow all chars, except for an unescaped `/` (= the one
- // closing the regex)
- // then also ignore bare `/` inside `[]`, where they don't need to be
- // escaped: anything inside `[]` can be ignored safely
- $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*';
-
- // a regular expression can only be followed by a few operators or some
- // of the RegExp methods (a `\` followed by a variable or value is
- // likely part of a division, not a regex)
- $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
- $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*';
- $propertiesAndMethods = array(
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
- 'constructor',
- 'flags',
- 'global',
- 'ignoreCase',
- 'multiline',
- 'source',
- 'sticky',
- 'unicode',
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
- 'compile(',
- 'exec(',
- 'test(',
- 'toSource(',
- 'toString(',
- );
- $delimiters = array_fill(0, count($propertiesAndMethods), '/');
- $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
- $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
- $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
-
- // regular expressions following a `)` are rather annoying to detect...
- // quite often, `/` after `)` is a division operator & if it happens to
- // be followed by another one (or a comment), it is likely to be
- // confused for a regular expression
- // however, it's perfectly possible for a regex to follow a `)`: after
- // a single-line `if()`, `while()`, ... statement, for example
- // since, when they occur like that, they're always the start of a
- // statement, there's only a limited amount of ways they can be useful:
- // by calling the regex methods directly
- // if a regex following `)` is not followed by `.<property or method>`,
- // it's quite likely not a regex
- $before = '\)\s*';
- $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
- $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
-
- // 1 more edge case: a regex can be followed by a lot more operators or
- // keywords if there's a newline (ASI) in between, where the operator
- // actually starts a new statement
- // (https://github.com/matthiasmullie/minify/issues/56)
- $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
- $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
- $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
- $this->registerPattern('/'.$pattern.$after.'/', $callback);
- }
-
- /**
- * Strip whitespace.
- *
- * We won't strip *all* whitespace, but as much as possible. The thing that
- * we'll preserve are newlines we're unsure about.
- * JavaScript doesn't require statements to be terminated with a semicolon.
- * It will automatically fix missing semicolons with ASI (automatic semi-
- * colon insertion) at the end of line causing errors (without semicolon.)
- *
- * Because it's sometimes hard to tell if a newline is part of a statement
- * that should be terminated or not, we'll just leave some of them alone.
- *
- * @param string $content The content to strip the whitespace for
- *
- * @return string
- */
- protected function stripWhitespace($content)
- {
- // uniform line endings, make them all line feed
- $content = str_replace(array("\r\n", "\r"), "\n", $content);
-
- // collapse all non-line feed whitespace into a single space
- $content = preg_replace('/[^\S\n]+/', ' ', $content);
-
- // strip leading & trailing whitespace
- $content = str_replace(array(" \n", "\n "), "\n", $content);
-
- // collapse consecutive line feeds into just 1
- $content = preg_replace('/\n+/', "\n", $content);
-
- $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
- $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
- $operators = $this->getOperatorsForRegex($this->operators, '/');
- $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
- $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
-
- // strip whitespace that ends in (or next line begin with) an operator
- // that allows statements to be broken up over multiple lines
- unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
- $content = preg_replace(
- array(
- '/('.implode('|', $operatorsBefore).')\s+/',
- '/\s+('.implode('|', $operatorsAfter).')/',
- ),
- '\\1',
- $content
- );
-
- // make sure + and - can't be mistaken for, or joined into ++ and --
- $content = preg_replace(
- array(
- '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
- '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
- ),
- '\\1',
- $content
- );
-
- // collapse whitespace around reserved words into single space
- $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
- $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
-
- /*
- * We didn't strip whitespace after a couple of operators because they
- * could be used in different contexts and we can't be sure it's ok to
- * strip the newlines. However, we can safely strip any non-line feed
- * whitespace that follows them.
- */
- $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
- $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
- $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
- $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
-
- /*
- * Whitespace after `return` can be omitted in a few occasions
- * (such as when followed by a string or regex)
- * Same for whitespace in between `)` and `{`, or between `{` and some
- * keywords.
- */
- $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
- $content = preg_replace('/\)\s+\{/', '){', $content);
- $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
-
- /*
- * Get rid of double semicolons, except where they can be used like:
- * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
- * I'll safeguard these double semicolons inside for-loops by
- * temporarily replacing them with an invalid condition: they won't have
- * a double semicolon and will be easy to spot to restore afterwards.
- */
- $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
- $content = preg_replace('/;+/', ';', $content);
- $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
-
- /*
- * Next, we'll be removing all semicolons where ASI kicks in.
- * for-loops however, can have an empty body (ending in only a
- * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
- * Here, nothing happens during the loop; it's just used to keep
- * increasing `i`. With that ; omitted, the next line would be expected
- * to be the for-loop's body... Same goes for while loops.
- * I'm going to double that semicolon (if any) so after the next line,
- * which strips semicolons here & there, we're still left with this one.
- */
- $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
- $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
- /*
- * Below will also keep `;` after a `do{}while();` along with `while();`
- * While these could be stripped after do-while, detecting this
- * distinction is cumbersome, so I'll play it safe and make sure `;`
- * after any kind of `while` is kept.
- */
- $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
-
- /*
- * We also can't strip empty else-statements. Even though they're
- * useless and probably shouldn't be in the code in the first place, we
- * shouldn't be stripping the `;` that follows it as it breaks the code.
- * We can just remove those useless else-statements completely.
- *
- * @see https://github.com/matthiasmullie/minify/issues/91
- */
- $content = preg_replace('/else;/s', '', $content);
-
- /*
- * We also don't really want to terminate statements followed by closing
- * curly braces (which we've ignored completely up until now) or end-of-
- * script: ASI will kick in here & we're all about minifying.
- * Semicolons at beginning of the file don't make any sense either.
- */
- $content = preg_replace('/;(\}|$)/s', '\\1', $content);
- $content = ltrim($content, ';');
-
- // get rid of remaining whitespace af beginning/end
- return trim($content);
- }
-
- /**
- * We'll strip whitespace around certain operators with regular expressions.
- * This will prepare the given array by escaping all characters.
- *
- * @param string[] $operators
- * @param string $delimiter
- *
- * @return string[]
- */
- protected function getOperatorsForRegex(array $operators, $delimiter = '/')
- {
- // escape operators for use in regex
- $delimiters = array_fill(0, count($operators), $delimiter);
- $escaped = array_map('preg_quote', $operators, $delimiters);
-
- $operators = array_combine($operators, $escaped);
-
- // ignore + & - for now, they'll get special treatment
- unset($operators['+'], $operators['-']);
-
- // dot can not just immediately follow a number; it can be confused for
- // decimal point, or calling a method on it, e.g. 42 .toString()
- $operators['.'] = '(?<![0-9]\s)\.';
-
- // don't confuse = with other assignment shortcuts (e.g. +=)
- $chars = preg_quote('+-*\=<>%&|', $delimiter);
- $operators['='] = '(?<!['.$chars.'])\=';
-
- return $operators;
- }
-
- /**
- * We'll strip whitespace around certain keywords with regular expressions.
- * This will prepare the given array by escaping all characters.
- *
- * @param string[] $keywords
- * @param string $delimiter
- *
- * @return string[]
- */
- protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
- {
- // escape keywords for use in regex
- $delimiter = array_fill(0, count($keywords), $delimiter);
- $escaped = array_map('preg_quote', $keywords, $delimiter);
-
- // add word boundaries
- array_walk($keywords, function ($value) {
- return '\b'.$value.'\b';
- });
-
- $keywords = array_combine($keywords, $escaped);
-
- return $keywords;
- }
-
- /**
- * Replaces all occurrences of array['key'] by array.key.
- *
- * @param string $content
- *
- * @return string
- */
- protected function propertyNotation($content)
- {
- // PHP only supports $this inside anonymous functions since 5.4
- $minifier = $this;
- $keywords = $this->keywordsReserved;
- $callback = function ($match) use ($minifier, $keywords) {
- $property = trim($minifier->extracted[$match[1]], '\'"');
-
- /*
- * Check if the property is a reserved keyword. In this context (as
- * property of an object literal/array) it shouldn't matter, but IE8
- * freaks out with "Expected identifier".
- */
- if (in_array($property, $keywords)) {
- return $match[0];
- }
-
- /*
- * See if the property is in a variable-like format (e.g.
- * array['key-here'] can't be replaced by array.key-here since '-'
- * is not a valid character there.
- */
- if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
- return $match[0];
- }
-
- return '.'.$property;
- };
-
- /*
- * Figure out if previous character is a variable name (of the array
- * we want to use property notation on) - this is to make sure
- * standalone ['value'] arrays aren't confused for keys-of-an-array.
- * We can (and only have to) check the last character, because PHP's
- * regex implementation doesn't allow unfixed-length look-behind
- * assertions.
- */
- preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
- $previousChar = $previousChar[1];
-
- /*
- * Make sure word preceding the ['value'] is not a keyword, e.g.
- * return['x']. Because -again- PHP's regex implementation doesn't allow
- * unfixed-length look-behind assertions, I'm just going to do a lot of
- * separate look-behind assertions, one for each keyword.
- */
- $keywords = $this->getKeywordsForRegex($keywords);
- $keywords = '(?<!'.implode(')(?<!', $keywords).')';
-
- return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
- }
-
- /**
- * Replaces true & false by !0 and !1.
- *
- * @param string $content
- *
- * @return string
- */
- protected function shortenBools($content)
- {
- /*
- * 'true' or 'false' could be used as property names (which may be
- * followed by whitespace) - we must not replace those!
- * Since PHP doesn't allow variable-length (to account for the
- * whitespace) lookbehind assertions, I need to capture the leading
- * character and check if it's a `.`
- */
- $callback = function ($match) {
- if (trim($match[1]) === '.') {
- return $match[0];
- }
-
- return $match[1].($match[2] === 'true' ? '!0' : '!1');
- };
- $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
-
- // for(;;) is exactly the same as while(true), but shorter :)
- $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
-
- // now make sure we didn't turn any do ... while(true) into do ... for(;;)
- preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
-
- // go backward to make sure positional offsets aren't altered when $content changes
- $dos = array_reverse($dos);
- foreach ($dos as $do) {
- $offsetDo = $do[0][1];
-
- // find all `while` (now `for`) following `do`: one of those must be
- // associated with the `do` and be turned back into `while`
- preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
- foreach ($whiles as $while) {
- $offsetWhile = $while[0][1];
-
- $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
- $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
- if ($open === $close) {
- // only restore `while` if amount of `{` and `}` are the same;
- // otherwise, that `for` isn't associated with this `do`
- $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
- break;
- }
- }
- }
-
- return $content;
- }
-}
-
-interface ConverterInterface {
- public function convert($path);
-}
-
-class Converter implements ConverterInterface {
- public function convert($path) {
- return $path;
- }
-}
-
-// Minify extensions
-// Copyright (c) 2013-2019 Datenstrom
-
-class MinifyCss extends CSS { }
-
-class MinifyJavaScript extends JS {
-
- // Use hardcoded keywords and operators
- public function __construct() {
- $this->keywordsReserved = array("do", "if", "in", "for", "let", "new", "try", "var", "case", "else", "enum", "eval", "null", "this", "true", "void", "with", "break", "catch", "class", "const", "false", "super", "throw", "while", "yield", "delete", "export", "import", "public", "return", "static", "switch", "typeof", "default", "extends", "finally", "package", "private", "continue", "debugger", "function", "arguments", "interface", "protected", "implements", "instanceof", "abstract", "boolean", "byte", "char", "double", "final", "float", "goto", "int", "long", "native", "short", "synchronized", "throws", "transient", "volatile");
- $this->keywordsBefore = array("do", "in", "let", "new", "var", "case", "else", "enum", "void", "with", "class", "const", "yield", "delete", "export", "import", "public", "static", "typeof", "extends", "package", "private", "function", "protected", "implements", "instanceof");
- $this->keywordsAfter = array("in", "public", "extends", "private", "protected", "implements", "instanceof");
- $this->operators = array("+", "-", "*", "/", "%", "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=", "&", "|", "^", "~", "<<", ">>", ">>>", "==", "===", "!=", "!==", ">", "<", ">=", "<=", "&&", "||", "!", ".", "[", "]", "?", ":", ",", ";", "(", ")", "{", "}");
- $this->operatorsBefore = array("+", "-", "*", "/", "%", "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=", "&", "|", "^", "~", "<<", ">>", ">>>", "==", "===", "!=", "!==", ">", "<", ">=", "<=", "&&", "||", "!", ".", "[", "?", ":", ",", ";", "(", "{");
- $this->operatorsAfter = array("+", "-", "*", "/", "%", "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=", "&", "|", "^", "<<", ">>", ">>>", "==", "===", "!=", "!==", ">", "<", ">=", "<=", "&&", "||", ".", "[", "]", "?", ":", ",", ";", "(", ")", "}");
- }
-}
-
-class MinifyBasic extends Minify {
-
- // Minify data, remove only comments and empty lines
- public function execute($path = null) {
- $content = "";
- $this->extractStrings();
- foreach ($this->data as $source => $data) {
- $data = $this->replace($data);
- $data = preg_replace("/\/\*.*?\*\//s", "", $data);
- $data = preg_replace("/\/\/.*?[\r\n]+/", "", $data);
- $data = preg_replace("/[\r\n]+/", "\n", $data);
- $content .= trim($data);
- }
- return $this->restoreExtractedData($content);
- }
-}
diff --git a/system/extensions/command.php b/system/extensions/command.php
@@ -1,623 +0,0 @@
-<?php
-// Command extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/command
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-class YellowCommand {
- const VERSION = "0.8.17";
- const TYPE = "feature";
- const PRIORITY = "3";
- public $yellow; //access to API
- public $files; //number of files
- public $links; //number of links
- public $errors; //number of errors
- public $locationsArguments; //locations with location arguments detected
- public $locationsArgumentsPagination; //locations with pagination arguments detected
-
- // Handle initialisation
- public function onLoad($yellow) {
- $this->yellow = $yellow;
- }
-
- // Handle command
- public function onCommand($command, $text) {
- switch ($command) {
- case "": $statusCode = $this->processCommandHelp(); break;
- case "build": $statusCode = $this->processCommandBuild($command, $text); break;
- case "check": $statusCode = $this->processCommandCheck($command, $text); break;
- case "clean": $statusCode = $this->processCommandClean($command, $text); break;
- case "serve": $statusCode = $this->processCommandServe($command, $text); break;
- default: $statusCode = 0;
- }
- return $statusCode;
- }
-
- // Handle command help
- public function onCommandHelp() {
- $help .= "build [directory location]\n";
- $help .= "check [directory location]\n";
- $help .= "clean [directory location]\n";
- $help .= "serve [directory url]\n";
- return $help;
- }
-
- // Process command to show available commands
- public function processCommandHelp() {
- echo "Datenstrom Yellow is for people who make small websites.\n";
- $lineCounter = 0;
- foreach ($this->getCommandHelp() as $line) {
- echo(++$lineCounter>1 ? " " : "Syntax: ")."php yellow.php $line\n";
- }
- return 200;
- }
-
- // Process command to build static website
- public function processCommandBuild($command, $text) {
- $statusCode = 0;
- list($path, $location) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($location) || substru($location, 0, 1)=="/") {
- if ($this->checkStaticSettings()) {
- $statusCode = $this->buildStaticFiles($path, $location);
- } else {
- $statusCode = 500;
- $this->files = 0;
- $this->errors = 1;
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
- echo "ERROR building files: Please configure CoreStaticUrl in file '$fileName'!\n";
- }
- echo "Yellow $command: $this->files file".($this->files!=1 ? "s" : "");
- echo ", $this->errors error".($this->errors!=1 ? "s" : "")."\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Build static files
- public function buildStaticFiles($path, $locationFilter) {
- $path = rtrim(empty($path) ? $this->yellow->system->get("coreStaticDirectory") : $path, "/");
- $this->files = $this->errors = 0;
- $this->locationsArguments = $this->locationsArgumentsPagination = array();
- $statusCode = empty($locationFilter) ? $this->cleanStaticFiles($path, $locationFilter) : 200;
- $staticUrl = $this->yellow->system->get("coreStaticUrl");
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
- $locations = $this->getContentLocations();
- $filesEstimated = count($locations);
- foreach ($locations as $location) {
- echo "\rBuilding static website ".$this->getProgressPercent($this->files, $filesEstimated, 5, 60)."%... ";
- if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location, true));
- }
- foreach ($this->locationsArguments as $location) {
- echo "\rBuilding static website ".$this->getProgressPercent($this->files, $filesEstimated, 5, 60)."%... ";
- if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location, true));
- }
- $filesEstimated = $this->files + count($this->locationsArguments) + count($this->locationsArgumentsPagination);
- foreach ($this->locationsArgumentsPagination as $location) {
- echo "\rBuilding static website ".$this->getProgressPercent($this->files, $filesEstimated, 5, 95)."%... ";
- if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
- if (substru($location, -1)!=$this->yellow->toolbox->getLocationArgumentsSeparator()) {
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location, false, true));
- }
- for ($pageNumber=2; $pageNumber<=999; ++$pageNumber) {
- $statusCodeLocation = $this->buildStaticFile($path, $location.$pageNumber, false, true);
- $statusCode = max($statusCode, $statusCodeLocation);
- if ($statusCodeLocation==100) break;
- }
- }
- if (empty($locationFilter)) {
- foreach ($this->getMediaLocations() as $location) {
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location));
- }
- foreach ($this->getSystemLocations() as $location) {
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location));
- }
- foreach ($this->getExtraLocations($path) as $location) {
- $statusCode = max($statusCode, $this->buildStaticFile($path, $location));
- }
- $statusCode = max($statusCode, $this->buildStaticFile($path, "/error/", false, false, true));
- }
- echo "\rBuilding static website 100%... done\n";
- return $statusCode;
- }
-
- // Build static file
- public function buildStaticFile($path, $location, $analyse = false, $probe = false, $error = false) {
- $this->yellow->content = new YellowContent($this->yellow);
- $this->yellow->page = new YellowPage($this->yellow);
- $this->yellow->page->fileName = substru($location, 1);
- if (!is_readable($this->yellow->page->fileName)) {
- ob_start();
- $staticUrl = $this->yellow->system->get("coreStaticUrl");
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
- $statusCode = $this->requestStaticFile($scheme, $address, $base, $location);
- if ($statusCode<400 || $error) {
- $fileData = ob_get_contents();
- $statusCode = $this->saveStaticFile($path, $location, $fileData, $statusCode);
- }
- ob_end_clean();
- } else {
- $statusCode = $this->copyStaticFile($path, $location);
- }
- if ($statusCode==200 && $analyse) $this->analyseLocations($scheme, $address, $base, $fileData);
- if ($statusCode==404 && $probe) $statusCode = 100;
- if ($statusCode==404 && $error) $statusCode = 200;
- if ($statusCode>=200) ++$this->files;
- if ($statusCode>=400) {
- ++$this->errors;
- echo "\rERROR building location '$location', ".$this->yellow->page->getStatusCode(true)."\n";
- }
- if (defined("DEBUG") && DEBUG>=1) echo "YellowCommand::buildStaticFile status:$statusCode location:$location<br/>\n";
- return $statusCode;
- }
-
- // Request static file
- public function requestStaticFile($scheme, $address, $base, $location) {
- list($serverName, $serverPort) = $this->yellow->toolbox->getTextList($address, ":", 2);
- if (empty($serverPort)) $serverPort = $scheme=="https" ? 443 : 80;
- $_SERVER["HTTPS"] = $scheme=="https" ? "on" : "off";
- $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1";
- $_SERVER["SERVER_NAME"] = $serverName;
- $_SERVER["SERVER_PORT"] = $serverPort;
- $_SERVER["REQUEST_METHOD"] = "GET";
- $_SERVER["REQUEST_URI"] = $base.$location;
- $_SERVER["SCRIPT_NAME"] = $base."/yellow.php";
- $_SERVER["REMOTE_ADDR"] = "127.0.0.1";
- $_REQUEST = array();
- return $this->yellow->request();
- }
-
- // Save static file
- public function saveStaticFile($path, $location, $fileData, $statusCode) {
- $modified = strtotime($this->yellow->page->getHeader("Last-Modified"));
- if ($modified==0) $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName);
- if ($statusCode>=301 && $statusCode<=303) {
- $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location"));
- $modified = time();
- }
- $fileName = $this->getStaticFile($path, $location, $statusCode);
- if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName);
- if (!$this->yellow->toolbox->createFile($fileName, $fileData, true) ||
- !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
- $statusCode = 500;
- $this->yellow->page->statusCode = $statusCode;
- $this->yellow->page->set("pageError", "Can't write file '$fileName'!");
- }
- return $statusCode;
- }
-
- // Copy static file
- public function copyStaticFile($path, $location) {
- $statusCode = 200;
- $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName);
- $fileName = $this->getStaticFile($path, $location, $statusCode);
- if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName);
- if (!$this->yellow->toolbox->copyFile($this->yellow->page->fileName, $fileName, true) ||
- !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
- $statusCode = 500;
- $this->yellow->page->statusCode = $statusCode;
- $this->yellow->page->set("pageError", "Can't write file '$fileName'!");
- }
- return $statusCode;
- }
-
- // Analyse locations with arguments
- public function analyseLocations($scheme, $address, $base, $rawData) {
- preg_match_all("/<(.*?)href=\"([^\"]+)\"(.*?)>/i", $rawData, $matches);
- foreach ($matches[2] as $match) {
- $location = rawurldecode($match);
- if (preg_match("/^(.*?)#(.*)$/", $location, $tokens)) $location = $tokens[1];
- if (preg_match("/^(\w+):\/\/([^\/]+)(.*)$/", $location, $tokens)) {
- if ($tokens[1]!=$scheme) continue;
- if ($tokens[2]!=$address) continue;
- $location = $tokens[3];
- }
- if (substru($location, 0, strlenu($base))!=$base) continue;
- $location = substru($location, strlenu($base));
- if (!$this->yellow->toolbox->isLocationArguments($location)) continue;
- if (!$this->yellow->toolbox->isLocationArgumentsPagination($location)) {
- $location = rtrim($location, "/")."/";
- if (!isset($this->locationsArguments[$location])) {
- $this->locationsArguments[$location] = $location;
- if (defined("DEBUG") && DEBUG>=2) echo "YellowCommand::analyseLocations detected location:$location<br/>\n";
- }
- } else {
- $location = rtrim($location, "0..9");
- if (!isset($this->locationsArgumentsPagination[$location])) {
- $this->locationsArgumentsPagination[$location] = $location;
- if (defined("DEBUG") && DEBUG>=2) echo "YellowCommand::analyseLocations detected location:$location<br/>\n";
- }
- }
- }
- }
-
- // Process command to check static files for broken links
- public function processCommandCheck($command, $text) {
- $statusCode = 0;
- list($path, $location) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($location) || substru($location, 0, 1)=="/") {
- if ($this->checkStaticSettings()) {
- $statusCode = $this->checkStaticFiles($path, $location);
- } else {
- $statusCode = 500;
- $this->links = 0;
- $this->errors = 1;
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
- echo "ERROR checking files: Please configure CoreStaticUrl in file '$fileName'!\n";
- }
- echo "Yellow $command: $this->links link".($this->links!=1 ? "s" : "");
- echo ", $this->errors error".($this->errors!=1 ? "s" : "")."\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Check static files for broken links
- public function checkStaticFiles($path, $locationFilter) {
- $path = rtrim(empty($path) ? $this->yellow->system->get("coreStaticDirectory") : $path, "/");
- $this->links = $this->errors = 0;
- $regex = "/^[^.]+$|".$this->yellow->system->get("coreStaticDefaultFile")."$/";
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($path, $regex, false, false);
- list($statusCodeFiles, $links) = $this->analyseLinks($path, $locationFilter, $fileNames);
- list($statusCodeLinks, $broken, $redirect) = $this->analyseStatus($path, $links);
- if ($statusCodeLinks!=200) {
- $this->showLinks($broken, "Broken links");
- $this->showLinks($redirect, "Redirect links");
- }
- return max($statusCodeFiles, $statusCodeLinks);
- }
-
- // Analyse links in static files
- public function analyseLinks($path, $locationFilter, $fileNames) {
- $statusCode = 200;
- $links = array();
- if (!empty($fileNames)) {
- $staticUrl = $this->yellow->system->get("coreStaticUrl");
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
- foreach ($fileNames as $fileName) {
- if (is_readable($fileName)) {
- $locationSource = $this->getStaticLocation($path, $fileName);
- if (!preg_match("#^$base$locationFilter#", "$base$locationSource")) continue;
- $fileData = $this->yellow->toolbox->readFile($fileName);
- preg_match_all("/<(.*?)href=\"([^\"]+)\"(.*?)>/i", $fileData, $matches);
- foreach ($matches[2] as $match) {
- $location = rawurldecode($match);
- if (preg_match("/^(.*?)#(.*)$/", $location, $tokens)) $location = $tokens[1];
- if (preg_match("/^(\w+):\/\/([^\/]+)(.*)$/", $location, $matches)) {
- $url = $location.(empty($matches[3]) ? "/" : "");
- if (!isset($links[$url])) {
- $links[$url] = $locationSource;
- } else {
- $links[$url] .= ",".$locationSource;
- }
- if (defined("DEBUG") && DEBUG>=2) echo "YellowCommand::analyseLinks detected url:$url<br/>\n";
- } elseif (substru($location, 0, 1)=="/") {
- $url = "$scheme://$address$location";
- if (!isset($links[$url])) {
- $links[$url] = $locationSource;
- } else {
- $links[$url] .= ",".$locationSource;
- }
- if (defined("DEBUG") && DEBUG>=2) echo "YellowCommand::analyseLinks detected url:$url<br/>\n";
- }
- }
- if (defined("DEBUG") && DEBUG>=1) echo "YellowCommand::analyseLinks location:$locationSource<br/>\n";
- } else {
- $statusCode = 500;
- ++$this->errors;
- echo "ERROR reading files: Can't read file '$fileName'!\n";
- }
- }
- $this->links = count($links);
- } else {
- $statusCode = 500;
- ++$this->errors;
- echo "ERROR reading files: Can't find files in directory '$path'!\n";
- }
- return array($statusCode, $links);
- }
-
- // Analyse link status
- public function analyseStatus($path, $links) {
- $statusCode = 200;
- $remote = $broken = $redirect = $data = array();
- $staticUrl = $this->yellow->system->get("coreStaticUrl");
- $staticUrlLength = strlenu(rtrim($staticUrl, "/"));
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
- $staticLocations = $this->getContentLocations(true);
- foreach ($links as $url=>$value) {
- if (preg_match("#^$staticUrl#", $url)) {
- $location = substru($url, $staticUrlLength);
- $fileName = $path.substru($url, $staticUrlLength);
- if (is_readable($fileName)) continue;
- if (in_array($location, $staticLocations)) continue;
- }
- if (preg_match("/^(http|https):/", $url)) $remote[$url] = $value;
- }
- $remoteNow = 0;
- uksort($remote, "strnatcasecmp");
- foreach ($remote as $url=>$value) {
- echo "\rChecking static website ".$this->getProgressPercent(++$remoteNow, count($remote), 5, 95)."%... ";
- if (defined("DEBUG") && DEBUG>=1) echo "YellowCommand::analyseStatus url:$url\n";
- $referer = "$scheme://$address$base".(($pos = strposu($value, ",")) ? substru($value, 0, $pos) : $value);
- $statusCodeUrl = $this->getLinkStatus($url, $referer);
- if ($statusCodeUrl!=200) {
- $statusCode = max($statusCode, $statusCodeUrl);
- $data[$url] = "$statusCodeUrl,$value";
- }
- }
- foreach ($data as $url=>$value) {
- $locations = preg_split("/\s*,\s*/", $value);
- $statusCodeUrl = array_shift($locations);
- foreach ($locations as $location) {
- if ($statusCodeUrl==302) continue;
- if ($statusCodeUrl>=300 && $statusCodeUrl<=399) {
- $redirect["$scheme://$address$base$location -> $url - ".$this->getStatusFormatted($statusCodeUrl)] = $statusCodeUrl;
- } else {
- $broken["$scheme://$address$base$location -> $url - ".$this->getStatusFormatted($statusCodeUrl)] = $statusCodeUrl;
- }
- ++$this->errors;
- }
- }
- echo "\rChecking static website 100%... done\n";
- return array($statusCode, $broken, $redirect);
- }
-
- // Show links
- public function showLinks($data, $text) {
- if (!empty($data)) {
- echo "$text\n\n";
- uksort($data, "strnatcasecmp");
- $data = array_slice($data, 0, 99);
- foreach ($data as $key=>$value) {
- echo "- $key\n";
- }
- echo "\n";
- }
- }
-
- // Process command to clean static files
- public function processCommandClean($command, $text) {
- $statusCode = 0;
- list($path, $location) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($location) || substru($location, 0, 1)=="/") {
- $statusCode = $this->cleanStaticFiles($path, $location);
- echo "Yellow $command: Static file".(empty($location) ? "s" : "")." ".($statusCode!=200 ? "not " : "")."cleaned\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Clean static files and directories
- public function cleanStaticFiles($path, $location) {
- $statusCode = 200;
- $path = rtrim(empty($path) ? $this->yellow->system->get("coreStaticDirectory") : $path, "/");
- if (empty($location)) {
- $statusCode = max($statusCode, $this->broadcastCommand("clean", "all"));
- $statusCode = max($statusCode, $this->cleanStaticDirectory($path));
- } else {
- if ($this->yellow->lookup->isFileLocation($location)) {
- $fileName = $this->getStaticFile($path, $location, $statusCode);
- $statusCode = $this->cleanStaticFile($fileName);
- } else {
- $statusCode = $this->cleanStaticDirectory($path.$location);
- }
- }
- return $statusCode;
- }
-
- // Clean static directory
- public function cleanStaticDirectory($path) {
- $statusCode = 200;
- if (is_dir($path) && $this->checkStaticDirectory($path)) {
- if (!$this->yellow->toolbox->deleteDirectory($path)) {
- $statusCode = 500;
- echo "ERROR cleaning files: Can't delete directory '$path'!\n";
- }
- }
- return $statusCode;
- }
-
- // Clean static file
- public function cleanStaticFile($fileName) {
- $statusCode = 200;
- if (is_file($fileName)) {
- if (!$this->yellow->toolbox->deleteFile($fileName)) {
- $statusCode = 500;
- echo "ERROR cleaning files: Can't delete file '$fileName'!\n";
- }
- }
- return $statusCode;
- }
-
- // Broadcast command to other extensions
- public function broadcastCommand($command, $text) {
- $statusCode = 0;
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onCommand") && $key!="command") {
- $statusCode = max($statusCode, $value["obj"]->onCommand($command, $text));
- }
- }
- return $statusCode;
- }
-
- // Process command to start built-in web server
- public function processCommandServe($command, $text) {
- list($path, $url) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($path) && is_dir($this->yellow->system->get("coreStaticDirectory"))) $path = $this->yellow->system->get("coreStaticDirectory");
- if (empty($url)) $url = "http://localhost:8000";
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($url);
- if ($scheme=="http" && !empty($address)) {
- if (!preg_match("/\:\d+$/", $address)) $address .= ":8000";
- echo "Starting built-in web server on $scheme://$address/\n";
- echo "Press Ctrl-C to quit...\n";
- if (empty($path) || $path=="dynamic") {
- system("php -S $address yellow.php", $returnStatus);
- } else {
- system("php -S $address -t $path", $returnStatus);
- }
- $statusCode = $returnStatus!=0 ? 500 : 200;
- if ($statusCode!=200) echo "ERROR starting web server: Please check your arguments!\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Check static settings
- public function checkStaticSettings() {
- return !empty($this->yellow->system->get("coreStaticUrl"));
- }
-
- // Check static directory
- public function checkStaticDirectory($path) {
- $ok = false;
- if (!empty($path)) {
- if ($path==rtrim($this->yellow->system->get("coreStaticDirectory"), "/")) $ok = true;
- if ($path==rtrim($this->yellow->system->get("coreTrashDirectory"), "/")) $ok = true;
- if (is_file("$path/".$this->yellow->system->get("coreStaticDefaultFile"))) $ok = true;
- if (is_file("$path/yellow.php")) $ok = false;
- }
- return $ok;
- }
-
- // Return command help
- public function getCommandHelp() {
- $data = array();
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onCommandHelp")) {
- foreach (preg_split("/[\r\n]+/", $value["obj"]->onCommandHelp()) as $line) {
- list($command, $dummy) = $this->yellow->toolbox->getTextList($line, " ", 2);
- if (!empty($command) && !isset($data[$command])) $data[$command] = $line;
- }
- }
- }
- uksort($data, "strnatcasecmp");
- return $data;
- }
-
- // Return human readable status
- public function getStatusFormatted($statusCode) {
- return $this->yellow->toolbox->getHttpStatusFormatted($statusCode, true);
- }
-
- // Return progress in percent
- public function getProgressPercent($now, $total, $increments, $max)
- {
- $percent = intval(($max / $total) * $now);
- if ($increments>1) $percent = intval($percent / $increments) * $increments;
- return min($max, $percent);
- }
-
- // Return static file
- public function getStaticFile($path, $location, $statusCode) {
- if ($statusCode<400) {
- $fileName = $path.$location;
- if (!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->system->get("coreStaticDefaultFile");
- } elseif ($statusCode==404) {
- $fileName = $path."/".$this->yellow->system->get("coreStaticErrorFile");
- }
- return $fileName;
- }
-
- // Return static location
- public function getStaticLocation($path, $fileName) {
- $location = substru($fileName, strlenu($path));
- if (basename($location)==$this->yellow->system->get("coreStaticDefaultFile")) {
- $defaultFileLength = strlenu($this->yellow->system->get("coreStaticDefaultFile"));
- $location = substru($location, 0, -$defaultFileLength);
- }
- return $location;
- }
-
- // Return static redirect
- public function getStaticRedirect($location) {
- $output = "<!DOCTYPE html><html>\n<head>\n";
- $output .= "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n";
- $output .= "<meta http-equiv=\"refresh\" content=\"0;url=".htmlspecialchars($location)."\" />\n";
- $output .= "</head>\n</html>";
- return $output;
- }
-
- // Return content locations
- public function getContentLocations($includeAll = false) {
- $locations = array();
- $staticUrl = $this->yellow->system->get("coreStaticUrl");
- list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
- $this->yellow->page->setRequestInformation($scheme, $address, $base, "", "");
- foreach ($this->yellow->content->index(true, true) as $page) {
- if (preg_match("/exclude/i", $page->get("build")) && !$includeAll) continue;
- if ($page->get("status")=="private" || $page->get("status")=="draft") continue;
- array_push($locations, $page->location);
- }
- if (!$this->yellow->content->find("/") && $this->yellow->system->get("coreMultiLanguageMode")) array_unshift($locations, "/");
- return $locations;
- }
-
- // Return media locations
- public function getMediaLocations() {
- $locations = array();
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($this->yellow->system->get("coreMediaDirectory"), "/.*/", false, false);
- foreach ($fileNames as $fileName) {
- array_push($locations, "/".$fileName);
- }
- return $locations;
- }
-
- // Return system locations
- public function getSystemLocations() {
- $locations = array();
- $regex = "/\.(css|gif|ico|js|jpg|png|svg|txt|woff|woff2)$/";
- $extensionDirectoryLength = strlenu($this->yellow->system->get("coreExtensionDirectory"));
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($this->yellow->system->get("coreExtensionDirectory"), $regex, false, false);
- foreach ($fileNames as $fileName) {
- array_push($locations, $this->yellow->system->get("coreExtensionLocation").substru($fileName, $extensionDirectoryLength));
- }
- $resourceDirectoryLength = strlenu($this->yellow->system->get("coreResourceDirectory"));
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($this->yellow->system->get("coreResourceDirectory"), $regex, false, false);
- foreach ($fileNames as $fileName) {
- array_push($locations, $this->yellow->system->get("coreResourceLocation").substru($fileName, $resourceDirectoryLength));
- }
- return $locations;
- }
-
- // Return extra locations
- public function getExtraLocations($path) {
- $locations = array();
- $pathIgnore = "($path/|".
- $this->yellow->system->get("coreStaticDirectory")."|".
- $this->yellow->system->get("coreCacheDirectory")."|".
- $this->yellow->system->get("coreContentDirectory")."|".
- $this->yellow->system->get("coreMediaDirectory")."|".
- $this->yellow->system->get("coreSystemDirectory").")";
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive(".", "/.*/", false, false);
- foreach ($fileNames as $fileName) {
- $fileName = substru($fileName, 2);
- if (preg_match("#^$pathIgnore#", $fileName) || $fileName=="yellow.php") continue;
- array_push($locations, "/".$fileName);
- }
- return $locations;
- }
-
- // Return link status
- public function getLinkStatus($url, $referer) {
- $curlHandle = curl_init();
- curl_setopt($curlHandle, CURLOPT_URL, $url);
- curl_setopt($curlHandle, CURLOPT_REFERER, $referer);
- curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; DatenstromYellow/".YellowCore::VERSION."; LinkChecker)");
- curl_setopt($curlHandle, CURLOPT_NOBODY, 1);
- curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 30);
- curl_exec($curlHandle);
- $statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
- curl_close($curlHandle);
- if (defined("DEBUG") && DEBUG>=2) echo "YellowCommand::getLinkStatus status:$statusCode url:$url<br/>\n";
- return $statusCode;
- }
-}
diff --git a/system/extensions/core.php b/system/extensions/core.php
@@ -1,71 +1,54 @@
<?php
-// Core extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/core
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Core extension, https://github.com/annaesvensson/yellow-core
class YellowCore {
- const VERSION = "0.8.13";
- const TYPE = "feature";
- public $page; //current page
- public $content; //content files from file system
- public $media; //media files from file system
- public $system; //system settings
- public $text; //text settings
- public $lookup; //location and file lookup
- public $toolbox; //toolbox with helpers
- public $extensions; //features and themes
+ const VERSION = "0.8.125";
+ const RELEASE = "0.8.23";
+ public $content; // content files
+ public $media; // media files
+ public $system; // system settings
+ public $language; // language settings
+ public $user; // user settings
+ public $extension; // extensions
+ public $lookup; // lookup and normalisation methods
+ public $toolbox; // toolbox with helper methods
+ public $page; // current page
public function __construct() {
- $this->checkRequirements();
- $this->page = new YellowPage($this);
$this->content = new YellowContent($this);
$this->media = new YellowMedia($this);
$this->system = new YellowSystem($this);
- $this->text = new YellowText($this);
+ $this->language = new YellowLanguage($this);
+ $this->user = new YellowUser($this);
+ $this->extension = new YellowExtension($this);
$this->lookup = new YellowLookup($this);
- $this->toolbox = new YellowToolbox();
- $this->extensions = new YellowExtensions($this);
- $this->system->setDefault("sitename", "Yellow");
- $this->system->setDefault("author", "Yellow");
+ $this->toolbox = new YellowToolbox($this);
+ $this->page = new YellowPage($this);
+ $this->checkRequirements();
+ $this->system->setDefault("sitename", "Localhost");
+ $this->system->setDefault("author", "Datenstrom");
$this->system->setDefault("email", "webmaster");
$this->system->setDefault("language", "en");
$this->system->setDefault("layout", "default");
$this->system->setDefault("theme", "default");
$this->system->setDefault("parser", "markdown");
$this->system->setDefault("status", "public");
- $this->system->setDefault("coreStaticUrl", "");
- $this->system->setDefault("coreStaticDefaultFile", "index.html");
- $this->system->setDefault("coreStaticErrorFile", "404.html");
- $this->system->setDefault("coreStaticDirectory", "public/");
- $this->system->setDefault("coreCacheDirectory", "cache/");
- $this->system->setDefault("coreTrashDirectory", "system/trash/");
$this->system->setDefault("coreServerUrl", "auto");
- $this->system->setDefault("coreServerTimezone", "UTC");
- $this->system->setDefault("coreMultiLanguageMode", "0");
+ $this->system->setDefault("coreTimezone", "UTC");
+ $this->system->setDefault("coreContentExtension", ".md");
+ $this->system->setDefault("coreContentDefaultFile", "page.md");
+ $this->system->setDefault("coreContentErrorFile", "page-error-(.*).md");
+ $this->system->setDefault("coreLanguageFile", "yellow-language.ini");
+ $this->system->setDefault("coreUserFile", "yellow-user.ini");
+ $this->system->setDefault("coreWebsiteFile", "yellow-website.log");
$this->system->setDefault("coreMediaLocation", "/media/");
$this->system->setDefault("coreDownloadLocation", "/media/downloads/");
$this->system->setDefault("coreImageLocation", "/media/images/");
+ $this->system->setDefault("coreThumbnailLocation", "/media/thumbnails/");
$this->system->setDefault("coreExtensionLocation", "/media/extensions/");
- $this->system->setDefault("coreResourceLocation", "/media/resources/");
- $this->system->setDefault("coreMediaDirectory", "media/");
- $this->system->setDefault("coreDownloadDirectory", "media/downloads/");
- $this->system->setDefault("coreImageDirectory", "media/images/");
- $this->system->setDefault("coreSystemDirectory", "system/");
- $this->system->setDefault("coreExtensionDirectory", "system/extensions/");
- $this->system->setDefault("coreLayoutDirectory", "system/layouts/");
- $this->system->setDefault("coreResourceDirectory", "system/resources/");
- $this->system->setDefault("coreSettingDirectory", "system/settings/");
- $this->system->setDefault("coreContentDirectory", "content/");
- $this->system->setDefault("coreContentRootDirectory", "default/");
- $this->system->setDefault("coreContentHomeDirectory", "home/");
- $this->system->setDefault("coreContentSharedDirectory", "shared/");
- $this->system->setDefault("coreContentDefaultFile", "page.md");
- $this->system->setDefault("coreContentErrorFile", "page-error-(.*).md");
- $this->system->setDefault("coreContentExtension", ".md");
- $this->system->setDefault("coreDownloadExtension", ".download");
- $this->system->setDefault("coreSystemFile", "system.ini");
- $this->system->setDefault("coreTextFile", "text.ini");
- $this->system->setDefault("coreLogFile", "yellow.log");
+ $this->system->setDefault("coreThemeLocation", "/media/themes/");
+ $this->system->setDefault("coreMultiLanguageMode", "0");
+ $this->system->setDefault("coreDebugMode", "0");
}
public function __destruct() {
@@ -74,42 +57,53 @@ class YellowCore {
// Check requirements
public function checkRequirements() {
- $troubleshooting = PHP_SAPI!="cli" ? "<a href=\"https://datenstrom.se/yellow/help/troubleshooting\">See troubleshooting</a>." : "";
- version_compare(PHP_VERSION, "5.6", ">=") || die("Datenstrom Yellow requires PHP 5.6 or higher! $troubleshooting\n");
- extension_loaded("curl") || die("Datenstrom Yellow requires PHP curl extension! $troubleshooting\n");
- extension_loaded("gd") || die("Datenstrom Yellow requires PHP gd extension! $troubleshooting\n");
- extension_loaded("exif") || die("Datenstrom Yellow requires PHP exif extension! $troubleshooting\n");
- extension_loaded("mbstring") || die("Datenstrom Yellow requires PHP mbstring extension! $troubleshooting\n");
- extension_loaded("zip") || die("Datenstrom Yellow requires PHP zip extension! $troubleshooting\n");
+ if (!version_compare(PHP_VERSION, "7.0", ">=")) $this->exitFatalError("Datenstrom Yellow requires PHP 7.0 or higher!");
+ if (!extension_loaded("curl")) $this->exitFatalError("Datenstrom Yellow requires PHP curl extension!");
+ if (!extension_loaded("gd")) $this->exitFatalError("Datenstrom Yellow requires PHP gd extension!");
+ if (!extension_loaded("mbstring")) $this->exitFatalError("Datenstrom Yellow requires PHP mbstring extension!");
+ if (!extension_loaded("zip")) $this->exitFatalError("Datenstrom Yellow requires PHP zip extension!");
mb_internal_encoding("UTF-8");
- if (defined("DEBUG") && DEBUG>=1) {
- ini_set("display_errors", 1);
- error_reporting(E_ALL);
- }
- error_reporting(E_ALL ^ E_NOTICE); //TODO: remove later, for backwards compatibility
}
// Handle initialisation
public function load() {
- $this->system->load($this->system->get("coreSettingDirectory").$this->system->get("coreSystemFile"));
- $this->extensions->load($this->system->get("coreExtensionDirectory"));
- $this->text->load($this->system->get("coreExtensionDirectory"));
- $this->text->load($this->system->get("coreSettingDirectory"), $this->system->get("coreTextFile"), $this->system->get("language"));
- $this->lookup->detectFileSystem();
+ $this->system->load("system/extensions/yellow-system.ini");
+ $this->system->set("coreSystemFile", "yellow-system.ini");
+ $this->system->set("coreContentDirectory", "content/");
+ $this->system->set("coreMediaDirectory", $this->lookup->findMediaDirectory("coreMediaLocation"));
+ $this->system->set("coreSystemDirectory", "system/");
+ $this->system->set("coreCacheDirectory", "system/cache/");
+ $this->system->set("coreExtensionDirectory", "system/extensions/");
+ $this->system->set("coreLayoutDirectory", "system/layouts/");
+ $this->system->set("coreThemeDirectory", "system/themes/");
+ $this->system->set("coreTrashDirectory", "system/trash/");
+ list($pathInstall, $pathRoot, $pathHome) = $this->lookup->findFileSystemInformation();
+ $this->system->set("coreServerInstallDirectory", $pathInstall);
+ $this->system->set("coreServerRootDirectory", $pathRoot);
+ $this->system->set("coreServerHomeDirectory", $pathHome);
+ register_shutdown_function(array($this, "processFatalError"));
+ if ($this->system->get("coreDebugMode")>=1) {
+ ini_set("display_errors", 1);
+ error_reporting(E_ALL);
+ }
+ date_default_timezone_set($this->system->get("coreTimezone"));
+ $this->extension->load($this->system->get("coreExtensionDirectory"));
+ $this->language->load($this->system->get("coreExtensionDirectory").$this->system->get("coreLanguageFile"));
+ $this->user->load($this->system->get("coreExtensionDirectory").$this->system->get("coreUserFile"));
$this->startup();
}
- // Handle request
+ // Handle request from web browser
public function request() {
$statusCode = 0;
$this->toolbox->timerStart($time);
ob_start();
- list($scheme, $address, $base, $location, $fileName) = $this->getRequestInformation();
- $this->page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onRequest")) {
+ list($scheme, $address, $base, $location, $fileName) = $this->lookup->getRequestInformation();
+ $this->page->setRequestInformation($scheme, $address, $base, $location, $fileName, true);
+ foreach ($this->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onRequest")) {
$this->lookup->requestHandler = $key;
- $statusCode = $value["obj"]->onRequest($scheme, $address, $base, $location, $fileName);
+ $statusCode = $value["object"]->onRequest($scheme, $address, $base, $location, $fileName);
if ($statusCode!=0) break;
}
}
@@ -117,10 +111,10 @@ class YellowCore {
$this->lookup->requestHandler = "core";
$statusCode = $this->processRequest($scheme, $address, $base, $location, $fileName, true);
}
- if ($this->page->isExisting("pageError")) $statusCode = $this->processRequestError();
+ if ($this->page->isError()) $statusCode = $this->processRequestError();
ob_end_flush();
$this->toolbox->timerStop($time);
- if (defined("DEBUG") && DEBUG>=1 && $this->lookup->isContentFile($fileName)) {
+ if ($this->system->get("coreDebugMode")>=1 && ($this->lookup->isContentFile($fileName) || $this->page->isError())) {
echo "YellowCore::request status:$statusCode time:$time ms<br/>\n";
}
return $statusCode;
@@ -143,16 +137,14 @@ class YellowCore {
}
}
if ($statusCode==0) {
- $fileName = $this->lookup->findFileFromCache($location, $fileName, $cacheable && !$this->isCommandLine());
- if ($this->lookup->isContentFile($fileName) || !is_readable($fileName)) {
- $fileName = $this->readPage($scheme, $address, $base, $location, $fileName, $cacheable,
- max(is_readable($fileName) ? 200 : 404, $this->page->statusCode), $this->page->get("pageError"));
- $statusCode = $this->sendPage();
- } else {
- $statusCode = $this->sendFile(200, $fileName, true);
+ if ($this->lookup->isContentFile($fileName)) {
+ $statusCode = $this->sendPage($scheme, $address, $base, $location, $fileName, $cacheable, true);
+ } elseif (!is_string_empty($fileName)) {
+ $statusCode = $this->sendFile(200, $fileName, $cacheable);
}
+ if (!is_readable($fileName)) $this->page->error(404);
}
- if (defined("DEBUG") && DEBUG>=1 && $this->lookup->isContentFile($fileName)) {
+ if ($this->system->get("coreDebugMode")>=1 && ($this->lookup->isContentFile($fileName) || $this->page->isError())) {
echo "YellowCore::processRequest file:$fileName<br/>\n";
}
return $statusCode;
@@ -161,62 +153,72 @@ class YellowCore {
// Process request with error
public function processRequestError() {
ob_clean();
- $fileName = $this->readPage($this->page->scheme, $this->page->address, $this->page->base,
- $this->page->location, $this->page->fileName, $this->page->cacheable, $this->page->statusCode,
- $this->page->get("pageError"));
- $statusCode = $this->sendPage();
- if (defined("DEBUG") && DEBUG>=1) echo "YellowCore::processRequestError file:$fileName<br/>\n";
+ $statusCode = $this->sendPage($this->page->scheme, $this->page->address, $this->page->base,
+ $this->page->location, $this->page->fileName, false, false);
+ if ($this->system->get("coreDebugMode")>=1) echo "YellowCore::processRequestError file:".$this->page->fileName."<br/>\n";
return $statusCode;
}
- // Read page
- public function readPage($scheme, $address, $base, $location, $fileName, $cacheable, $statusCode, $pageError) {
- if ($statusCode>=400) {
- $locationError = $this->content->getHomeLocation($this->page->location).$this->system->get("coreContentSharedDirectory");
- $fileNameError = $this->lookup->findFileFromLocation($locationError, true).$this->system->get("coreContentErrorFile");
- $fileNameError = strreplaceu("(.*)", $statusCode, $fileNameError);
- if (is_file($fileNameError)) {
- $rawData = $this->toolbox->readFile($fileNameError);
- } else {
- $language = $this->lookup->findLanguageFromFile($fileName, $this->system->get("language"));
- $rawData = "---\nTitle: ".$this->text->getText("coreError${statusCode}Title", $language)."\n";
- $rawData .= "Layout: error\n---\n".$this->text->getText("coreError${statusCode}Text", $language);
- }
- $cacheable = false;
- } else {
- $rawData = $this->toolbox->readFile($fileName);
+ // Process fatal runtime error
+ public function processFatalError() {
+ $error = error_get_last();
+ if (!is_null($error) && isset($error["type"]) && ($error["type"]==E_ERROR || $error["type"]==E_PARSE)) {
+ $fileNameAbsolute = isset($error["file"]) ? $error["file"] : "";
+ $fileName = substru($fileNameAbsolute, strlenu($this->system->get("coreServerInstallDirectory")));
+ $this->toolbox->log("error", "Can't parse file '$fileName'!");
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted(500));
+ $troubleshooting = PHP_SAPI!="cli" ?
+ "<a href=\"".$this->toolbox->getTroubleshootingUrl()."\">See troubleshooting</a>." : "See ".$this->toolbox->getTroubleshootingUrl();
+ echo "<br/>\nDatenstrom Yellow stopped with fatal error. Activate the debug mode for more information. $troubleshooting\n";
}
- $this->page = new YellowPage($this);
- $this->page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $this->page->parseData($rawData, $cacheable, $statusCode, $pageError);
- $this->text->setLanguage($this->page->get("language"));
- $this->page->parseContent();
- return $fileName;
+ }
+
+ // Show error message and terminate immediately
+ public function exitFatalError($errorMessage = "") {
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted(500));
+ $troubleshooting = PHP_SAPI!="cli" ?
+ "<a href=\"".$this->toolbox->getTroubleshootingUrl()."\">See troubleshooting</a>." : "See ".$this->toolbox->getTroubleshootingUrl();
+ echo "$errorMessage $troubleshooting\n";
+ exit(1);
}
// Send page response
- public function sendPage() {
+ public function sendPage($scheme, $address, $base, $location, $fileName, $cacheable, $showSource) {
+ $rawData = $showSource ? $this->toolbox->readFile($fileName) : $this->page->getRawDataError();
+ $statusCode = max($this->page->statusCode, 200);
+ $errorMessage = $this->page->errorMessage;
+ $this->page = new YellowPage($this);
+ $this->page->setRequestInformation($scheme, $address, $base, $location, $fileName, $cacheable);
+ $this->page->parseMeta($rawData, $statusCode, $errorMessage);
+ $this->language->set($this->page->get("language"));
+ $this->page->parseContent();
$this->page->parsePage();
- $statusCode = $this->page->statusCode;
- $lastModifiedFormatted = $this->page->getHeader("Last-Modified");
- if ($statusCode==200 && $this->page->isCacheable() && $this->toolbox->isNotModified($lastModifiedFormatted)) {
- $statusCode = 304;
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- } else {
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- foreach ($this->page->headerData as $key=>$value) {
- @header("$key: $value");
- }
- if (!is_null($this->page->outputData)) echo $this->page->outputData;
- }
- if (defined("DEBUG") && DEBUG>=1) {
+ $statusCode = $this->sendData($this->page->statusCode, $this->page->headerData, $this->page->outputData);
+ if ($this->system->get("coreDebugMode")>=1) {
foreach ($this->page->headerData as $key=>$value) {
echo "YellowCore::sendPage $key: $value<br/>\n";
}
+ $language = $this->page->get("language");
$layout = $this->page->get("layout");
$theme = $this->page->get("theme");
$parser = $this->page->get("parser");
- echo "YellowCore::sendPage layout:$layout theme:$theme parser:$parser<br/>\n";
+ echo "YellowCore::sendPage language:$language layout:$layout theme:$theme parser:$parser<br/>\n";
+ }
+ return $statusCode;
+ }
+
+ // Send data response
+ public function sendData($statusCode, $headerData, $outputData) {
+ $lastModifiedFormatted = isset($headerData["Last-Modified"]) ? $headerData["Last-Modified"] : "";
+ if ($statusCode==200 && !isset($headerData["Cache-Control"]) && $this->toolbox->isNotModified($lastModifiedFormatted)) {
+ $statusCode = 304;
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
+ } else {
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
+ foreach ($headerData as $key=>$value) {
+ $this->toolbox->sendHttpHeader("$key: $value");
+ }
+ if (!is_null($outputData)) echo $outputData;
}
return $statusCode;
}
@@ -226,102 +228,91 @@ class YellowCore {
$lastModifiedFormatted = $this->toolbox->getHttpDateFormatted($this->toolbox->getFileModified($fileName));
if ($statusCode==200 && $cacheable && $this->toolbox->isNotModified($lastModifiedFormatted)) {
$statusCode = 304;
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
} else {
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- if (!$cacheable) @header("Cache-Control: no-cache, no-store");
- @header("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
- @header("Last-Modified: ".$lastModifiedFormatted);
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
+ if (!$cacheable) $this->toolbox->sendHttpHeader("Cache-Control: no-cache, no-store");
+ $this->toolbox->sendHttpHeader("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
+ $this->toolbox->sendHttpHeader("Last-Modified: ".$lastModifiedFormatted);
echo $this->toolbox->readFile($fileName);
}
return $statusCode;
}
- // Send data response
- public function sendData($statusCode, $rawData, $fileName, $cacheable) {
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- if (!$cacheable) @header("Cache-Control: no-cache, no-store");
- @header("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
- @header("Last-Modified: ".$this->toolbox->getHttpDateFormatted(time()));
- echo $rawData;
- return $statusCode;
- }
-
// Send status response
public function sendStatus($statusCode, $location = "") {
- if (!empty($location)) $this->page->clean($statusCode, $location);
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
+ if (!is_string_empty($location)) $this->page->status($statusCode, $location);
+ $this->toolbox->sendHttpHeader($this->toolbox->getHttpStatusFormatted($statusCode));
foreach ($this->page->headerData as $key=>$value) {
- @header("$key: $value");
- }
- if (defined("DEBUG") && DEBUG>=1) {
- foreach ($this->page->headerData as $key=>$value) {
- echo "YellowCore::sendStatus $key: $value<br/>\n";
- }
+ $this->toolbox->sendHttpHeader("$key: $value");
}
return $statusCode;
}
- // Handle command
+ // Handle command from command line
public function command($line = "") {
$statusCode = 0;
$this->toolbox->timerStart($time);
- list($command, $text) = $this->getCommandInformation($line);
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onCommand")) {
+ list($command, $text) = $this->lookup->getCommandInformation($line);
+ foreach ($this->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onCommand")) {
$this->lookup->commandHandler = $key;
- $statusCode = $value["obj"]->onCommand($command, $text);
+ $statusCode = $value["object"]->onCommand($command, $text);
if ($statusCode!=0) break;
}
}
+ if ($statusCode==0 && is_string_empty($command)) {
+ $lines = array();
+ foreach ($this->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onCommandHelp")) {
+ $this->lookup->commandHandler = $key;
+ $output = $value["object"]->onCommandHelp();
+ $lines = array_merge($lines, is_array($output) ? $output : array($output));
+ }
+ }
+ usort($lines, "strnatcasecmp");
+ $this->showCommandHelp($lines);
+ $statusCode = 200;
+ }
if ($statusCode==0) {
$this->lookup->commandHandler = "core";
$statusCode = 400;
echo "Yellow $command: Command not found\n";
}
$this->toolbox->timerStop($time);
- if (defined("DEBUG") && DEBUG>=1) {
+ if ($this->system->get("coreDebugMode")>=1) {
echo "YellowCore::command status:$statusCode time:$time ms<br/>\n";
}
return $statusCode<400 ? 0 : 1;
}
+ // Show command help
+ public function showCommandHelp($lines) {
+ echo "Datenstrom Yellow is for people who make small websites. https://datenstrom.se/yellow/\n";
+ $lineCounter = 0;
+ foreach ($lines as $line) {
+ echo(++$lineCounter>1 ? " " : "Syntax: ")."php yellow.php $line\n";
+ }
+ }
+
// Handle startup
public function startup() {
- if ($this->isLoaded()) {
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onStartup")) $value["obj"]->onStartup();
- }
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onUpdate")) $value["obj"]->onUpdate("startup");
+ if (isset($this->extension->data)) {
+ foreach ($this->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onStartup")) $value["object"]->onStartup();
}
}
}
// Handle shutdown
public function shutdown() {
- if ($this->isLoaded()) {
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onShutdown")) $value["obj"]->onShutdown();
+ if (isset($this->extension->data)) {
+ foreach ($this->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onShutdown")) $value["object"]->onShutdown();
}
}
}
- // Handle logging
- public function log($action, $message) {
- $statusCode = 0;
- foreach ($this->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onLog")) {
- $statusCode = $value["obj"]->onLog($action, $message);
- if ($statusCode!=0) break;
- }
- }
- if ($statusCode==0) {
- $line = date("Y-m-d H:i:s")." ".trim($action)." ".trim($message)."\n";
- $this->toolbox->appendFile($this->system->get("coreExtensionDirectory").$this->system->get("coreLogFile"), $line);
- }
- }
-
// Include layout
public function layout($name, $arguments = null) {
$this->lookup->layoutArguments = func_get_args();
@@ -332,2926 +323,3515 @@ class YellowCore {
public function getLayoutArguments($sizeMin = 9) {
return array_pad($this->lookup->layoutArguments, $sizeMin, null);
}
-
- public function getLayoutArgs($sizeMin = 9) { //TODO: remove later, for backwards compatibility
- return $this->getLayoutArguments($sizeMin);
- }
-
- // Return request information
- public function getRequestInformation($scheme = "", $address = "", $base = "") {
- if (empty($scheme) && empty($address) && empty($base)) {
- $url = $this->system->get("coreServerUrl");
- if ($url=="auto" || $this->isCommandLine()) $url = $this->toolbox->detectServerUrl();
- list($scheme, $address, $base) = $this->lookup->getUrlInformation($url);
- $this->system->set("coreServerScheme", $scheme);
- $this->system->set("coreServerAddress", $address);
- $this->system->set("coreServerBase", $base);
- if (defined("DEBUG") && DEBUG>=3) echo "YellowCore::getRequestInformation $scheme://$address$base<br/>\n";
- }
- $location = substru($this->toolbox->detectServerLocation(), strlenu($base));
- if (empty($fileName)) $fileName = $this->lookup->findFileFromSystem($location);
- if (empty($fileName)) $fileName = $this->lookup->findFileFromMedia($location);
- if (empty($fileName)) $fileName = $this->lookup->findFileFromLocation($location);
- return array($scheme, $address, $base, $location, $fileName);
- }
-
- // Return command information
- public function getCommandInformation($line = "") {
- if (empty($line)) {
- $line = $this->toolbox->getTextString(array_slice($this->toolbox->getServer("argv"), 1));
- if (defined("DEBUG") && DEBUG>=3) echo "YellowCore::getCommandInformation $line<br/>\n";
- }
- return $this->toolbox->getTextList($line, " ", 2);
- }
-
- // Return request handler
- public function getRequestHandler() {
- return $this->lookup->requestHandler;
- }
-
- // Return command handler
- public function getCommandHandler() {
- return $this->lookup->commandHandler;
- }
-
- // Check if running at command line
- public function isCommandLine() {
- return isset($this->lookup->commandHandler);
- }
-
- // Check if all extensions loaded
- public function isLoaded() {
- return isset($this->extensions->extensions);
- }
}
-
-class YellowPage {
- public $yellow; //access to API
- public $scheme; //server scheme
- public $address; //server address
- public $base; //base location
- public $location; //page location
- public $fileName; //content file name
- public $rawData; //raw data of page
- public $metaDataOffsetBytes; //meta data offset
- public $metaData; //meta data
- public $pageCollection; //page collection
- public $pageRelations; //page relations
- public $headerData; //response header
- public $outputData; //response output
- public $parser; //content parser
- public $parserData; //content data of page
- public $available; //page is available? (boolean)
- public $visible; //page is visible location? (boolean)
- public $active; //page is active location? (boolean)
- public $cacheable; //page is cacheable? (boolean)
- public $lastModified; //last modification date
- public $statusCode; //status code
+class YellowContent {
+ public $yellow; // access to API
+ public $pages; // scanned pages
+
public function __construct($yellow) {
$this->yellow = $yellow;
- $this->metaData = new YellowDataCollection();
- $this->pageCollection = new YellowPageCollection($yellow);
- $this->pageRelations = array();
- $this->headerData = array();
- }
-
- // Set request information
- public function setRequestInformation($scheme, $address, $base, $location, $fileName) {
- $this->scheme = $scheme;
- $this->address = $address;
- $this->base = $base;
- $this->location = $location;
- $this->fileName = $fileName;
+ $this->pages = array();
}
- // Parse page data
- public function parseData($rawData, $cacheable, $statusCode, $pageError = "") {
- $this->rawData = $rawData;
- $this->parser = null;
- $this->parserData = "";
- $this->available = $this->yellow->lookup->isAvailableLocation($this->location, $this->fileName);
- $this->visible = true;
- $this->active = $this->yellow->lookup->isActiveLocation($this->location, $this->yellow->page->location);
- $this->cacheable = $cacheable;
- $this->lastModified = 0;
- $this->statusCode = $statusCode;
- $this->parseMeta($pageError);
+ // Scan file system on demand
+ public function scanLocation($location) {
+ if (!isset($this->pages[$location])) {
+ $this->pages[$location] = array();
+ $scheme = $this->yellow->page->scheme;
+ $address = $this->yellow->page->address;
+ $base = $this->yellow->page->base;
+ if (is_string_empty($location)) {
+ $rootLocations = $this->yellow->lookup->findContentRootLocations();
+ foreach ($rootLocations as $rootLocation=>$rootFileName) {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($scheme, $address, $base, $rootLocation, $rootFileName, false);
+ $page->parseMeta("");
+ array_push($this->pages[$location], $page);
+ }
+ } else {
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowContent::scanLocation location:$location<br/>\n";
+ $fileNames = $this->yellow->lookup->findChildrenFromContentLocation($location);
+ foreach ($fileNames as $fileName) {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($scheme, $address, $base,
+ $this->yellow->lookup->findContentLocationFromFile($fileName), $fileName, false);
+ $page->parseMeta($this->yellow->toolbox->readFile($fileName, 4096));
+ if (strlenb($page->rawData)<4096) $page->statusCode = 200;
+ array_push($this->pages[$location], $page);
+ }
+ }
+ }
+ return $this->pages[$location];
}
-
- // Parse page data update
- public function parseDataUpdate() {
- if ($this->statusCode==0) {
- $this->rawData = $this->yellow->toolbox->readFile($this->fileName);
- $this->statusCode = 200;
- $this->parseMeta();
+
+ // Return page from, null if not found
+ public function find($location, $absoluteLocation = false) {
+ $found = false;
+ if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
+ foreach ($this->scanLocation($this->getParentLocation($location)) as $page) {
+ if ($page->location==$location) {
+ if (!$this->yellow->lookup->isRootLocation($page->location)) {
+ $found = true;
+ break;
+ }
+ }
}
+ return $found ? $page : null;
}
- // Parse page meta data
- public function parseMeta($pageError = "") {
- $this->metaData = new YellowDataCollection();
- if (!is_null($this->rawData)) {
- $this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
- $this->set("language", $this->yellow->lookup->findLanguageFromFile($this->fileName, $this->yellow->system->get("language")));
- $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
- $this->parseMetaRaw(array("sitename", "author", "layout", "theme", "parser", "status"));
- $titleHeader = ($this->location==$this->yellow->content->getHomeLocation($this->location)) ?
- $this->get("sitename") : $this->get("title")." - ".$this->get("sitename");
- if (!$this->isExisting("titleContent")) $this->set("titleContent", $this->get("title"));
- if (!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
- if (!$this->isExisting("titleHeader")) $this->set("titleHeader", $titleHeader);
- if ($this->get("status")=="unlisted") $this->visible = false;
- if ($this->get("status")=="shared") $this->available = false;
- $this->set("pageRead", $this->yellow->lookup->normaliseUrl(
- $this->yellow->system->get("coreServerScheme"),
- $this->yellow->system->get("coreServerAddress"),
- $this->yellow->system->get("coreServerBase"),
- $this->location));
- $this->set("pageEdit", $this->yellow->lookup->normaliseUrl(
- $this->yellow->system->get("coreServerScheme"),
- $this->yellow->system->get("coreServerAddress"),
- $this->yellow->system->get("coreServerBase"),
- rtrim($this->yellow->system->get("editLocation"), "/").$this->location));
- } else {
- $this->set("type", $this->yellow->toolbox->getFileType($this->fileName));
- $this->set("group", $this->yellow->toolbox->getFileGroup($this->fileName, $this->yellow->system->get("coreMediaDirectory")));
- $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
- }
- if (!empty($pageError)) $this->set("pageError", $pageError);
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParseMeta")) $value["obj"]->onParseMeta($this);
- }
+ // Return page collection with all pages
+ public function index($showInvisible = false, $multiLanguage = false, $levelMax = 0) {
+ $rootLocation = $multiLanguage ? "" : $this->getRootLocation($this->yellow->page->location);
+ return $this->getChildrenRecursive($rootLocation, $showInvisible, $levelMax);
}
- // Parse page meta data from raw data
- public function parseMetaRaw($defaultKeys) {
- foreach ($defaultKeys as $key) {
- $value = $this->yellow->system->get($key);
- if (!empty($key) && !strempty($value)) $this->set($key, $value);
- }
- if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+/s", $this->rawData, $parts)) {
- $this->metaDataOffsetBytes = strlenb($parts[0]);
- foreach (preg_split("/[\r\n]+/", $parts[2]) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (!empty($matches[1]) && !strempty($matches[2])) $this->set($matches[1], $matches[2]);
+ // Return page collection with top-level navigation
+ public function top($showInvisible = false, $showOnePager = true) {
+ $rootLocation = $this->getRootLocation($this->yellow->page->location);
+ $pages = $this->getChildren($rootLocation, $showInvisible);
+ if (count($pages)==1 && $showOnePager) {
+ $scheme = $this->yellow->page->scheme;
+ $address = $this->yellow->page->address;
+ $base = $this->yellow->page->base;
+ $one = ($pages->offsetGet(0)->location!=$this->yellow->page->location) ? $pages->offsetGet(0) : $this->yellow->page;
+ preg_match_all("/<h(\d) id=\"([^\"]+)\">(.*?)<\/h\d>/i", $one->getContentHtml(), $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ if ($match[1]==2) {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($scheme, $address, $base, $one->location."#".$match[2], $one->fileName, false);
+ $page->parseMeta("---\nTitle: $match[3]\n---\n");
+ $pages->append($page);
}
}
- } elseif (preg_match("/^(\xEF\xBB\xBF)?([^\r\n]+)[\r\n]+=+[\r\n]+/", $this->rawData, $parts)) {
- $this->metaDataOffsetBytes = strlenb($parts[0]);
- $this->set("title", $parts[2]);
}
+ return $pages;
}
- // Parse page content on demand
- public function parseContent($sizeMax = 0) {
- if (!is_object($this->parser)) {
- if ($this->yellow->extensions->isExisting($this->get("parser"))) {
- $value = $this->yellow->extensions->extensions[$this->get("parser")];
- if (method_exists($value["obj"], "onParseContentRaw")) {
- $this->parser = $value["obj"];
- $this->parserData = $this->getContent(true, $sizeMax);
- $this->parserData = preg_replace("/@pageRead/i", $this->get("pageRead"), $this->parserData);
- $this->parserData = preg_replace("/@pageEdit/i", $this->get("pageEdit"), $this->parserData);
- $this->parserData = $this->parser->onParseContentRaw($this, $this->parserData);
- $this->parserData = $this->yellow->toolbox->normaliseData($this->parserData, "html");
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParseContentText")) {
- $output = $value["obj"]->onParseContentText($this, $this->parserData);
- if (!is_null($output)) $this->parserData = $output;
- }
- }
- }
- } else {
- $this->parserData = $this->getContent(true, $sizeMax);
- $this->parserData = preg_replace("/\[yellow error\]/i", $this->get("pageError"), $this->parserData);
- }
- if (!$this->isExisting("description")) {
- $description = $this->yellow->toolbox->createTextDescription($this->parserData, 150);
- $this->set("description", !empty($description) ? $description : $this->get("title"));
+ // Return page collection with path ancestry
+ public function path($location, $absoluteLocation = false) {
+ $pages = new YellowPageCollection($this->yellow);
+ if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
+ if ($page = $this->find($location)) {
+ $pages->prepend($page);
+ for (; $parent = $page->getParent(); $page=$parent) {
+ $pages->prepend($parent);
}
- if (defined("DEBUG") && DEBUG>=3) echo "YellowPage::parseContent location:".$this->location."<br/>\n";
+ $home = $this->find($this->getHomeLocation($page->location));
+ if ($home && $home->location!=$page->location) $pages->prepend($home);
}
+ return $pages;
}
- // Parse page content shortcut
- public function parseContentShortcut($name, $text, $type) {
- $output = null;
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParseContentShortcut")) {
- $output = $value["obj"]->onParseContentShortcut($this, $name, $text, $type);
- if (!is_null($output)) break;
+ // Return page collection with multiple languages
+ public function multi($location, $absoluteLocation = false, $showInvisible = false) {
+ $pages = new YellowPageCollection($this->yellow);
+ if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
+ $locationEnd = substru($location, strlenu($this->getRootLocation($location)) - 4);
+ foreach ($this->scanLocation("") as $page) {
+ if ($content = $this->find(substru($page->location, 4).$locationEnd)) {
+ if ($content->isAvailable() && ($content->isVisible() || $showInvisible)) {
+ if (!$this->yellow->lookup->isRootLocation($content->location)) $pages->append($content);
+ }
}
}
- if (is_null($output)) {
- if ($name=="yellow" && $type=="inline") {
- $output = "Datenstrom Yellow ".YellowCore::VERSION;
- if ($text=="error") $output = $this->get("pageError");
- if ($text=="log") {
- $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreLogFile");
- $fileHandle = @fopen($fileName, "r");
- if ($fileHandle) {
- $dataBufferSize = 512;
- fseek($fileHandle, max(0, filesize($fileName) - $dataBufferSize));
- $dataBuffer = fread($fileHandle, $dataBufferSize);
- if (strlenb($dataBuffer)==$dataBufferSize) {
- $dataBuffer = ($pos = strposu($dataBuffer, "\n")) ? substru($dataBuffer, $pos+1) : $dataBuffer;
- }
- fclose($fileHandle);
- }
- $output = strreplaceu("\n", "<br />\n", htmlspecialchars($dataBuffer));
- }
+ return $pages;
+ }
+
+ // Return page collection that's empty
+ public function clean() {
+ return new YellowPageCollection($this->yellow);
+ }
+
+ // Return languages in multi language mode
+ public function getLanguages($showInvisible = false) {
+ $languages = array();
+ if ($this->yellow->system->get("coreMultiLanguageMode")) {
+ foreach ($this->scanLocation("") as $page) {
+ if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) array_push($languages, $page->get("language"));
}
}
- if (defined("DEBUG") && DEBUG>=3 && !empty($name)) echo "YellowPage::parseContentShortcut name:$name type:$type<br/>\n";
- return $output;
+ return $languages;
}
- // Parse page
- public function parsePage() {
- $this->parsePageLayout($this->get("layout"));
- if (!$this->isCacheable()) $this->setHeader("Cache-Control", "no-cache, no-store");
- if (!$this->isHeader("Content-Type")) $this->setHeader("Content-Type", "text/html; charset=utf-8");
- if (!$this->isHeader("Content-Modified")) $this->setHeader("Content-Modified", $this->getModified(true));
- if (!$this->isHeader("Last-Modified")) $this->setHeader("Last-Modified", $this->getLastModified(true));
- $fileNameTheme = $this->yellow->system->get("coreResourceDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
- if (!is_file($fileNameTheme)) {
- $this->error(500, "Theme '".$this->get("theme")."' does not exist!");
- }
- if (!is_object($this->parser)) {
- $this->error(500, "Parser '".$this->get("parser")."' does not exist!");
- }
- if (!$this->yellow->text->isLanguage($this->get("language"))) {
- $this->error(500, "Language '".$this->get("language")."' does not exist!");
- }
- if ($this->yellow->lookup->isNestedLocation($this->location, $this->fileName, true)) {
- $this->error(500, "Folder '".dirname($this->fileName)."' may not contain subfolders!");
- }
- if ($this->yellow->getRequestHandler()=="core" && $this->isExisting("redirect") && $this->statusCode==200) {
- $location = $this->yellow->lookup->normaliseLocation($this->get("redirect"), $this->location);
- $location = $this->yellow->lookup->normaliseUrl($this->scheme, $this->address, "", $location);
- $this->clean(301, $location);
- }
- if ($this->yellow->getRequestHandler()=="core" && !$this->isAvailable() && $this->statusCode==200) {
- $this->error(404);
- }
- if ($this->isExisting("pageClean")) $this->outputData = null;
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParsePageOutput")) {
- $output = $value["obj"]->onParsePageOutput($this, $this->outputData);
- if (!is_null($output)) $this->outputData = $output;
+ // Return child pages
+ public function getChildren($location, $showInvisible = false) {
+ $pages = new YellowPageCollection($this->yellow);
+ foreach ($this->scanLocation($location) as $page) {
+ if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
+ if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
}
}
+ return $pages;
}
- // Parse page layout
- public function parsePageLayout($name) {
- $this->outputData = null;
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParsePageLayout")) {
- $value["obj"]->onParsePageLayout($this, $name);
+ // Return child pages recursively
+ public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
+ --$levelMax;
+ $pages = new YellowPageCollection($this->yellow);
+ foreach ($this->scanLocation($location) as $page) {
+ if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
+ if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
+ }
+ if (!$this->yellow->lookup->isFileLocation($page->location) && $levelMax!=0) {
+ $pages->merge($this->getChildrenRecursive($page->location, $showInvisible, $levelMax));
}
}
- if (is_null($this->outputData)) {
- ob_start();
- $this->includeLayout($name);
- $this->outputData = ob_get_contents();
- ob_end_clean();
- }
- }
-
- // Include page layout
- public function includeLayout($name) {
- $fileNameLayoutNormal = $this->yellow->system->get("coreLayoutDirectory").$this->yellow->lookup->normaliseName($name).".html";
- $fileNameLayoutTheme = $this->yellow->system->get("coreLayoutDirectory").
- $this->yellow->lookup->normaliseName($this->get("theme"))."-".$this->yellow->lookup->normaliseName($name).".html";
- if (is_file($fileNameLayoutTheme)) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowPage::includeLayout file:$fileNameLayoutTheme<br>\n";
- $this->setLastModified(filemtime($fileNameLayoutTheme));
- require($fileNameLayoutTheme);
- } elseif (is_file($fileNameLayoutNormal)) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowPage::includeLayout file:$fileNameLayoutNormal<br>\n";
- $this->setLastModified(filemtime($fileNameLayoutNormal));
- require($fileNameLayoutNormal);
- } else {
- $this->error(500, "Layout '$name' does not exist!");
- echo "Layout error<br/>\n";
- }
+ return $pages;
}
- // Set page setting
- public function set($key, $value) {
- $this->metaData[$key] = $value;
+ // Return shared pages
+ public function getShared($location) {
+ $pages = new YellowPageCollection($this->yellow);
+ $sharedLocation = $this->getHomeLocation($location)."shared/";
+ return $pages->merge($this->scanLocation($sharedLocation));
}
- // Return page setting
- public function get($key) {
- return $this->isExisting($key) ? $this->metaData[$key] : "";
+ // Return root location
+ public function getRootLocation($location) {
+ $rootLocation = "root/";
+ if ($this->yellow->system->get("coreMultiLanguageMode")) {
+ foreach ($this->scanLocation("") as $page) {
+ $token = substru($page->location, 4);
+ if ($token!="/" && substru($location, 0, strlenu($token))==$token) {
+ $rootLocation = "root$token";
+ break;
+ }
+ }
+ }
+ return $rootLocation;
}
- // Return page setting, HTML encoded
- public function getHtml($key) {
- return htmlspecialchars($this->get($key));
+ // Return home location
+ public function getHomeLocation($location) {
+ return substru($this->getRootLocation($location), 4);
}
- // Return page setting as language specific date format
- public function getDate($key, $format = "") {
- if (!empty($format)) {
- $format = $this->yellow->text->get($format);
- } else {
- $format = $this->yellow->text->get("coreDateFormatMedium");
+ // Return parent location
+ public function getParentLocation($location) {
+ $parentLocation = "";
+ $token = rtrim(substru($this->getRootLocation($location), 4), "/");
+ if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
+ if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
}
- return $this->yellow->text->getDateFormatted(strtotime($this->get($key)), $format);
+ if (is_string_empty($parentLocation)) $parentLocation = "root$token/";
+ return $parentLocation;
}
-
- // Return page setting as language specific date format, HTML encoded
- public function getDateHtml($key, $format = "") {
- return htmlspecialchars($this->getDate($key, $format));
+
+ // Return top-level location
+ public function getParentTopLocation($location) {
+ $parentTopLocation = "";
+ $token = rtrim(substru($this->getRootLocation($location), 4), "/");
+ if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
+ if (is_string_empty($parentTopLocation)) $parentTopLocation = "$token/";
+ return $parentTopLocation;
+ }
+}
+
+class YellowMedia {
+ public $yellow; // access to API
+ public $files; // scanned files
+
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
+ $this->files = array();
}
- // Return page setting as language specific date format, relative to today
- public function getDateRelative($key, $format = "", $daysLimit = 30) {
- if (!empty($format)) {
- $format = $this->yellow->text->get($format);
- } else {
- $format = $this->yellow->text->get("coreDateFormatMedium");
+ // Scan file system on demand
+ public function scanLocation($location) {
+ if (!isset($this->files[$location])) {
+ $this->files[$location] = array();
+ $scheme = $this->yellow->page->scheme;
+ $address = $this->yellow->page->address;
+ $base = $this->yellow->system->get("coreServerBase");
+ if (is_string_empty($location)) {
+ $fileNames = array($this->yellow->system->get("coreMediaDirectory"));
+ } else {
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowMedia::scanLocation location:$location<br/>\n";
+ $fileNames = $this->yellow->lookup->findChildrenFromMediaLocation($location);
+ }
+ foreach ($fileNames as $fileName) {
+ $file = new YellowPage($this->yellow);
+ $file->setRequestInformation($scheme, $address, $base,
+ $this->yellow->lookup->findMediaLocationFromFile($fileName), $fileName, false);
+ $file->parseMeta(null);
+ array_push($this->files[$location], $file);
+ }
}
- return $this->yellow->text->getDateRelative(strtotime($this->get($key)), $format, $daysLimit);
+ return $this->files[$location];
}
- // Return page setting as language specific date format, relative to today, HTML encoded
- public function getDateRelativeHtml($key, $format = "", $daysLimit = 30) {
- return htmlspecialchars($this->getDateRelative($key, $format, $daysLimit));
+ // Return page with media file information, null if not found
+ public function find($location, $absoluteLocation = false) {
+ $found = false;
+ if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->system->get("coreServerBase")));
+ foreach ($this->scanLocation($this->getParentLocation($location)) as $file) {
+ if ($file->location==$location) {
+ if ($this->yellow->lookup->isFileLocation($file->location)) {
+ $found = true;
+ break;
+ }
+ }
+ }
+ return $found ? $file : null;
}
- // Return page setting as custom date format
- public function getDateFormatted($key, $format) {
- return $this->yellow->text->getDateFormatted(strtotime($this->get($key)), $format);
+ // Return page collection with all media files
+ public function index($showInvisible = false, $multiPass = false, $levelMax = 0) {
+ return $this->getChildrenRecursive("", $showInvisible, $levelMax);
}
- // Return page setting as custom date format, HTML encoded
- public function getDateFormattedHtml($key, $format) {
- return htmlspecialchars($this->getDateFormatted($key, $format));
+ // Return page collection that's empty
+ public function clean() {
+ return new YellowPageCollection($this->yellow);
}
- // Return page content, HTML encoded or raw format
- public function getContent($rawFormat = false, $sizeMax = 0) {
- if ($rawFormat) {
- $this->parseDataUpdate();
- $text = substrb($this->rawData, $this->metaDataOffsetBytes);
- } else {
- $this->parseContent($sizeMax);
- $text = $this->parserData;
+ // Return child files
+ public function getChildren($location, $showInvisible = false) {
+ $files = new YellowPageCollection($this->yellow);
+ foreach ($this->scanLocation($location) as $file) {
+ if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
+ if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
+ }
}
- return $sizeMax ? substrb($text, 0, $sizeMax) : $text;
- }
-
- // Return parent page, null if none
- public function getParent() {
- $parentLocation = $this->yellow->content->getParentLocation($this->location);
- return $this->yellow->content->find($parentLocation);
+ return $files;
}
- // Return top-level parent page, null if none
- public function getParentTop($homeFallback = false) {
- $parentTopLocation = $this->yellow->content->getParentTopLocation($this->location);
- if (!$this->yellow->content->find($parentTopLocation) && $homeFallback) {
- $parentTopLocation = $this->yellow->content->getHomeLocation($this->location);
+ // Return child files recursively
+ public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
+ --$levelMax;
+ $files = new YellowPageCollection($this->yellow);
+ foreach ($this->scanLocation($location) as $file) {
+ if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
+ if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
+ }
+ if (!$this->yellow->lookup->isFileLocation($file->location) && $levelMax!=0) {
+ $files->merge($this->getChildrenRecursive($file->location, $showInvisible, $levelMax));
+ }
}
- return $this->yellow->content->find($parentTopLocation);
- }
-
- // Return page collection with pages on the same level
- public function getSiblings($showInvisible = false) {
- $parentLocation = $this->yellow->content->getParentLocation($this->location);
- return $this->yellow->content->getChildren($parentLocation, $showInvisible);
+ return $files;
}
- // Return page collection with child pages
- public function getChildren($showInvisible = false) {
- return $this->yellow->content->getChildren($this->location, $showInvisible);
+ // Return home location
+ public function getHomeLocation($location) {
+ return $this->yellow->system->get("coreMediaLocation");
}
- // Return page collection with sub pages
- public function getChildrenRecursive($showInvisible = false, $levelMax = 0) {
- return $this->yellow->content->getChildrenRecursive($this->location, $showInvisible, $levelMax);
+ // Return parent location
+ public function getParentLocation($location) {
+ $token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
+ if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
+ if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
+ }
+ if (is_string_empty($parentLocation)) $parentLocation = "";
+ return $parentLocation;
}
- // Set page collection with additional pages
- public function setPages($pages) {
- $this->pageCollection = $pages;
+ // Return top-level location
+ public function getParentTopLocation($location) {
+ $token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
+ if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
+ if (is_string_empty($parentTopLocation)) $parentTopLocation = "$token/";
+ return $parentTopLocation;
}
+}
- // Return page collection with additional pages
- public function getPages() {
- return $this->pageCollection;
- }
+class YellowSystem {
+ public $yellow; // access to API
+ public $modified; // system modification date
+ public $settings; // system settings
+ public $settingsDefaults; // system settings defaults
- // Set related page
- public function setPage($key, $page) {
- $this->pageRelations[$key] = $page;
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
+ $this->modified = 0;
+ $this->settings = new YellowArray();
+ $this->settingsDefaults = new YellowArray();
}
- // Return related page
- public function getPage($key) {
- return isset($this->pageRelations[$key]) ? $this->pageRelations[$key] : $this;
+ // Load system settings from file
+ public function load($fileName) {
+ $this->modified = $this->yellow->toolbox->getFileModified($fileName);
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $this->settings = $this->yellow->toolbox->getTextSettings($fileData, "");
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowSystem::load file:$fileName<br/>\n";
+ if ($this->yellow->system->get("coreDebugMode")>=3) {
+ foreach ($this->settings as $key=>$value) {
+ echo "YellowSystem::load ".ucfirst($key).":$value<br/>\n";
+ }
+ }
}
- // Return page URL
- public function getUrl() {
- return $this->yellow->lookup->normaliseUrl($this->scheme, $this->address, $this->base, $this->location);
+ // Save system settings to file
+ public function save($fileName, $settings) {
+ $this->modified = time();
+ $settingsNew = new YellowArray();
+ foreach ($settings as $key=>$value) {
+ if (!is_string_empty($key) && !is_string_empty($value)) {
+ $this->set($key, $value);
+ $settingsNew[$key] = $value;
+ }
+ }
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $fileData = $this->yellow->toolbox->setTextSettings($fileData, "", "", $settingsNew);
+ return $this->yellow->toolbox->createFile($fileName, $fileData);
}
- // Return page base
- public function getBase($multiLanguage = false) {
- return $multiLanguage ? rtrim($this->base.$this->yellow->content->getHomeLocation($this->location), "/") : $this->base;
+ // Set default system setting
+ public function setDefault($key, $value) {
+ $this->settingsDefaults[$key] = $value;
+ }
+
+ // Set default system settings
+ public function setDefaults($lines) {
+ foreach ($lines as $line) {
+ if (preg_match("/^\#/", $line)) continue;
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $this->settingsDefaults[$matches[1]] = $matches[2];
+ }
+ }
+ }
}
- // Return page location
- public function getLocation($absoluteLocation = false) {
- return $absoluteLocation ? $this->base.$this->location : $this->location;
+ // Set system setting
+ public function set($key, $value) {
+ $this->settings[$key] = $value;
}
- // Set page request argument
- public function setRequest($key, $value) {
- $_REQUEST[$key] = $value;
+ // Return system setting
+ public function get($key) {
+ if (isset($this->settings[$key])) {
+ $value = $this->settings[$key];
+ } else {
+ $value = isset($this->settingsDefaults[$key]) ? $this->settingsDefaults[$key] : "";
+ }
+ return $value;
}
- // Return page request argument
- public function getRequest($key) {
- return isset($_REQUEST[$key]) ? $_REQUEST[$key] : "";
+ // Return system setting, HTML encoded
+ public function getHtml($key) {
+ return htmlspecialchars($this->get($key));
}
- // Return page request argument, HTML encoded
- public function getRequestHtml($key) {
- return htmlspecialchars($this->getRequest($key));
+ // Return different value for system setting
+ public function getDifferent($key) {
+ $array = array_diff($this->getAvailable($key), array($this->get($key)));
+ return reset($array);
}
+
+ // Return available values for system setting
+ public function getAvailable($key) {
+ $values = array();
+ $valueDefault = isset($this->settingsDefaults[$key]) ? $this->settingsDefaults[$key] : "";
+ if ($key=="email") {
+ foreach ($this->yellow->user->settings as $userKey=>$userValue) {
+ array_push($values, $userKey);
+ }
+ } elseif ($key=="language") {
+ foreach ($this->yellow->language->settings as $languageKey=>$languageValue) {
+ array_push($values, $languageKey);
+ }
+ } elseif ($key=="layout") {
+ $path = $this->yellow->system->get("coreLayoutDirectory");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.html$/", true, false, false) as $entry) {
+ array_push($values, lcfirst(substru($entry, 0, -5)));
+ }
+ } elseif ($key=="theme") {
+ $path = $this->yellow->system->get("coreThemeDirectory");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.css$/", true, false, false) as $entry) {
+ array_push($values, lcfirst(substru($entry, 0, -4)));
+ }
+ }
+ return !is_array_empty($values) ? $values : array($valueDefault);
+ }
+ public function getValues($key) { return $this->getAvailable($key); } //TODO: remove later, for backwards compatibility
- // Set page response header
- public function setHeader($key, $value) {
- $this->headerData[$key] = $value;
+ // Return system settings
+ public function getSettings($filterStart = "", $filterEnd = "") {
+ $settings = array();
+ if (is_string_empty($filterStart) && is_string_empty($filterEnd)) {
+ $settings = array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy());
+ } else {
+ foreach (array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy()) as $key=>$value) {
+ if (!is_string_empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $settings[$key] = $value;
+ if (!is_string_empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $settings[$key] = $value;
+ }
+ }
+ return $settings;
}
- // Return page response header
- public function getHeader($key) {
- return $this->isHeader($key) ? $this->headerData[$key] : "";
+ // Return system settings modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
- // Return page extra data
- public function getExtra($name) {
- $output = "";
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onParsePageExtra")) {
- $outputExtension = $value["obj"]->onParsePageExtra($this, $name);
- if (!is_null($outputExtension)) $output .= $outputExtension;
+ // Check if system setting exists
+ public function isExisting($key) {
+ return isset($this->settings[$key]);
+ }
+}
+
+class YellowLanguage {
+ public $yellow; // access to API
+ public $modified; // language modification date
+ public $settings; // language settings
+ public $settingsDefaults; // language settings defaults
+ public $language; // current language
+
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
+ $this->modified = 0;
+ $this->settings = new YellowArray();
+ $this->settingsDefaults = new YellowArray();
+ $this->language = "";
+ }
+
+ // Load language settings from file
+ public function load($fileName) {
+ $this->modified = $this->yellow->toolbox->getFileModified($fileName);
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $settings = $this->yellow->toolbox->getTextSettings($fileData, "language");
+ foreach ($settings as $language=>$block) {
+ if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
+ foreach ($block as $key=>$value) {
+ $this->settings[$language][$key] = $value;
}
}
- if ($name=="header") {
- $fileNameTheme = $this->yellow->system->get("coreResourceDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
- if (is_file($fileNameTheme)) {
- $locationTheme = $this->yellow->system->get("coreServerBase").
- $this->yellow->system->get("coreResourceLocation").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
- $output .= "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"$locationTheme\" />\n";
- }
- $fileNameScript = $this->yellow->system->get("coreResourceDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".js";
- if (is_file($fileNameScript)) {
- $locationScript = $this->yellow->system->get("coreServerBase").
- $this->yellow->system->get("coreResourceLocation").$this->yellow->lookup->normaliseName($this->get("theme")).".js";
- $output .= "<script type=\"text/javascript\" src=\"$locationScript\"></script>\n";
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowLanguage::load file:$fileName<br/>\n";
+ foreach ($this->settings->getArrayCopy() as $key=>$value) {
+ if (!isset($this->settings[$key]["languageDescription"])) {
+ unset($this->settings[$key]);
}
}
- return $output;
+ $callback = function ($a, $b) {
+ return strnatcmp($a["languageDescription"], $b["languageDescription"]);
+ };
+ $this->settings->uasort($callback);
}
- // Set page response output
- public function setOutput($output) {
- $this->outputData = $output;
+ // Set current language
+ public function set($language) {
+ $this->language = $language;
}
- // Return page modification date, Unix time or HTTP format
- public function getModified($httpFormat = false) {
- $modified = strtotime($this->get("modified"));
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
+ // Set default language setting
+ public function setDefault($key, $value, $language) {
+ if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
+ $this->settings[$language][$key] = $value;
+ $this->settingsDefaults[$key] = true;
}
- // Set last modification date, Unix time
- public function setLastModified($modified) {
- $this->lastModified = max($this->lastModified, $modified);
+ // Set default language settings
+ public function setDefaults($lines) {
+ $language = "";
+ foreach ($lines as $line) {
+ if (preg_match("/^\#/", $line)) continue;
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])=="language" && !is_string_empty($matches[2])) {
+ $language = $matches[2];
+ if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
+ }
+ if (!is_string_empty($language) && !is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $this->settings[$language][$matches[1]] = $matches[2];
+ $this->settingsDefaults[$matches[1]] = true;
+ }
+ }
+ }
}
- // Return last modification date, Unix time or HTTP format
- public function getLastModified($httpFormat = false) {
- $lastModified = max($this->lastModified, $this->getModified(), $this->pageCollection->getModified(),
- $this->yellow->system->getModified(), $this->yellow->text->getModified(), $this->yellow->extensions->getModified());
- foreach ($this->pageRelations as $page) $lastModified = max($lastModified, $page->getModified());
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($lastModified) : $lastModified;
+ // Set language setting
+ public function setText($key, $value, $language) {
+ if (!isset($this->settings[$language])) $this->settings[$language] = new YellowArray();
+ $this->settings[$language][$key] = $value;
}
- // Return page status code, number or HTTP format
- public function getStatusCode($httpFormat = false) {
- $statusCode = $this->statusCode;
- if ($httpFormat) {
- $statusCode = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
- if ($this->isExisting("pageError")) $statusCode .= ": ".$this->get("pageError");
+ // Return language setting
+ public function getText($key, $language = "") {
+ if (is_string_empty($language)) $language = $this->language;
+ return $this->isText($key, $language) ? $this->settings[$language][$key] : "[$key]";
+ }
+
+ // Return language setting, HTML encoded
+ public function getTextHtml($key, $language = "") {
+ return htmlspecialchars($this->getText($key, $language));
+ }
+
+ // Return text as language specific date, convert to one of the standard formats
+ public function getDateStandard($text, $language = "") {
+ if (preg_match("/^\d+$/", $text)) {
+ $output = $text;
+ } elseif (preg_match("/^\d+\-\d+$/", $text)) {
+ $format = $this->getText("coreDateFormatShort", $language);
+ $output = $this->getDateFormatted(strtotime($text), $format, $language);
+ } elseif (preg_match("/^\d+\-\d+\-\d+$/", $text)) {
+ $format = $this->getText("coreDateFormatMedium", $language);
+ $output = $this->getDateFormatted(strtotime($text), $format, $language);
+ } else {
+ $format = $this->getText("coreDateFormatLong", $language);
+ $output = $this->getDateFormatted(strtotime($text), $format, $language);
}
- return $statusCode;
+ return $output;
}
- // Respond with error page
- public function error($statusCode, $pageError = "") {
- if (!$this->isExisting("pageError") && $statusCode>0) {
- $this->statusCode = $statusCode;
- $this->set("pageError", empty($pageError) ? "Layout error!" : $pageError);
+ // Return Unix time as date, relative to today
+ public function getDateRelative($timestamp, $format, $daysLimit, $language = "") {
+ $timeDifference = time() - $timestamp;
+ $days = abs(intval($timeDifference/86400));
+ $key = $timeDifference>=0 ? "coreDatePast" : "coreDateFuture";
+ $tokens = preg_split("/\s*,\s*/", $this->getText($key, $language));
+ if (count($tokens)>=8) {
+ if ($days<=$daysLimit || $daysLimit==0) {
+ if ($days==0) {
+ $output = $tokens[0];
+ } elseif ($days==1) {
+ $output = $tokens[1];
+ } elseif ($days>=2 && $days<=29) {
+ $output = preg_replace("/@x/i", $days, $tokens[2]);
+ } elseif ($days>=30 && $days<=59) {
+ $output = $tokens[3];
+ } elseif ($days>=60 && $days<=364) {
+ $output = preg_replace("/@x/i", intval($days/30), $tokens[4]);
+ } elseif ($days>=365 && $days<=729) {
+ $output = $tokens[5];
+ } else {
+ $output = preg_replace("/@x/i", intval($days/365.25), $tokens[6]);
+ }
+ } else {
+ $output = preg_replace("/@x/i", $this->getDateFormatted($timestamp, $format, $language), $tokens[7]);
+ }
+ } else {
+ $output = "[$key]";
}
+ return $output;
}
- // Respond with status code, no page content
- public function clean($statusCode, $location = "") {
- if (!$this->isExisting("pageClean") && $statusCode>0) {
- $this->statusCode = $statusCode;
- $this->lastModified = 0;
- $this->headerData = array();
- if (!empty($location)) {
- $this->setHeader("Location", $location);
- $this->setHeader("Cache-Control", "no-cache, no-store");
+ // Return Unix time as date
+ public function getDateFormatted($timestamp, $format, $language = "") {
+ $dateMonthsNominative = preg_split("/\s*,\s*/", $this->getText("coreDateMonthsNominative", $language));
+ $dateMonthsGenitive = preg_split("/\s*,\s*/", $this->getText("coreDateMonthsGenitive", $language));
+ $dateWeekdays = preg_split("/\s*,\s*/", $this->getText("coreDateWeekdays", $language));
+ $monthNominative = $dateMonthsNominative[date("n", $timestamp) - 1];
+ $monthGenitive = $dateMonthsGenitive[date("n", $timestamp) - 1];
+ $weekday = $dateWeekdays[date("N", $timestamp) - 1];
+ $timeZone = $this->yellow->system->get("coreTimezone");
+ $timeZoneHelper = new DateTime("now", new DateTimeZone($timeZone));
+ $timeZoneOffset = $timeZoneHelper->getOffset();
+ $timeZoneAbbreviation = "GMT".($timeZoneOffset<0 ? "-" : "+").abs(intval($timeZoneOffset/3600));
+ $format = preg_replace("/(?<!\\\)F/", addcslashes($monthNominative, "A..Za..z"), $format);
+ $format = preg_replace("/(?<!\\\)V/", addcslashes($monthGenitive, "A..Za..z"), $format);
+ $format = preg_replace("/(?<!\\\)M/", addcslashes(substru($monthNominative, 0, 3), "A..Za..z"), $format);
+ $format = preg_replace("/(?<!\\\)D/", addcslashes(substru($weekday, 0, 3), "A..Za..z"), $format);
+ $format = preg_replace("/(?<!\\\)l/", addcslashes($weekday, "A..Za..z"), $format);
+ $format = preg_replace("/(?<!\\\)T/", addcslashes($timeZoneAbbreviation, "A..Za..z"), $format);
+ return date($format, $timestamp);
+ }
+
+ // Return language settings
+ public function getSettings($filterStart = "", $filterEnd = "", $language = "") {
+ $settings = array();
+ if (is_string_empty($language)) $language = $this->language;
+ if (isset($this->settings[$language])) {
+ if (is_string_empty($filterStart) && is_string_empty($filterEnd)) {
+ $settings = $this->settings[$language]->getArrayCopy();
+ } else {
+ foreach ($this->settings[$language] as $key=>$value) {
+ if (!is_string_empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $settings[$key] = $value;
+ if (!is_string_empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $settings[$key] = $value;
+ }
}
- $this->set("pageClean", (string)$statusCode);
}
+ return $settings;
}
- // Check if page is available
- public function isAvailable() {
- return $this->available;
+ // Return language settings modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
- // Check if page is visible
- public function isVisible() {
- return $this->visible;
+ // Check if language setting exists
+ public function isText($key, $language = "") {
+ if (is_string_empty($language)) $language = $this->language;
+ return isset($this->settings[$language]) && isset($this->settings[$language][$key]);
}
- // Check if page is within current HTTP request
- public function isActive() {
- return $this->active;
+ // Check if language exists
+ public function isExisting($language) {
+ return isset($this->settings[$language]);
}
+}
+
+class YellowUser {
+ public $yellow; // access to API
+ public $modified; // user modification date
+ public $settings; // user settings
+ public $email; // current email
- // Check if page is cacheable
- public function isCacheable() {
- return $this->cacheable;
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
+ $this->modified = 0;
+ $this->settings = new YellowArray();
+ $this->email = "";
}
- // Check if page with error
- public function isError() {
- return $this->statusCode>=400;
+ // Load user settings from file
+ public function load($fileName) {
+ $this->modified = $this->yellow->toolbox->getFileModified($fileName);
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $this->settings = $this->yellow->toolbox->getTextSettings($fileData, "email");
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUser::load file:$fileName<br/>\n";
+ }
+
+ // Save user settings to file
+ public function save($fileName, $email, $settings) {
+ $this->modified = time();
+ $settingsNew = new YellowArray();
+ $settingsNew["email"] = $email;
+ foreach ($settings as $key=>$value) {
+ if (!is_string_empty($key) && !is_string_empty($value)) {
+ $this->setUser($key, $value, $email);
+ $settingsNew[$key] = $value;
+ }
+ }
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $fileData = $this->yellow->toolbox->setTextSettings($fileData, "email", $email, $settingsNew);
+ return $this->yellow->toolbox->createFile($fileName, $fileData);
}
- // Check if page setting exists
- public function isExisting($key) {
- return isset($this->metaData[$key]);
+ // Remove user settings from file
+ public function remove($fileName, $email) {
+ $this->modified = time();
+ if (isset($this->settings[$email])) unset($this->settings[$email]);
+ $fileData = $this->yellow->toolbox->readFile($fileName);
+ $fileData = $this->yellow->toolbox->unsetTextSettings($fileData, "email", $email);
+ return $this->yellow->toolbox->createFile($fileName, $fileData);
}
- // Check if request argument exists
- public function isRequest($key) {
- return isset($_REQUEST[$key]);
+ // Set current email
+ public function set($email) {
+ $this->email = $email;
}
- // Check if response header exists
- public function isHeader($key) {
- return isset($this->headerData[$key]);
+ // Set user setting
+ public function setUser($key, $value, $email) {
+ if (!isset($this->settings[$email])) $this->settings[$email] = new YellowArray();
+ $this->settings[$email][$key] = $value;
}
- // Check if related page exists
- public function isPage($key) {
- return isset($this->pageRelations[$key]);
+ // Return user setting
+ public function getUser($key, $email = "") {
+ if (is_string_empty($email)) $email = $this->email;
+ return isset($this->settings[$email]) && isset($this->settings[$email][$key]) ? $this->settings[$email][$key] : "";
}
-}
-class YellowDataCollection extends ArrayObject {
- public function __construct() {
- parent::__construct(array());
+ // Return user setting, HTML encoded
+ public function getUserHtml($key, $email = "") {
+ return htmlspecialchars($this->getUser($key, $email));
}
-
- // Return array element
- public function offsetGet($key) {
- if (is_string($key)) $key = lcfirst($key);
- return parent::offsetGet($key);
+
+ // Return user settings
+ public function getSettings($email = "") {
+ $settings = array();
+ if (is_string_empty($email)) $email = $this->email;
+ if (isset($this->settings[$email])) $settings = $this->settings[$email]->getArrayCopy();
+ return $settings;
}
- // Set array element
- public function offsetSet($key, $value) {
- if (is_string($key)) $key = lcfirst($key);
- parent::offsetSet($key, $value);
+ // Return user settings modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
- // Remove array element
- public function offsetUnset($key) {
- if (is_string($key)) $key = lcfirst($key);
- parent::offsetUnset($key);
+ // Check if user setting exists
+ public function isUser($key, $email = "") {
+ if (is_string_empty($email)) $email = $this->email;
+ return isset($this->settings[$email]) && isset($this->settings[$email][$key]);
}
- // Check if array element exists
- public function offsetExists($key) {
- if (is_string($key)) $key = lcfirst($key);
- return parent::offsetExists($key);
+ // Check if user exists
+ public function isExisting($email) {
+ return isset($this->settings[$email]);
}
}
-
-class YellowPageCollection extends ArrayObject {
- public $yellow; //access to API
- public $filterValue; //current page filter value
- public $paginationNumber; //current page number in pagination
- public $paginationCount; //highest page number in pagination
+class YellowExtension {
+ public $yellow; // access to API
+ public $modified; // extension modification date
+ public $data; // extension data
+
public function __construct($yellow) {
- parent::__construct(array());
$this->yellow = $yellow;
+ $this->modified = 0;
+ $this->data = array();
}
- // Filter page collection by setting
- public function filter($key, $value, $exactMatch = true) {
- $array = array();
- $value = strreplaceu(" ", "-", strtoloweru($value));
- $valueLength = strlenu($value);
- $this->filterValue = "";
- foreach ($this->getArrayCopy() as $page) {
- if ($page->isExisting($key)) {
- foreach (preg_split("/\s*,\s*/", $page->get($key)) as $pageValue) {
- $pageValueLength = $exactMatch ? strlenu($pageValue) : $valueLength;
- if ($value==substru(strreplaceu(" ", "-", strtoloweru($pageValue)), 0, $pageValueLength)) {
- if (empty($this->filterValue)) $this->filterValue = substru($pageValue, 0, $pageValueLength);
- array_push($array, $page);
- break;
- }
- }
- }
+ // Load extensions
+ public function load($path) {
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.php$/", true, false) as $entry) {
+ $this->modified = max($this->modified, filemtime($entry));
+ require_once($entry);
+ $name = $this->yellow->lookup->normaliseName(basename($entry), true, true);
+ $this->register(lcfirst($name), "Yellow".ucfirst($name));
+ if ($this->yellow->system->get("coreDebugMode")>=3) echo "YellowExtension::load file:$entry<br/>\n";
}
- $this->exchangeArray($array);
- return $this;
- }
-
- // Filter page collection by file name
- public function match($regex = "/.*/") {
- $array = array();
- foreach ($this->getArrayCopy() as $page) {
- if (preg_match($regex, $page->fileName)) array_push($array, $page);
- }
- $this->exchangeArray($array);
- return $this;
- }
-
- // Sort page collection by setting
- public function sort($key, $ascendingOrder = true) {
- $array = $this->getArrayCopy();
- $sortIndex = 0;
- foreach ($array as $page) {
- $page->set("sortindex", ++$sortIndex);
- }
- $callback = function ($a, $b) use ($key, $ascendingOrder) {
- $result = $ascendingOrder ?
- strnatcasecmp($a->get($key), $b->get($key)) :
- strnatcasecmp($b->get($key), $a->get($key));
- return $result==0 ? $a->get("sortindex") - $b->get("sortindex") : $result;
+ $callback = function ($a, $b) {
+ return $a["priority"] - $b["priority"];
};
- usort($array, $callback);
- $this->exchangeArray($array);
- return $this;
+ uasort($this->data, $callback);
+ foreach ($this->data as $key=>$value) {
+ if (method_exists($this->data[$key]["object"], "onLoad")) $this->data[$key]["object"]->onLoad($this->yellow);
+ }
}
- // Sort page collection by settings similarity
- public function similar($page, $ascendingOrder = false) {
- $location = $page->location;
- $keywords = strtoloweru($page->get("title").",".$page->get("tag").",".$page->get("author"));
- $tokens = array_unique(array_filter(preg_split("/[,\s\(\)\+\-]/", $keywords), "strlen"));
- if (!empty($tokens)) {
- $array = array();
- foreach ($this->getArrayCopy() as $page) {
- $searchScore = 0;
- foreach ($tokens as $token) {
- if (stristr($page->get("title"), $token)) $searchScore += 10;
- if (stristr($page->get("tag"), $token)) $searchScore += 5;
- if (stristr($page->get("author"), $token)) $searchScore += 2;
- }
- if ($page->location!=$location) {
- $page->set("searchscore", $searchScore);
- array_push($array, $page);
- }
- }
- $this->exchangeArray($array);
- $this->sort("modified", $ascendingOrder)->sort("searchscore", $ascendingOrder);
+ // Register extension
+ public function register($key, $class) {
+ if (!$this->isExisting($key) && class_exists($class)) {
+ $this->data[$key] = array();
+ $this->data[$key]["object"] = $class=="YellowCore" ? new stdClass : new $class;
+ $this->data[$key]["class"] = $class;
+ $this->data[$key]["version"] = defined("$class::VERSION") ? $class::VERSION : 0;
+ $this->data[$key]["priority"] = defined("$class::PRIORITY") ? $class::PRIORITY : count($this->data) + 10;
}
- return $this;
- }
-
- // Calculate union, merge page collection
- public function merge($input) {
- $this->exchangeArray(array_merge($this->getArrayCopy(), (array)$input));
- return $this;
}
- // Calculate intersection, remove pages that are not present in another page collection
- public function intersect($input) {
- $callback = function ($a, $b) {
- return strcmp(spl_object_hash($a), spl_object_hash($b));
- };
- $this->exchangeArray(array_uintersect($this->getArrayCopy(), (array)$input, $callback));
- return $this;
- }
-
- // Calculate difference, remove pages that are present in another page collection
- public function diff($input) {
- $callback = function ($a, $b) {
- return strcmp(spl_object_hash($a), spl_object_hash($b));
- };
- $this->exchangeArray(array_udiff($this->getArrayCopy(), (array)$input, $callback));
- return $this;
+ // Return extension
+ public function get($key) {
+ return $this->data[$key]["object"];
}
- // Append to end of page collection
- public function append($page) {
- parent::append($page);
- return $this;
+ // Return extensions modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
}
- // Prepend to start of page collection
- public function prepend($page) {
- $array = $this->getArrayCopy();
- array_unshift($array, $page);
- $this->exchangeArray($array);
- return $this;
+ // Check if extension exists
+ public function isExisting($key) {
+ return isset($this->data[$key]);
}
+}
+
+class YellowLookup {
+ public $yellow; // access to API
+ public $requestHandler; // request handler name
+ public $commandHandler; // command handler name
+ public $layoutArguments; // layout arguments
- // Limit the number of pages in page collection
- public function limit($pagesMax) {
- $this->exchangeArray(array_slice($this->getArrayCopy(), 0, $pagesMax));
- return $this;
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
}
- // Reverse page collection
- public function reverse() {
- $this->exchangeArray(array_reverse($this->getArrayCopy()));
- return $this;
+ // Return file system information
+ public function findFileSystemInformation() {
+ $pathInstall = substru(__DIR__, 0, 1-strlenu($this->yellow->system->get("coreExtensionDirectory")));
+ $pathBase = $this->yellow->system->get("coreContentDirectory");
+ $pathRoot = $this->yellow->system->get("coreMultiLanguageMode") ? "default/" : "";
+ $pathHome = "home/";
+ if (!is_string_empty($pathRoot)) {
+ $firstRoot = "";
+ $token = $root = rtrim($pathRoot, "/");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
+ if (is_string_empty($firstRoot)) $firstRoot = $token = $entry;
+ if ($this->normaliseToken($entry)==$root) {
+ $token = $entry;
+ break;
+ }
+ }
+ $pathRoot = $this->normaliseToken($token)."/";
+ $pathBase .= "$firstRoot/";
+ }
+ if (!is_string_empty($pathHome)) {
+ $firstHome = "";
+ $token = $home = rtrim($pathHome, "/");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
+ if (is_string_empty($firstHome)) $firstHome = $token = $entry;
+ if ($this->normaliseToken($entry)==$home) {
+ $token = $entry;
+ break;
+ }
+ }
+ $pathHome = $this->normaliseToken($token)."/";
+ }
+ return array($pathInstall, $pathRoot, $pathHome);
}
- // Randomize page collection
- public function shuffle() {
- $array = $this->getArrayCopy();
- shuffle($array);
- $this->exchangeArray($array);
- return $this;
+ // Return content language
+ public function findContentLanguage($fileName, $languageDefault) {
+ $language = $languageDefault;
+ $pathBase = $this->yellow->system->get("coreContentDirectory");
+ $pathRoot = $this->yellow->system->get("coreServerRootDirectory");
+ if (!is_string_empty($pathRoot)) {
+ $fileName = substru($fileName, strlenu($pathBase));
+ if (preg_match("/^(.+?)\//", $fileName, $matches)) {
+ $name = $this->normaliseToken($matches[1]);
+ if (strlenu($name)==2) $language = $name;
+ }
+ }
+ return $language;
}
- // Paginate page collection
- public function pagination($limit, $reverse = true) {
- $this->paginationNumber = 1;
- $this->paginationCount = ceil($this->count() / $limit);
- if ($this->yellow->page->isRequest("page")) $this->paginationNumber = intval($this->yellow->page->getRequest("page"));
- if ($this->paginationNumber>$this->paginationCount) $this->paginationNumber = 0;
- if ($this->paginationNumber>=1) {
- $array = $this->getArrayCopy();
- if ($reverse) $array = array_reverse($array);
- $this->exchangeArray(array_slice($array, ($this->paginationNumber - 1) * $limit, $limit));
+ // Return content root locations
+ public function findContentRootLocations() {
+ $rootLocations = array();
+ $pathBase = $this->yellow->system->get("coreContentDirectory");
+ $pathRoot = $this->yellow->system->get("coreServerRootDirectory");
+ if (!is_string_empty($pathRoot)) {
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
+ $token = $this->normaliseToken($entry)."/";
+ if ($token==$pathRoot) $token = "";
+ $rootLocations["root/$token"] = "$pathBase$entry/";
+ }
+ } else {
+ $rootLocations["root/"] = "$pathBase";
}
- return $this;
+ if ($this->yellow->system->get("coreDebugMode")>=3) {
+ foreach ($rootLocations as $key=>$key) {
+ echo "YellowLookup::findContentRootLocations $key -> $value<br/>\n";
+ }
+ }
+ return $rootLocations;
}
- // Return current page number in pagination
- public function getPaginationNumber() {
- return $this->paginationNumber;
+ // Return content location from file path
+ public function findContentLocationFromFile($fileName) {
+ $invalid = false;
+ $location = "/";
+ $pathBase = $this->yellow->system->get("coreContentDirectory");
+ $pathRoot = $this->yellow->system->get("coreServerRootDirectory");
+ $pathHome = $this->yellow->system->get("coreServerHomeDirectory");
+ $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
+ $fileExtension = $this->yellow->system->get("coreContentExtension");
+ if (substru($fileName, 0, strlenu($pathBase))==$pathBase && mb_check_encoding($fileName, "UTF-8")) {
+ $fileName = substru($fileName, strlenu($pathBase));
+ $tokens = explode("/", $fileName);
+ if (!is_string_empty($pathRoot)) {
+ $token = $this->normaliseToken($tokens[0])."/";
+ if ($token!=$pathRoot) $location .= $token;
+ array_shift($tokens);
+ }
+ for ($i=0; $i<count($tokens)-1; ++$i) {
+ $token = $this->normaliseToken($tokens[$i])."/";
+ if ($i || $token!=$pathHome) $location .= $token;
+ }
+ $token = $this->normaliseToken($tokens[$i], $fileExtension);
+ if ($token!=$fileDefault) {
+ $location .= $this->normaliseToken($tokens[$i], $fileExtension, true);
+ }
+ $extension = ($pos = strrposu($fileName, ".")) ? substru($fileName, $pos) : "";
+ if ($extension!=$fileExtension) $invalid = true;
+ } else {
+ $invalid = true;
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ $debug = ($invalid ? "INVALID" : $location)." <- $pathBase$fileName";
+ echo "YellowLookup::findContentLocationFromFile $debug<br/>\n";
+ }
+ return $invalid ? "" : $location;
}
- // Return highest page number in pagination
- public function getPaginationCount() {
- return $this->paginationCount;
+ // Return file path from content location
+ public function findFileFromContentLocation($location, $directory = false) {
+ $found = $invalid = false;
+ $path = $this->yellow->system->get("coreContentDirectory");
+ $pathRoot = $this->yellow->system->get("coreServerRootDirectory");
+ $pathHome = $this->yellow->system->get("coreServerHomeDirectory");
+ $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
+ $fileExtension = $this->yellow->system->get("coreContentExtension");
+ $tokens = explode("/", $location);
+ if ($this->isRootLocation($location)) {
+ if (!is_string_empty($pathRoot)) {
+ $token = (count($tokens)>2) ? $tokens[1] : rtrim($pathRoot, "/");
+ $path .= $this->findFileDirectory($path, $token, "", true, true, $found, $invalid);
+ }
+ } else {
+ if (!is_string_empty($pathRoot)) {
+ if (count($tokens)>2) {
+ if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathRoot, "/"))) $invalid = true;
+ $path .= $this->findFileDirectory($path, $tokens[1], "", true, false, $found, $invalid);
+ if ($found) array_shift($tokens);
+ }
+ if (!$found) {
+ $path .= $this->findFileDirectory($path, rtrim($pathRoot, "/"), "", true, true, $found, $invalid);
+ }
+ }
+ if (count($tokens)>2) {
+ if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathHome, "/"))) $invalid = true;
+ for ($i=1; $i<count($tokens)-1; ++$i) {
+ $path .= $this->findFileDirectory($path, $tokens[$i], "", true, true, $found, $invalid);
+ }
+ } else {
+ $i = 1;
+ $tokens[0] = rtrim($pathHome, "/");
+ $path .= $this->findFileDirectory($path, $tokens[0], "", true, true, $found, $invalid);
+ }
+ if (!$directory) {
+ if (!is_string_empty($tokens[$i])) {
+ $token = $tokens[$i].$fileExtension;
+ if ($token==$fileDefault) $invalid = true;
+ $path .= $this->findFileDirectory($path, $token, $fileExtension, false, true, $found, $invalid);
+ } else {
+ $path .= $this->findFileDefault($path, $fileDefault, $fileExtension, false);
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ $debug = "$location -> ".($invalid ? "INVALID" : $path);
+ echo "YellowLookup::findFileFromContentLocation $debug<br/>\n";
+ }
+ }
+ }
+ return $invalid ? "" : $path;
}
- // Return location for a page in pagination
- public function getPaginationLocation($absoluteLocation = true, $pageNumber = 1) {
- $location = $locationArguments = "";
- if ($pageNumber>=1 && $pageNumber<=$this->paginationCount) {
- $location = $this->yellow->page->getLocation($absoluteLocation);
- $locationArguments = $this->yellow->toolbox->getLocationArgumentsNew("page", $pageNumber>1 ? "$pageNumber" : "");
+ // Return children from content location
+ public function findChildrenFromContentLocation($location) {
+ $fileNames = array();
+ if (!$this->isFileLocation($location)) {
+ $path = $this->findFileFromContentLocation($location, true);
+ $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
+ $fileExtension = $this->yellow->system->get("coreContentExtension");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry) {
+ $token = $this->findFileDefault($path.$entry, $fileDefault, $fileExtension, false);
+ array_push($fileNames, $path.$entry."/".$token);
+ }
+ if (!$this->isRootLocation($location)) {
+ $regex = "/^.*\\".$fileExtension."$/";
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
+ if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) continue;
+ array_push($fileNames, $path.$entry);
+ }
+ }
}
- return $location.$locationArguments;
+ return $fileNames;
}
- // Return location for previous page in pagination
- public function getPaginationPrevious($absoluteLocation = true) {
- $pageNumber = $this->paginationNumber-1;
- return $this->getPaginationLocation($absoluteLocation, $pageNumber);
+ // Return media location from file path
+ public function findMediaLocationFromFile($fileName) {
+ $location = "";
+ $extensionDirectoryLength = strlenu($this->yellow->system->get("coreExtensionDirectory"));
+ $themeDirectoryLength = strlenu($this->yellow->system->get("coreThemeDirectory"));
+ $mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
+ if (substru($fileName, 0, $extensionDirectoryLength)==$this->yellow->system->get("coreExtensionDirectory")) {
+ if ($this->isSafeFile($fileName)) {
+ $location = $this->yellow->system->get("coreExtensionLocation").substru($fileName, $extensionDirectoryLength);
+ }
+ } elseif (substru($fileName, 0, $themeDirectoryLength)==$this->yellow->system->get("coreThemeDirectory")) {
+ if ($this->isSafeFile($fileName)) {
+ $location = $this->yellow->system->get("coreThemeLocation").substru($fileName, $themeDirectoryLength);
+ }
+ } elseif (substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory")) {
+ $location = "/".$fileName;
+ }
+ return $location;
}
-
- // Return location for next page in pagination
- public function getPaginationNext($absoluteLocation = true) {
- $pageNumber = $this->paginationNumber+1;
- return $this->getPaginationLocation($absoluteLocation, $pageNumber);
+
+ // Return file path from media location
+ public function findFileFromMediaLocation($location) {
+ $fileName = "";
+ $extensionLocationLength = strlenu($this->yellow->system->get("coreExtensionLocation"));
+ $themeLocationLength = strlenu($this->yellow->system->get("coreThemeLocation"));
+ $mediaLocationLength = strlenu($this->yellow->system->get("coreMediaLocation"));
+ if (substru($location, 0, $extensionLocationLength)==$this->yellow->system->get("coreExtensionLocation")) {
+ if ($this->isSafeFile($location)) {
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").substru($location, $extensionLocationLength);
+ }
+ } elseif (substru($location, 0, $themeLocationLength)==$this->yellow->system->get("coreThemeLocation")) {
+ if ($this->isSafeFile($location)) {
+ $fileName = $this->yellow->system->get("coreThemeDirectory").substru($location, $themeLocationLength);
+ }
+ } elseif (substru($location, 0, $mediaLocationLength)==$this->yellow->system->get("coreMediaLocation")) {
+ $fileName = substru($location, 1);
+ }
+ return $fileName;
}
- // Return current page number in collection
- public function getPageNumber($page) {
- $pageNumber = 0;
- foreach ($this->getIterator() as $key=>$value) {
- if ($page->getLocation()==$value->getLocation()) {
- $pageNumber = $key+1;
- break;
+ // Return children from media location
+ public function findChildrenFromMediaLocation($location) {
+ $fileNames = array();
+ if (!$this->isFileLocation($location)) {
+ $path = $this->findFileFromMediaLocation($location);
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, true) as $entry) {
+ array_push($fileNames, $entry."/");
+ }
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, false, true) as $entry) {
+ array_push($fileNames, $entry);
}
}
- return $pageNumber;
+ return $fileNames;
}
- // Return page in collection, null if none
- public function getPage($pageNumber = 1) {
- return ($pageNumber>=1 && $pageNumber<=$this->count()) ? $this->offsetGet($pageNumber-1) : null;
+ // Return media directory from a system setting
+ public function findMediaDirectory($key) {
+ return substru($key, -8, 8)=="Location" ? $this->findFileFromMediaLocation($this->yellow->system->get($key)) : "";
}
- // Return previous page in collection, null if none
- public function getPagePrevious($page) {
- $pageNumber = $this->getPageNumber($page)-1;
- return $this->getPage($pageNumber);
- }
-
- // Return next page in collection, null if none
- public function getPageNext($page) {
- $pageNumber = $this->getPageNumber($page)+1;
- return $this->getPage($pageNumber);
+ // Return file or directory that matches token
+ public function findFileDirectory($path, $token, $fileExtension, $directory, $default, &$found, &$invalid) {
+ if ($this->normaliseToken($token, $fileExtension)!=$token) $invalid = true;
+ if (!$invalid) {
+ $regex = "/^[\d\-\_\.]*".str_replace("-", ".", $token)."$/";
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, $directory, false) as $entry) {
+ if ($this->normaliseToken($entry, $fileExtension)==$token) {
+ $token = $entry;
+ $found = true;
+ break;
+ }
+ }
+ }
+ if ($directory) $token .= "/";
+ return ($default || $found) ? $token : "";
}
- // Return current page filter
- public function getFilter() {
- return $this->filterValue;
+ // Return default file in directory
+ public function findFileDefault($path, $fileDefault, $fileExtension, $includePath = true) {
+ $token = $fileDefault;
+ if (!is_file($path."/".$fileDefault)) {
+ $regex = "/^[\d\-\_\.]*($fileDefault)$/";
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
+ if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) {
+ $token = $entry;
+ break;
+ }
+ }
+ }
+ return $includePath ? "$path/$token" : $token;
}
- // Return page collection modification date, Unix time or HTTP format
- public function getModified($httpFormat = false) {
- $modified = 0;
- foreach ($this->getIterator() as $page) {
- $modified = max($modified, $page->getModified());
- }
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
+ // Normalise file/directory token
+ public function normaliseToken($text, $fileExtension = "", $removeExtension = false) {
+ if (!is_string_empty($fileExtension)) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
+ if (preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !is_string_empty($matches[1])) $text = $matches[1];
+ return preg_replace("/[^\pL\d\-\_]/u", "-", $text).($removeExtension ? "" : $fileExtension);
}
- // Check if there is a pagination
- public function isPagination() {
- return $this->paginationCount>1;
+ // Normalise name
+ public function normaliseName($text, $removePrefix = false, $removeExtension = false, $filterStrict = false) {
+ if ($removeExtension) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
+ if ($removePrefix && preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !is_string_empty($matches[1])) $text = $matches[1];
+ if ($filterStrict) $text = strtoloweru($text);
+ return preg_replace("/[^\pL\d\-\_]/u", "-", $text);
}
-}
-
-class YellowContent {
- public $yellow; //access to API
- public $pages; //scanned pages
- public function __construct($yellow) {
- $this->yellow = $yellow;
- $this->pages = array();
+ // Normalise prefix
+ public function normalisePrefix($text) {
+ $prefix = "";
+ if (preg_match("/^([\d\-\_\.]*)(.*)$/", $text, $matches)) $prefix = $matches[1];
+ if (!is_string_empty($prefix) && !preg_match("/[\-\_\.]$/", $prefix)) $prefix .= "-";
+ return $prefix;
}
- // Scan file system on demand
- public function scanLocation($location) {
- if (!isset($this->pages[$location])) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowContent::scanLocation location:$location<br/>\n";
- $this->pages[$location] = array();
- $scheme = $this->yellow->page->scheme;
- $address = $this->yellow->page->address;
- $base = $this->yellow->page->base;
- if (empty($location)) {
- $rootLocations = $this->yellow->lookup->findRootLocations();
- foreach ($rootLocations as $rootLocation) {
- list($rootLocation, $fileName) = $this->yellow->toolbox->getTextList($rootLocation, " ", 2);
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $rootLocation, $fileName);
- $page->parseData("", false, 0);
- array_push($this->pages[$location], $page);
+ // Normalise elements and attributes in HTML/SVG data
+ public function normaliseData($text, $type = "html", $filterStrict = true) {
+ $output = "";
+ $elementsHtml = array(
+ "a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "image", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meta", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr");
+ $elementsSvg = array(
+ "svg", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "feblend", "fecolormatrix", "fecomponenttransfer", "fecomposite", "feconvolvematrix", "fediffuselighting", "fedisplacementmap", "fedistantlight", "feflood", "fefunca", "fefuncb", "fefuncg", "fefuncr", "fegaussianblur", "femerge", "femergenode", "femorphology", "feoffset", "fepointlight", "fespecularlighting", "fespotlight", "fetile", "feturbulence", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "use", "view", "vkern");
+ $attributesHtml = array(
+ "accept", "action", "align", "allow", "allowfullscreen", "alt", "autocomplete", "autoplay", "background", "bgcolor", "border", "cellpadding", "cellspacing", "charset", "checked", "cite", "class", "clear", "color", "cols", "colspan", "content", "contenteditable", "controls", "coords", "crossorigin", "datetime", "default", "dir", "disabled", "download", "enctype", "face", "for", "frameborder", "headers", "height", "hidden", "high", "href", "hreflang", "id", "integrity", "ismap", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name", "noshade", "novalidate", "nowrap", "open", "optimum", "pattern", "placeholder", "poster", "prefix", "preload", "property", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "sandbox", "spellcheck", "scope", "selected", "shape", "size", "sizes", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "width", "xmlns");
+ $attributesSvg = array(
+ "accent-height", "accumulate", "additivive", "alignment-baseline", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "datenstrom", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "fill", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "specularconstant", "specularexponent", "spreadmethod", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "tabindex", "targetx", "targety", "transform", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xlink:href", "xml:id", "xml:space", "xmlns", "y", "y1", "y2", "z", "zoomandpan");
+ $attributesAllowEmptyString = array("alt", "download", "sandbox", "value");
+ $elementsSafe = $elementsHtml;
+ $attributesSafe = $attributesHtml;
+ if ($type=="svg") {
+ $elementsSafe = array_merge($elementsHtml, $elementsSvg);
+ $attributesSafe = array_merge($attributesHtml, $attributesSvg);
+ }
+ $offsetBytes = 0;
+ while (true) {
+ $elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
+ $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
+ $elementStart = $elementFound ? $matches[1][0] : "";
+ $elementName = $elementFound ? $matches[2][0]: "";
+ $elementMiddle = $elementFound ? $matches[3][0]: "";
+ $elementEnd = $elementFound ? $matches[4][0]: "";
+ $output .= $elementBefore;
+ if (substrb($elementName, 0, 1)=="!") {
+ $output .= "<$elementName$elementMiddle>";
+ } elseif (in_array(strtolower($elementName), $elementsSafe)) {
+ $elementAttributes = $this->getTextAttributes($elementMiddle, $attributesAllowEmptyString);
+ foreach ($elementAttributes as $key=>$value) {
+ if (!in_array(strtolower($key), $attributesSafe) && !preg_match("/^(aria|data)-/i", $key)) {
+ unset($elementAttributes[$key]);
+ }
}
- } else {
- $fileNames = $this->yellow->lookup->findChildrenFromLocation($location);
- foreach ($fileNames as $fileName) {
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base,
- $this->yellow->lookup->findLocationFromFile($fileName), $fileName);
- $page->parseData($this->yellow->toolbox->readFile($fileName, 4096), false, 0);
- if (strlenb($page->rawData)<4096) $page->statusCode = 200;
- array_push($this->pages[$location], $page);
+ if ($filterStrict) {
+ $href = isset($elementAttributes["href"]) ? $elementAttributes["href"] : "";
+ if (preg_match("/^\w+:/", $href) && !$this->isSafeUrl($href)) {
+ $elementAttributes["href"] = "error-xss-filter";
+ }
+ $href = isset($elementAttributes["xlink:href"]) ? $elementAttributes["xlink:href"] : "";
+ if (preg_match("/^\w+:/", $href) && !$this->isSafeUrl($href)) {
+ $elementAttributes["xlink:href"] = "error-xss-filter";
+ }
}
+ $output .= "<$elementStart$elementName";
+ foreach ($elementAttributes as $key=>$value) $output .= " $key=\"$value\"";
+ if (!is_string_empty($elementEnd)) $output .= " ";
+ $output .= "$elementEnd>";
}
+ if (!$elementFound) break;
+ $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
}
- return $this->pages[$location];
+ return $output;
}
-
- // Return page from file system, null if not found
- public function find($location, $absoluteLocation = false) {
- $found = false;
- if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
- foreach ($this->scanLocation($this->getParentLocation($location)) as $page) {
- if ($page->location==$location) {
- if (!$this->yellow->lookup->isRootLocation($page->location)) {
- $found = true;
- break;
+
+ // Normalise fields in MIME headers
+ public function normaliseHeaders($input, $type = "mime", $filterStrict = true) {
+ $output = "";
+ if ($type=="mime") {
+ $keysMixedEncoding = array("To", "From", "Reply-To", "Cc", "Bcc");
+ foreach ($input as $key=>$value) {
+ $key = ucwords(preg_replace("/[^a-zA-Z\-]/u", "-", $key), "-");
+ if (in_array($key, $keysMixedEncoding)) {
+ $text = "$key: ";
+ foreach (preg_split("/\s*,\s*/", $value) as $email) {
+ if (!preg_match("/^(.*?)(\s*)<(.*?)>$/", $email, $matches)) {
+ $matches[1] = $matches[2] = "";
+ $matches[3] = $email;
+ }
+ if ($filterStrict && !preg_match("/[\w\+\-\.\@]+/", $matches[3])) {
+ $matches[3] = "error-mail-filter";
+ }
+ if (substru($text, -2, 2)!=": ") $text .= ",\r\n ";
+ $text = $this->getMimeHeader($text, $matches[1]);
+ $text = $this->getMimeHeader($text, "$matches[2]<$matches[3]>", false);
+ }
+ $text .= "\r\n";
+ } else {
+ $text = $this->getMimeHeader("$key: ", $value)."\r\n";
}
+ $output .= $text;
}
}
- return $found ? $page : null;
- }
-
- // Return page collection with all pages
- public function index($showInvisible = false, $multiLanguage = false, $levelMax = 0) {
- $rootLocation = $multiLanguage ? "" : $this->getRootLocation($this->yellow->page->location);
- return $this->getChildrenRecursive($rootLocation, $showInvisible, $levelMax);
+ return $output;
}
- // Return page collection with top-level navigation
- public function top($showInvisible = false, $showOnePager = true) {
- $rootLocation = $this->getRootLocation($this->yellow->page->location);
- $pages = $this->getChildren($rootLocation, $showInvisible);
- if (count($pages)==1 && $showOnePager) {
- $scheme = $this->yellow->page->scheme;
- $address = $this->yellow->page->address;
- $base = $this->yellow->page->base;
- $one = ($pages->offsetGet(0)->location!=$this->yellow->page->location) ? $pages->offsetGet(0) : $this->yellow->page;
- preg_match_all("/<h(\d) id=\"([^\"]+)\">(.*?)<\/h\d>/i", $one->getContent(), $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- if ($match[1]==2) {
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $one->location."#".$match[2], $one->fileName);
- $page->parseData("---\nTitle: $match[3]\n---\n", false, 0);
- $pages->append($page);
- }
+ // Normalise relative path tokens
+ public function normalisePath($text) {
+ $textFiltered = "";
+ $textLength = strlenb($text);
+ for ($pos=0; $pos<$textLength; ++$pos) {
+ if ($text[$pos]=="." && ($pos==0 || $text[$pos-1]=="/")) {
+ while ($text[$pos]==".") ++$pos;
+ if ($text[$pos]=="/") ++$pos;
+ --$pos;
+ continue;
}
+ $textFiltered .= $text[$pos];
}
- return $pages;
+ return $textFiltered;
}
- // Return page collection with path ancestry
- public function path($location, $absoluteLocation = false) {
- $pages = new YellowPageCollection($this->yellow);
- if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
- if ($page = $this->find($location)) {
- $pages->prepend($page);
- for (; $parent = $page->getParent(); $page=$parent) {
- $pages->prepend($parent);
- }
- $home = $this->find($this->getHomeLocation($page->location));
- if ($home && $home->location!=$page->location) $pages->prepend($home);
+ // Normalise text lines, convert line endings
+ public function normaliseLines($text, $endOfLine = "lf") {
+ if ($endOfLine=="lf") {
+ $text = preg_replace("/\R/u", "\n", $text);
+ } else {
+ $text = preg_replace("/\R/u", "\r\n", $text);
}
- return $pages;
+ return $text;
}
- // Return page collection with multiple languages
- public function multi($location, $absoluteLocation = false, $showInvisible = false) {
- $pages = new YellowPageCollection($this->yellow);
- if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
- $locationEnd = substru($location, strlenu($this->getRootLocation($location)) - 4);
- foreach ($this->scanLocation("") as $page) {
- if ($content = $this->find(substru($page->location, 4).$locationEnd)) {
- if ($content->isAvailable() && ($content->isVisible() || $showInvisible)) {
- if (!$this->yellow->lookup->isRootLocation($content->location)) $pages->append($content);
- }
- }
+ // Normalise text into UTF-8 NFC
+ public function normaliseUnicode($text) {
+ if (PHP_OS=="Darwin" && !mb_check_encoding($text, "ASCII")) {
+ $utf8nfc = preg_match("//u", $text) && !preg_match("/[^\\x00-\\x{2FF}]/u", $text);
+ if (!$utf8nfc) $text = iconv("UTF-8-MAC", "UTF-8", $text);
}
- return $pages;
+ return $text;
}
- // Return page with shared content, null if not found
- public function shared($name) {
- $location = $this->yellow->lookup->getDirectoryLocation($this->yellow->page->location).$name;
- $page = $this->find($location);
- if ($page==null) {
- $location = $this->getHomeLocation($this->yellow->page->location).$this->yellow->system->get("coreContentSharedDirectory").$name;
- $page = $this->find($location);
+ // Normalise location, make absolute location
+ public function normaliseLocation($location, $pageLocation, $filterStrict = true) {
+ if (!preg_match("/^\w+:/", trim(html_entity_decode($location, ENT_QUOTES, "UTF-8")))) {
+ $pageBase = $this->yellow->page->base;
+ $mediaBase = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreMediaLocation");
+ if (!preg_match("/^\#/", $location)) {
+ if (!preg_match("/^\//", $location)) {
+ $location = $this->getDirectoryLocation($pageBase.$pageLocation).$location;
+ } elseif (!preg_match("#^($pageBase|$mediaBase)#", $location)) {
+ $location = $pageBase.$location;
+ }
+ } else {
+ $location = $pageBase.$pageLocation.$location;
+ }
+ $location = str_replace("/./", "/", $location);
+ $location = str_replace(":", $this->yellow->toolbox->getLocationArgumentsSeparator(), $location);
+ } else {
+ if ($filterStrict && !$this->isSafeUrl($location)) $location = "error-xss-filter";
}
- if ($page) $page->setPage("main", $this->yellow->page);
- return $page;
+ return $location;
}
- // Return page collection that's empty
- public function clean() {
- return new YellowPageCollection($this->yellow);
+ // Normalise location arguments
+ public function normaliseArguments($text, $appendSlash = true, $filterStrict = true) {
+ if ($appendSlash) $text .= "/";
+ if ($filterStrict) $text = str_replace(" ", "-", strtoloweru($text));
+ $text = str_replace(":", $this->yellow->toolbox->getLocationArgumentsSeparator(), $text);
+ return str_replace(array("%2F","%3A","%3D"), array("/",":","="), rawurlencode($text));
}
- // Return languages in multi language mode
- public function getLanguages($showInvisible = false) {
- $languages = array();
- foreach ($this->scanLocation("") as $page) {
- if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) array_push($languages, $page->get("language"));
+ // Normalise URL, make absolute URL
+ public function normaliseUrl($scheme, $address, $base, $location, $filterStrict = true) {
+ if (!preg_match("/^\w+:/", $location)) {
+ $url = "$scheme://$address$base$location";
+ } else {
+ if ($filterStrict && !$this->isSafeUrl($location)) $location = "error-xss-filter";
+ $url = $location;
}
- return $languages;
+ return $url;
}
- // Return child pages
- public function getChildren($location, $showInvisible = false) {
- $pages = new YellowPageCollection($this->yellow);
- foreach ($this->scanLocation($location) as $page) {
- if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
- if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
- }
+ // Return URL information
+ public function getUrlInformation($url) {
+ $scheme = $address = $base = "";
+ if (preg_match("#^(\w+)://([^/]+)(.*)$#", rtrim($url, "/"), $matches)) {
+ $scheme = $matches[1];
+ $address = $matches[2];
+ $base = $matches[3];
}
- return $pages;
+ return array($scheme, $address, $base);
}
- // Return sub pages
- public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
- --$levelMax;
- $pages = new YellowPageCollection($this->yellow);
- foreach ($this->scanLocation($location) as $page) {
- if ($page->isAvailable() && ($page->isVisible() || $showInvisible)) {
- if (!$this->yellow->lookup->isRootLocation($page->location) && is_readable($page->fileName)) $pages->append($page);
- }
- if (!$this->yellow->lookup->isFileLocation($page->location) && $levelMax!=0) {
- $pages->merge($this->getChildrenRecursive($page->location, $showInvisible, $levelMax));
- }
- }
- return $pages;
+ // Return request information
+ public function getRequestInformation($scheme = "", $address = "", $base = "") {
+ if (is_string_empty($scheme) && is_string_empty($address) && is_string_empty($base)) {
+ $url = $this->yellow->system->get("coreServerUrl");
+ if ($url=="auto" || $this->isCommandLine()) $url = $this->yellow->toolbox->detectServerUrl();
+ list($scheme, $address, $base) = $this->getUrlInformation($url);
+ $this->yellow->system->set("coreServerScheme", $scheme);
+ $this->yellow->system->set("coreServerAddress", $address);
+ $this->yellow->system->set("coreServerBase", $base);
+ if ($this->yellow->system->get("coreDebugMode")>=3) {
+ echo "YellowLookup::getRequestInformation $scheme://$address$base<br/>\n";
+ }
+ }
+ $location = substru($this->yellow->toolbox->detectServerLocation(), strlenu($base));
+ $fileName = "";
+ if (is_string_empty($fileName)) $fileName = $this->findFileFromMediaLocation($location);
+ if (is_string_empty($fileName)) $fileName = $this->findFileFromContentLocation($location);
+ return array($scheme, $address, $base, $location, $fileName);
}
- // Return root location
- public function getRootLocation($location) {
- $rootLocation = "root/";
- if ($this->yellow->system->get("coreMultiLanguageMode")) {
- foreach ($this->scanLocation("") as $page) {
- $token = substru($page->location, 4);
- if ($token!="/" && substru($location, 0, strlenu($token))==$token) {
- $rootLocation = "root$token";
- break;
- }
+ // Return command information
+ public function getCommandInformation($line = "") {
+ if (is_string_empty($line)) {
+ $line = $this->yellow->toolbox->getTextString(array_slice($this->yellow->toolbox->getServer("argv"), 1));
+ if ($this->yellow->system->get("coreDebugMode")>=3) {
+ echo "YellowLookup::getCommandInformation $line<br/>\n";
}
}
- return $rootLocation;
+ return $this->yellow->toolbox->getTextList($line, " ", 2);
}
- // Return home location
- public function getHomeLocation($location) {
- return substru($this->getRootLocation($location), 4);
- }
-
- // Return parent location
- public function getParentLocation($location) {
- $token = rtrim(substru($this->getRootLocation($location), 4), "/");
- if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
- if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
- }
- if (empty($parentLocation)) $parentLocation = "root$token/";
- return $parentLocation;
+ // Return request handler
+ public function getRequestHandler() {
+ return $this->requestHandler;
}
-
- // Return top-level location
- public function getParentTopLocation($location) {
- $token = rtrim(substru($this->getRootLocation($location), 4), "/");
- if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
- if (empty($parentTopLocation)) $parentTopLocation = "$token/";
- return $parentTopLocation;
+
+ // Return command handler
+ public function getCommandHandler() {
+ return $this->commandHandler;
}
-}
-class YellowMedia {
- public $yellow; //access to API
- public $files; //scanned files
-
- public function __construct($yellow) {
- $this->yellow = $yellow;
- $this->files = array();
- }
-
- // Scan file system on demand
- public function scanLocation($location) {
- if (!isset($this->files[$location])) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowMedia::scanLocation location:$location<br/>\n";
- $this->files[$location] = array();
- $scheme = $this->yellow->page->scheme;
- $address = $this->yellow->page->address;
- $base = $this->yellow->system->get("coreServerBase");
- if (empty($location)) {
- $fileNames = array($this->yellow->system->get("coreMediaDirectory"));
- } else {
- $fileNames = array();
- $path = substru($location, 1);
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, true) as $entry) {
- array_push($fileNames, $entry."/");
- }
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, false, true) as $entry) {
- array_push($fileNames, $entry);
+ // Return attributes from text
+ public function getTextAttributes($text, $attributesAllowEmptyString) {
+ $tokens = array();
+ $posStart = $posQuote = 0;
+ $textLength = strlenb($text);
+ for ($pos=0; $pos<$textLength; ++$pos) {
+ if ($text[$pos]==" " && !$posQuote) {
+ if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
+ $posStart = $pos+1;
+ }
+ if ($text[$pos]=="=" && !$posQuote) {
+ if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
+ array_push($tokens, "=");
+ $posStart = $pos+1;
+ }
+ if ($text[$pos]=="\"") {
+ if ($posQuote) {
+ if ($pos>$posQuote) array_push($tokens, substrb($text, $posQuote+1, $pos-$posQuote-1));
+ $posQuote = 0;
+ $posStart = $pos+1;
+ } else {
+ if ($pos==$posStart) $posQuote = $pos;
}
}
- foreach ($fileNames as $fileName) {
- $file = new YellowPage($this->yellow);
- $file->setRequestInformation($scheme, $address, $base, "/".$fileName, $fileName);
- $file->parseData(null, false, 0);
- array_push($this->files[$location], $file);
+ }
+ if ($pos>$posStart && !$posQuote) {
+ array_push($tokens, substrb($text, $posStart, $pos-$posStart));
+ }
+ $attributes = array();
+ for ($i=0; $i<count($tokens); ++$i) {
+ if ($i+2<count($tokens) && $tokens[$i+1]=="=") {
+ $key = $tokens[$i];
+ $value = $tokens[$i+2];
+ $i += 2;
+ } else {
+ $key = $value = $tokens[$i];
+ }
+ if (!is_string_empty($key) && (!is_string_empty($value) || in_array(strtolower($key), $attributesAllowEmptyString))) {
+ $attributes[$key] = $value;
}
}
- return $this->files[$location];
+ return $attributes;
}
- // Return page with media file information, null if not found
- public function find($location, $absoluteLocation = false) {
- $found = false;
- if ($absoluteLocation) $location = substru($location, strlenu($this->yellow->system->get("coreServerBase")));
- foreach ($this->scanLocation($this->getParentLocation($location)) as $file) {
- if ($file->location==$location) {
- if ($this->yellow->lookup->isFileLocation($file->location)) {
- $found = true;
+ // Return MIME header field, encode and fold if necessary
+ public function getMimeHeader($text, $field, $allowEncode = true) {
+ if ($allowEncode) {
+ $encode = preg_match("/[\x7F-\xFF]/", $field);
+ $fieldPos = 0;
+ while (true) {
+ $textPos = strlenb($text)-(($pos = strrposb($text, "\n")) ? $pos+1 : 0);
+ $bytesAvailable = max(0, 78-$textPos);
+ $fragment = substrb($field, $fieldPos);
+ if ($encode && !is_string_empty($fragment)) $fragment = "=?UTF-8?B?".base64_encode($fragment)."?=";
+ if ($bytesAvailable<strlenb($fragment)) {
+ $bytesHandled = $bytesAvailable;
+ if (!$encode) {
+ for ($pos=$bytesHandled;$pos>0;--$pos) {
+ if ($field[$fieldPos+$pos]==" ") {
+ $fragment = substrb($field, $fieldPos, $pos);
+ $bytesHandled = $pos+1;
+ break;
+ }
+ }
+ if ($pos==0) $encode = true;
+ }
+ if ($encode) {
+ while (true) {
+ $fragment = substrb($field, $fieldPos, $bytesHandled);
+ if (!is_string_empty($fragment)) $fragment = "=?UTF-8?B?".base64_encode($fragment)."?=";
+ if ($bytesAvailable>=strlenb($fragment) || $bytesHandled==0) break;
+ --$bytesHandled;
+ }
+ }
+ $text .= $fragment."\r\n ";
+ $fieldPos += $bytesHandled;
+ } else {
+ $text .= $fragment;
break;
}
}
+ } else {
+ $textPos = strlenb($text)-(($pos = strrposb($text, "\n")) ? $pos+1 : 0);
+ $bytesAvailable = max(0, 78-$textPos);
+ if ($bytesAvailable<strlenb($field)) {
+ $text .= "\r\n ".ltrim($field);
+ } else {
+ $text .= $field;
+ }
}
- return $found ? $file : null;
- }
-
- // Return page collection with all media files
- public function index($showInvisible = false, $multiPass = false, $levelMax = 0) {
- return $this->getChildrenRecursive("", $showInvisible, $levelMax);
+ return $text;
}
- // Return page collection that's empty
- public function clean() {
- return new YellowPageCollection($this->yellow);
+ // Return directory location
+ public function getDirectoryLocation($location) {
+ return ($pos = strrposu($location, "/")) ? substru($location, 0, $pos+1) : "/";
}
- // Return child files
- public function getChildren($location, $showInvisible = false) {
- $files = new YellowPageCollection($this->yellow);
- foreach ($this->scanLocation($location) as $file) {
- if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
- if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
- }
+ // Return redirect location
+ public function getRedirectLocation($location) {
+ if ($this->isFileLocation($location)) {
+ $location = "$location/";
+ } else {
+ $languageDefault = $this->yellow->system->get("language");
+ $language = $this->yellow->toolbox->detectBrowserLanguage($this->yellow->content->getLanguages(), $languageDefault);
+ $location = "/$language/";
}
- return $files;
+ return $location;
}
- // Return sub files
- public function getChildrenRecursive($location, $showInvisible = false, $levelMax = 0) {
- --$levelMax;
- $files = new YellowPageCollection($this->yellow);
- foreach ($this->scanLocation($location) as $file) {
- if ($file->isAvailable() && ($file->isVisible() || $showInvisible)) {
- if ($this->yellow->lookup->isFileLocation($file->location)) $files->append($file);
- }
- if (!$this->yellow->lookup->isFileLocation($file->location) && $levelMax!=0) {
- $files->merge($this->getChildrenRecursive($file->location, $showInvisible, $levelMax));
- }
- }
- return $files;
+ // Check if clean URL is requested
+ public function isRequestCleanUrl($location) {
+ return isset($_REQUEST["clean-url"]) && substru($location, -1, 1)=="/";
}
- // Return home location
- public function getHomeLocation($location) {
- return $this->yellow->system->get("coreMediaLocation");
- }
-
- // Return parent location
- public function getParentLocation($location) {
- $token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
- if (preg_match("#^($token.*\/).+?$#", $location, $matches)) {
- if ($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
- }
- if (empty($parentLocation)) $parentLocation = "";
- return $parentLocation;
+ // Check if location is specifying root
+ public function isRootLocation($location) {
+ return substru($location, 0, 1)!="/";
}
- // Return top-level location
- public function getParentTopLocation($location) {
- $token = rtrim($this->yellow->system->get("coreMediaLocation"), "/");
- if (preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
- if (empty($parentTopLocation)) $parentTopLocation = "$token/";
- return $parentTopLocation;
+ // Check if location is specifying file or directory
+ public function isFileLocation($location) {
+ return substru($location, -1, 1)!="/";
}
-}
-
-class YellowSystem {
- public $yellow; //access to API
- public $modified; //settings modification date
- public $settings; //settings
- public $settingsDefaults; //settings defaults
- public function __construct($yellow) {
- $this->yellow = $yellow;
- $this->modified = 0;
- $this->settings = new YellowDataCollection();
- $this->settingsDefaults = new YellowDataCollection();
+ // Check if location can be redirected into directory
+ public function isRedirectLocation($location) {
+ $redirect = false;
+ if ($this->isFileLocation($location)) {
+ $redirect = is_dir($this->findFileFromContentLocation("$location/", true));
+ } elseif ($location=="/") {
+ $redirect = $this->yellow->system->get("coreMultiLanguageMode");
+ }
+ return $redirect;
}
- // Load system settings from file
- public function load($fileName) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowSystem::load file:$fileName<br/>\n";
- $this->modified = $this->yellow->toolbox->getFileModified($fileName);
- $fileData = $this->yellow->toolbox->readFile($fileName);
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\#/", $line)) continue;
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (!empty($matches[1]) && !strempty($matches[2])) {
- $this->set($matches[1], $matches[2]);
- if (defined("DEBUG") && DEBUG>=3) echo "YellowSystem::load $matches[1]:$matches[2]<br/>\n";
- }
- }
+ // Check if location contains nested directories
+ public function isNestedLocation($location, $fileName, $checkHomeLocation = false) {
+ $nested = false;
+ if (!$checkHomeLocation || $location==$this->yellow->content->getHomeLocation($location)) {
+ $path = dirname($fileName);
+ if (!is_array_empty($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false))) $nested = true;
}
+ return $nested;
}
- // Save system settings to file
- public function save($fileName, $settings) {
- $settingsNew = new YellowDataCollection();
- foreach ($settings as $key=>$value) {
- if (!empty($key) && !strempty($value)) {
- $this->set($key, $value);
- $settingsNew[$key] = $value;
- }
- }
- $this->modified = time();
- $fileData = $this->yellow->toolbox->readFile($fileName);
- $fileDataNew = "";
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (!empty($matches[1]) && isset($settingsNew[$matches[1]])) {
- $fileDataNew .= "$matches[1]: ".$settingsNew[$matches[1]]."\n";
- unset($settingsNew[$matches[1]]);
- continue;
- }
+ // Check if location is within shared directory
+ public function isSharedLocation($location) {
+ $sharedLocation = $this->yellow->content->getHomeLocation($location)."shared/";
+ return substru($location, 0, strlenu($sharedLocation))==$sharedLocation;
+ }
+
+ // Check if location is within current HTTP request
+ public function isActiveLocation($location, $currentLocation) {
+ if ($this->isFileLocation($location)) {
+ $active = $currentLocation==$location;
+ } else {
+ if ($location==$this->yellow->content->getHomeLocation($location)) {
+ $active = $this->getDirectoryLocation($currentLocation)==$location;
+ } else {
+ $active = substru($currentLocation, 0, strlenu($location))==$location;
}
- $fileDataNew .= $line;
}
- foreach ($settingsNew as $key=>$value) {
- $fileDataNew .= ucfirst($key).": $value\n";
- }
- return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
+ return $active;
}
- // Set default system setting
- public function setDefault($key, $value) {
- $this->settingsDefaults[$key] = $value;
+ // Check if URL is a well-known URL scheme
+ public function isSafeUrl($url) {
+ return preg_match("/^(http|https|ftp|mailto|tel):/", $url);
}
- // Set system setting
- public function set($key, $value) {
- $this->settings[$key] = $value;
+ // Check if file is a well-known file type
+ public function isSafeFile($fileName) {
+ return preg_match("/\.(css|gif|ico|js|jpg|map|png|scss|svg|woff|woff2)$/", $fileName);
}
- // Return system setting
- public function get($key) {
- if (isset($this->settings[$key])) {
- $value = $this->settings[$key];
- } else {
- $value = isset($this->settingsDefaults[$key]) ? $this->settingsDefaults[$key] : "";
- }
- return $value;
+ // Check if file is valid
+ public function isValidFile($fileName) {
+ $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
+ $mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
+ $systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
+ return strposu($fileName, "/")===false ||
+ substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory") ||
+ substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory") ||
+ substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
}
- // Return system setting, HTML encoded
- public function getHtml($key) {
- return htmlspecialchars($this->get($key));
+ // Check if content file
+ public function isContentFile($fileName) {
+ $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
+ return substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory");
}
- // Return system settings
- public function getData($filterStart = "", $filterEnd = "") {
- $settings = array();
- if (empty($filterStart) && empty($filterEnd)) {
- $settings = array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy());
- } else {
- foreach (array_merge($this->settingsDefaults->getArrayCopy(), $this->settings->getArrayCopy()) as $key=>$value) {
- if (!empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $settings[$key] = $value;
- if (!empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $settings[$key] = $value;
- }
- }
- return $settings;
+ // Check if media file
+ public function isMediaFile($fileName) {
+ $mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
+ return substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory");
}
- // Return system settings modification date, Unix time or HTTP format
- public function getModified($httpFormat = false) {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
+ // Check if system file
+ public function isSystemFile($fileName) {
+ $systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
+ return substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
}
- // Check if system setting exists
- public function isExisting($key) {
- return isset($this->settings[$key]);
+ // Check if running at command line
+ public function isCommandLine() {
+ return isset($this->commandHandler);
}
}
-class YellowText {
- public $yellow; //access to API
- public $modified; //text modification date
- public $text; //text
- public $language; //current language
+class YellowToolbox {
+ public $yellow; // access to API
public function __construct($yellow) {
$this->yellow = $yellow;
- $this->modified = 0;
- $this->text = new YellowDataCollection();
- }
-
- // Load text settings
- public function load($path, $fileName = "", $languageDefault = "") {
- $regex = empty($fileName) ? "/^.*\.txt$/" : "/^$fileName$/";
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false) as $entry) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowText::load file:$entry<br/>\n";
- $language = $languageDefault;
- $this->modified = max($this->modified, filemtime($entry));
- $fileData = $this->yellow->toolbox->readFile($entry);
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\#/", $line)) continue;
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="language" && !strempty($matches[2])) $language = $matches[2];
- if (!empty($language) && !empty($matches[1]) && !strempty($matches[2])) {
- $this->setText($matches[1], $matches[2], $language);
- if (defined("DEBUG") && DEBUG>=3) echo "YellowText::load $matches[1]:$matches[2]<br/>\n";
- }
- }
- }
- }
- }
-
- // Set current language
- public function setLanguage($language) {
- $this->language = $language;
- }
-
- // Set text settings for specific language
- public function setText($key, $value, $language) {
- if (!isset($this->text[$language])) $this->text[$language] = new YellowDataCollection();
- $this->text[$language][$key] = $value;
- }
-
- // Return text setting
- public function get($key) {
- return $this->getText($key, $this->language);
}
- // Return text setting, HTML encoded
- public function getHtml($key) {
- return htmlspecialchars($this->getText($key, $this->language));
+ // Return browser cookie from from current HTTP request
+ public function getCookie($key) {
+ return isset($_COOKIE[$key]) ? $_COOKIE[$key] : "";
}
- // Return text setting for specific language
- public function getText($key, $language) {
- return $this->isExisting($key, $language) ? $this->text[$language][$key] : "[$key]";
+ // Return server argument from current HTTP request
+ public function getServer($key) {
+ return isset($_SERVER[$key]) ? $_SERVER[$key] : "";
}
- // Return text setting for specific language, HTML encoded
- public function getTextHtml($key, $language) {
- return htmlspecialchars($this->getText($key, $language));
+ // Return location arguments from current HTTP request
+ public function getLocationArguments() {
+ return $this->getServer("LOCATION_ARGUMENTS");
}
- // Return text settings
- public function getData($filterStart = "", $language = "") {
- $text = array();
- if (empty($language)) $language = $this->language;
- if ($this->isLanguage($language)) {
- if (empty($filterStart)) {
- $text = $this->text[$language];
- } else {
- foreach ($this->text[$language] as $key=>$value) {
- if (substru($key, 0, strlenu($filterStart))==$filterStart) $text[$key] = $value;
+ // Return location arguments from current HTTP request, modify existing arguments
+ public function getLocationArgumentsNew($key, $value) {
+ $locationArguments = "";
+ $found = false;
+ $separator = $this->getLocationArgumentsSeparator();
+ foreach (explode("/", $this->getServer("LOCATION_ARGUMENTS")) as $token) {
+ if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
+ if ($matches[1]==$key) {
+ $matches[2] = $value;
+ $found = true;
+ }
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ if (!is_string_empty($locationArguments)) $locationArguments .= "/";
+ $locationArguments .= "$matches[1]:$matches[2]";
}
}
}
- return $text;
- }
-
- // Return human readable date, custom date format
- public function getDateFormatted($timestamp, $format) {
- $dateMonths = preg_split("/\s*,\s*/", $this->get("coreDateMonths"));
- $dateWeekdays = preg_split("/\s*,\s*/", $this->get("coreDateWeekdays"));
- $month = $dateMonths[date("n", $timestamp) - 1];
- $weekday = $dateWeekdays[date("N", $timestamp) - 1];
- $timeZone = $this->yellow->system->get("coreServerTimezone");
- $timeZoneHelper = new DateTime(null, new DateTimeZone($timeZone));
- $timeZoneOffset = $timeZoneHelper->getOffset();
- $timeZoneAbbreviation = "GMT".($timeZoneOffset<0 ? "-" : "+").abs(intval($timeZoneOffset/3600));
- $format = preg_replace("/(?<!\\\)F/", addcslashes($month, "A..Za..z"), $format);
- $format = preg_replace("/(?<!\\\)M/", addcslashes(substru($month, 0, 3), "A..Za..z"), $format);
- $format = preg_replace("/(?<!\\\)D/", addcslashes(substru($weekday, 0, 3), "A..Za..z"), $format);
- $format = preg_replace("/(?<!\\\)l/", addcslashes($weekday, "A..Za..z"), $format);
- $format = preg_replace("/(?<!\\\)T/", addcslashes($timeZoneAbbreviation, "A..Za..z"), $format);
- return date($format, $timestamp);
+ if (!$found && !is_string_empty($key) && !is_string_empty($value)) {
+ if (!is_string_empty($locationArguments)) $locationArguments .= "/";
+ $locationArguments .= "$key:$value";
+ }
+ if (!is_string_empty($locationArguments)) {
+ $locationArguments = $this->yellow->lookup->normaliseArguments($locationArguments, false, false);
+ if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
+ }
+ return $locationArguments;
}
- // Return human readable date, relative to today
- public function getDateRelative($timestamp, $format, $daysLimit) {
- $timeDifference = time() - $timestamp;
- $days = abs(intval($timeDifference / 86400));
- $key = $timeDifference>=0 ? "coreDatePast" : "coreDateFuture";
- $tokens = preg_split("/\s*,\s*/", $this->get($key));
- if (count($tokens)>=8) {
- if ($days<=$daysLimit || $daysLimit==0) {
- if ($days==0) {
- $output = $tokens[0];
- } elseif ($days==1) {
- $output = $tokens[1];
- } elseif ($days>=2 && $days<=29) {
- $output = preg_replace("/@x/i", $days, $tokens[2]);
- } elseif ($days>=30 && $days<=59) {
- $output = $tokens[3];
- } elseif ($days>=60 && $days<=364) {
- $output = preg_replace("/@x/i", intval($days/30), $tokens[4]);
- } elseif ($days>=365 && $days<=729) {
- $output = $tokens[5];
- } else {
- $output = preg_replace("/@x/i", intval($days/365.25), $tokens[6]);
- }
- } else {
- $output = preg_replace("/@x/i", $this->getDateFormatted($timestamp, $format), $tokens[7]);
+ // Return location arguments from current HTTP request, convert form parameters
+ public function getLocationArgumentsCleanUrl() {
+ $locationArguments = "";
+ foreach (array_merge($_GET, $_POST) as $key=>$value) {
+ if (!is_string_empty($key) && !is_string_empty($value)) {
+ if (!is_string_empty($locationArguments)) $locationArguments .= "/";
+ $key = str_replace(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $key);
+ $value = str_replace(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $value);
+ $locationArguments .= "$key:$value";
}
- } else {
- $output = "[$key]";
}
- return $output;
- }
-
- // Return text settings modification date, Unix time or HTTP format
- public function getModified($httpFormat = false) {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
- }
-
- // Return languages
- public function getLanguages() {
- $languages = array();
- foreach ($this->text as $key=>$value) {
- array_push($languages, $key);
+ if (!is_string_empty($locationArguments)) {
+ $locationArguments = $this->yellow->lookup->normaliseArguments($locationArguments, false, false);
+ if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
}
- return $languages;
+ return $locationArguments;
}
-
- // Normalise date into known format
- public function normaliseDate($text) {
- if (preg_match("/^\d+\-\d+$/", $text)) {
- $output = $this->getDateFormatted(strtotime($text), $this->get("coreDateFormatShort"));
- } elseif (preg_match("/^\d+\-\d+\-\d+$/", $text)) {
- $output = $this->getDateFormatted(strtotime($text), $this->get("coreDateFormatMedium"));
- } elseif (preg_match("/^\d+\-\d+\-\d+ \d+\:\d+$/", $text)) {
- $output = $this->getDateFormatted(strtotime($text), $this->get("coreDateFormatLong"));
- } else {
- $output = $text;
- }
- return $output;
+
+ // Return location arguments separator
+ public function getLocationArgumentsSeparator() {
+ return (strtoupperu(substru(PHP_OS, 0, 3))!="WIN") ? ":" : "=";
}
- // Check if language exists
- public function isLanguage($language) {
- return isset($this->text[$language]);
+ // Return human readable HTTP date
+ public function getHttpDateFormatted($timestamp) {
+ return gmdate("D, d M Y H:i:s", $timestamp)." GMT";
}
- // Check if text setting exists
- public function isExisting($key, $language = "") {
- if (empty($language)) $language = $this->language;
- return isset($this->text[$language]) && isset($this->text[$language][$key]);
+ // Return human readable HTTP server status
+ public function getHttpStatusFormatted($statusCode, $shortFormat = false) {
+ switch ($statusCode) {
+ case 0: $text = "No data"; break;
+ case 200: $text = "OK"; break;
+ case 301: $text = "Moved permanently"; break;
+ case 302: $text = "Moved temporarily"; break;
+ case 303: $text = "Reload please"; break;
+ case 304: $text = "Not modified"; break;
+ case 400: $text = "Bad request"; break;
+ case 403: $text = "Forbidden"; break;
+ case 404: $text = "Not found"; break;
+ case 420: $text = "Not public"; break;
+ case 430: $text = "Login failed"; break;
+ case 434: $text = "Can create"; break;
+ case 435: $text = "Can restore"; break;
+ case 450: $text = "Update error"; break;
+ case 500: $text = "Server error"; break;
+ case 503: $text = "Service unavailable"; break;
+ default: $text = "Error $statusCode";
+ }
+ $serverProtocol = $this->getServer("SERVER_PROTOCOL");
+ if (!preg_match("/^HTTP\//", $serverProtocol)) $serverProtocol = "HTTP/1.1";
+ return $shortFormat ? $text : "$serverProtocol $statusCode $text";
}
-}
-
-class YellowLookup {
- public $yellow; //access to API
- public $requestHandler; //request handler name
- public $commandHandler; //command handler name
- public $layoutArguments; //layout arguments
- public function __construct($yellow) {
- $this->yellow = $yellow;
+ // Return MIME content type
+ public function getMimeContentType($fileName) {
+ $contentType = "";
+ $contentTypes = array(
+ "css" => "text/css",
+ "gif" => "image/gif",
+ "html" => "text/html; charset=utf-8",
+ "ico" => "image/x-icon",
+ "js" => "application/javascript",
+ "json" => "application/json",
+ "jpg" => "image/jpeg",
+ "md" => "text/markdown",
+ "png" => "image/png",
+ "scss" => "text/x-scss",
+ "svg" => "image/svg+xml",
+ "txt" => "text/plain",
+ "woff" => "application/font-woff",
+ "woff2" => "application/font-woff2",
+ "xml" => "text/xml; charset=utf-8");
+ $fileType = $this->getFileType($fileName);
+ if (is_string_empty($fileType)) {
+ $contentType = $contentTypes["html"];
+ } elseif (array_key_exists($fileType, $contentTypes)) {
+ $contentType = $contentTypes[$fileType];
+ }
+ return $contentType;
}
- // Detect file system
- public function detectFileSystem() {
- list($pathRoot, $pathHome) = $this->findFileSystemInformation();
- $this->yellow->system->set("coreContentRootDirectory", $pathRoot);
- $this->yellow->system->set("coreContentHomeDirectory", $pathHome);
- date_default_timezone_set($this->yellow->system->get("coreServerTimezone"));
+ // Send HTTP header
+ public function sendHttpHeader($text) {
+ if (!headers_sent()) header($text);
}
- // Return file system information
- public function findFileSystemInformation() {
- $path = $this->yellow->system->get("coreContentDirectory");
- $pathRoot = $this->yellow->system->get("coreContentRootDirectory");
- $pathHome = $this->yellow->system->get("coreContentHomeDirectory");
- if (!$this->yellow->system->get("coreMultiLanguageMode")) $pathRoot = "";
- if (!empty($pathRoot)) {
- $token = $root = rtrim($pathRoot, "/");
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry) {
- if (empty($firstRoot)) $firstRoot = $token = $entry;
- if ($this->normaliseToken($entry)==$root) {
- $token = $entry;
- break;
- }
- }
- $pathRoot = $this->normaliseToken($token)."/";
- $path .= "$firstRoot/";
- }
- if (!empty($pathHome)) {
- $token = $home = rtrim($pathHome, "/");
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry) {
- if (empty($firstHome)) $firstHome = $token = $entry;
- if ($this->normaliseToken($entry)==$home) {
- $token = $entry;
- break;
+ // Return files and directories
+ public function getDirectoryEntries($path, $regex = "/.*/", $sort = true, $directories = true, $includePath = true) {
+ $entries = array();
+ $directoryHandle = @opendir($path);
+ if ($directoryHandle) {
+ $path = rtrim($path, "/");
+ while (($entry = readdir($directoryHandle))!==false) {
+ if (substru($entry, 0, 1)==".") continue;
+ $entry = $this->yellow->lookup->normaliseUnicode($entry);
+ if (preg_match($regex, $entry)) {
+ if ($directories) {
+ if (is_dir("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
+ } else {
+ if (is_file("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
+ }
}
}
- $pathHome = $this->normaliseToken($token)."/";
+ if ($sort) natcasesort($entries);
+ closedir($directoryHandle);
}
- return array($pathRoot, $pathHome);
+ return $entries;
}
-
- // Return root locations
- public function findRootLocations($includePath = true) {
- $locations = array();
- $pathBase = $this->yellow->system->get("coreContentDirectory");
- $pathRoot = $this->yellow->system->get("coreContentRootDirectory");
- if (!empty($pathRoot)) {
- foreach ($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry) {
- $token = $this->normaliseToken($entry)."/";
- if ($token==$pathRoot) $token = "";
- array_push($locations, $includePath ? "root/$token $pathBase$entry/" : "root/$token");
- if (defined("DEBUG") && DEBUG>=2) echo "YellowLookup::findRootLocations root/$token<br/>\n";
+
+ // Return files and directories recursively
+ public function getDirectoryEntriesRecursive($path, $regex = "/.*/", $sort = true, $directories = true, $levelMax = 0) {
+ --$levelMax;
+ $entries = $this->getDirectoryEntries($path, $regex, $sort, $directories);
+ if ($levelMax!=0) {
+ foreach ($this->getDirectoryEntries($path, "/.*/", $sort, true) as $entry) {
+ $entries = array_merge($entries, $this->getDirectoryEntriesRecursive($entry, $regex, $sort, $directories, $levelMax));
}
- } else {
- array_push($locations, $includePath ? "root/ $pathBase" : "root/");
}
- return $locations;
+ return $entries;
}
- // Return location from file path
- public function findLocationFromFile($fileName) {
- $invalid = false;
- $location = "/";
- $pathBase = $this->yellow->system->get("coreContentDirectory");
- $pathRoot = $this->yellow->system->get("coreContentRootDirectory");
- $pathHome = $this->yellow->system->get("coreContentHomeDirectory");
- $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
- $fileExtension = $this->yellow->system->get("coreContentExtension");
- if (substru($fileName, 0, strlenu($pathBase))==$pathBase && mb_check_encoding($fileName, "UTF-8")) {
- $fileName = substru($fileName, strlenu($pathBase));
- $tokens = explode("/", $fileName);
- if (!empty($pathRoot)) {
- $token = $this->normaliseToken($tokens[0])."/";
- if ($token!=$pathRoot) $location .= $token;
- array_shift($tokens);
- }
- for ($i=0; $i<count($tokens)-1; ++$i) {
- $token = $this->normaliseToken($tokens[$i])."/";
- if ($i || $token!=$pathHome) $location .= $token;
- }
- $token = $this->normaliseToken($tokens[$i], $fileExtension);
- $fileFolder = $this->normaliseToken($tokens[$i-1], $fileExtension);
- if ($token!=$fileDefault && $token!=$fileFolder) {
- $location .= $this->normaliseToken($tokens[$i], $fileExtension, true);
+ // Read file, empty string if not found
+ public function readFile($fileName, $sizeMax = 0) {
+ $fileData = "";
+ $fileHandle = @fopen($fileName, "rb");
+ if ($fileHandle) {
+ clearstatcache(true, $fileName);
+ if (flock($fileHandle, LOCK_SH)) {
+ $fileSize = $sizeMax ? $sizeMax : filesize($fileName);
+ if ($fileSize) $fileData = fread($fileHandle, $fileSize);
+ flock($fileHandle, LOCK_UN);
}
- $extension = ($pos = strrposu($fileName, ".")) ? substru($fileName, $pos) : "";
- if ($extension!=$fileExtension) $invalid = true;
- } else {
- $invalid = true;
- }
- if (defined("DEBUG") && DEBUG>=2) {
- $debug = ($invalid ? "INVALID" : $location)." <- $pathBase$fileName";
- echo "YellowLookup::findLocationFromFile $debug<br/>\n";
+ fclose($fileHandle);
}
- return $invalid ? "" : $location;
+ return $fileData;
}
- // Return file path from location
- public function findFileFromLocation($location, $directory = false) {
- $found = $invalid = false;
- $path = $this->yellow->system->get("coreContentDirectory");
- $pathRoot = $this->yellow->system->get("coreContentRootDirectory");
- $pathHome = $this->yellow->system->get("coreContentHomeDirectory");
- $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
- $fileExtension = $this->yellow->system->get("coreContentExtension");
- $tokens = explode("/", $location);
- if ($this->isRootLocation($location)) {
- if (!empty($pathRoot)) {
- $token = (count($tokens)>2) ? $tokens[1] : rtrim($pathRoot, "/");
- $path .= $this->findFileDirectory($path, $token, "", true, true, $found, $invalid);
- }
- } else {
- if (!empty($pathRoot)) {
- if (count($tokens)>2) {
- if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathRoot, "/"))) $invalid = true;
- $path .= $this->findFileDirectory($path, $tokens[1], "", true, false, $found, $invalid);
- if ($found) array_shift($tokens);
- }
- if (!$found) {
- $path .= $this->findFileDirectory($path, rtrim($pathRoot, "/"), "", true, true, $found, $invalid);
- }
- }
- if (count($tokens)>2) {
- if ($this->normaliseToken($tokens[1])==$this->normaliseToken(rtrim($pathHome, "/"))) $invalid = true;
- for ($i=1; $i<count($tokens)-1; ++$i) {
- $path .= $this->findFileDirectory($path, $tokens[$i], "", true, true, $found, $invalid);
- }
- } else {
- $i = 1;
- $tokens[0] = rtrim($pathHome, "/");
- $path .= $this->findFileDirectory($path, $tokens[0], "", true, true, $found, $invalid);
- }
- if (!$directory) {
- if (!strempty($tokens[$i])) {
- $token = $tokens[$i].$fileExtension;
- $fileFolder = $tokens[$i-1].$fileExtension;
- if ($token==$fileDefault || $token==$fileFolder) $invalid = true;
- $path .= $this->findFileDirectory($path, $token, $fileExtension, false, true, $found, $invalid);
- } else {
- $path .= $this->findFileDefault($path, $fileDefault, $fileExtension, false);
- }
- if (defined("DEBUG") && DEBUG>=2) {
- $debug = "$location -> ".($invalid ? "INVALID" : $path);
- echo "YellowLookup::findFileFromLocation $debug<br/>\n";
- }
+ // Create file
+ public function createFile($fileName, $fileData, $mkdir = false) {
+ $ok = false;
+ if ($mkdir) {
+ $path = dirname($fileName);
+ if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ }
+ $fileHandle = @fopen($fileName, "cb");
+ if ($fileHandle) {
+ clearstatcache(true, $fileName);
+ if (flock($fileHandle, LOCK_EX)) {
+ ftruncate($fileHandle, 0);
+ fwrite($fileHandle, $fileData);
+ flock($fileHandle, LOCK_UN);
}
+ fclose($fileHandle);
+ $ok = true;
}
- return $invalid ? "" : $path;
+ return $ok;
}
- // Return file or directory that matches token
- public function findFileDirectory($path, $token, $fileExtension, $directory, $default, &$found, &$invalid) {
- if ($this->normaliseToken($token, $fileExtension)!=$token) $invalid = true;
- if (!$invalid) {
- $regex = "/^[\d\-\_\.]*".strreplaceu("-", ".", $token)."$/";
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, $directory, false) as $entry) {
- if ($this->normaliseToken($entry, $fileExtension)==$token) {
- $token = $entry;
- $found = true;
- break;
- }
+ // Append file
+ public function appendFile($fileName, $fileData, $mkdir = false) {
+ $ok = false;
+ if ($mkdir) {
+ $path = dirname($fileName);
+ if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ }
+ $fileHandle = @fopen($fileName, "ab");
+ if ($fileHandle) {
+ clearstatcache(true, $fileName);
+ if (flock($fileHandle, LOCK_EX)) {
+ fwrite($fileHandle, $fileData);
+ flock($fileHandle, LOCK_UN);
}
+ fclose($fileHandle);
+ $ok = true;
}
- if ($directory) $token .= "/";
- return ($default || $found) ? $token : "";
+ return $ok;
}
- // Return default file in directory
- public function findFileDefault($path, $fileDefault, $fileExtension, $includePath = true) {
- $token = $fileDefault;
- if (!is_file($path."/".$fileDefault)) {
- $fileFolder = $this->normaliseToken(basename($path), $fileExtension);
- $regex = "/^[\d\-\_\.]*($fileDefault|$fileFolder)$/";
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
- if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) {
- $token = $entry;
- break;
- }
- if ($this->normaliseToken($entry, $fileExtension)==$fileFolder) {
- $token = $entry;
- break;
- }
- }
+ // Copy file
+ public function copyFile($fileNameSource, $fileNameDestination, $mkdir = false) {
+ clearstatcache();
+ if ($mkdir) {
+ $path = dirname($fileNameDestination);
+ if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
- return $includePath ? "$path/$token" : $token;
+ return @copy($fileNameSource, $fileNameDestination);
}
- // Return children from location
- public function findChildrenFromLocation($location) {
- $fileNames = array();
- $fileDefault = $this->yellow->system->get("coreContentDefaultFile");
- $fileExtension = $this->yellow->system->get("coreContentExtension");
- if (!$this->isFileLocation($location)) {
- $path = $this->findFileFromLocation($location, true);
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry) {
- $token = $this->findFileDefault($path.$entry, $fileDefault, $fileExtension, false);
- array_push($fileNames, $path.$entry."/".$token);
- }
- if (!$this->isRootLocation($location)) {
- $fileFolder = $this->normaliseToken(basename($path), $fileExtension);
- $regex = "/^.*\\".$fileExtension."$/";
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry) {
- if ($this->normaliseToken($entry, $fileExtension)==$fileDefault) continue;
- if ($this->normaliseToken($entry, $fileExtension)==$fileFolder) continue;
- array_push($fileNames, $path.$entry);
- }
- }
+ // Rename file
+ public function renameFile($fileNameSource, $fileNameDestination, $mkdir = false) {
+ clearstatcache();
+ if ($mkdir) {
+ $path = dirname($fileNameDestination);
+ if (!is_string_empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
- return $fileNames;
+ return @rename($fileNameSource, $fileNameDestination);
}
-
- // Return language from file path
- public function findLanguageFromFile($fileName, $languageDefault) {
- $language = $languageDefault;
- $pathBase = $this->yellow->system->get("coreContentDirectory");
- $pathRoot = $this->yellow->system->get("coreContentRootDirectory");
- if (!empty($pathRoot)) {
- $fileName = substru($fileName, strlenu($pathBase));
- if (preg_match("/^(.+?)\//", $fileName, $matches)) {
- $name = $this->normaliseToken($matches[1]);
- if (strlenu($name)==2) $language = $name;
- }
- }
- return $language;
+
+ // Rename directory
+ public function renameDirectory($pathSource, $pathDestination, $mkdir = false) {
+ return $pathSource==$pathDestination || $this->renameFile($pathSource, $pathDestination, $mkdir);
}
- // Return file path from media location
- public function findFileFromMedia($location) {
- $fileName = null;
- if ($this->isFileLocation($location)) {
- $mediaLocationLength = strlenu($this->yellow->system->get("coreMediaLocation"));
- if (substru($location, 0, $mediaLocationLength)==$this->yellow->system->get("coreMediaLocation")) {
- $fileName = $this->yellow->system->get("coreMediaDirectory").substru($location, 7);
- }
+ // Delete file
+ public function deleteFile($fileName, $pathTrash = "") {
+ clearstatcache();
+ if (is_string_empty($pathTrash)) {
+ $ok = @unlink($fileName);
+ } else {
+ if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
+ $fileNameDestination = $pathTrash;
+ $fileNameDestination .= pathinfo($fileName, PATHINFO_FILENAME);
+ $fileNameDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s"));
+ $fileNameDestination .= ".".pathinfo($fileName, PATHINFO_EXTENSION);
+ $ok = @rename($fileName, $fileNameDestination);
}
- return $fileName;
+ return $ok;
}
- // Return file path from system location
- public function findFileFromSystem($location) {
- $fileName = null;
- if (preg_match("/\.(css|gif|ico|js|jpg|png|svg|txt|woff|woff2)$/", $location)) {
- $extensionLocationLength = strlenu($this->yellow->system->get("coreExtensionLocation"));
- $resourceLocationLength = strlenu($this->yellow->system->get("coreResourceLocation"));
- if (substru($location, 0, $extensionLocationLength)==$this->yellow->system->get("coreExtensionLocation")) {
- $fileName = $this->yellow->system->get("coreExtensionDirectory").substru($location, $extensionLocationLength);
- } elseif (substru($location, 0, $resourceLocationLength)==$this->yellow->system->get("coreResourceLocation")) {
- $fileName = $this->yellow->system->get("coreResourceDirectory").substru($location, $resourceLocationLength);
+ // Delete directory
+ public function deleteDirectory($path, $pathTrash = "") {
+ clearstatcache();
+ if (is_string_empty($pathTrash)) {
+ $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
+ $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach ($files as $file) {
+ if ($file->getType()=="dir") {
+ @rmdir($file->getPathname());
+ } else {
+ @unlink($file->getPathname());
+ }
}
+ $ok = @rmdir($path);
+ } else {
+ if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
+ $pathDestination = $pathTrash;
+ $pathDestination .= basename($path);
+ $pathDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s"));
+ $ok = @rename($path, $pathDestination);
}
- return $fileName;
+ return $ok;
+ }
+
+ // Set file/directory modification date, Unix time
+ public function modifyFile($fileName, $modified) {
+ clearstatcache(true, $fileName);
+ return @touch($fileName, $modified);
+ }
+
+ // Return file/directory modification date, Unix time
+ public function getFileModified($fileName) {
+ return (is_file($fileName) || is_dir($fileName)) ? filemtime($fileName) : 0;
}
- // Return file path from cache if possible
- public function findFileFromCache($location, $fileName, $cacheable) {
- if ($cacheable) {
- $location .= $this->yellow->toolbox->getLocationArguments();
- $fileNameStatic = rtrim($this->yellow->system->get("coreCacheDirectory"), "/").$location;
- if (!$this->isFileLocation($location)) $fileNameStatic .= $this->yellow->system->get("coreStaticDefaultFile");
- if (is_readable($fileNameStatic)) $fileName = $fileNameStatic;
+ // Return file/directory deletion date, Unix time
+ public function getFileDeleted($fileName) {
+ $deleted = 0;
+ $text = basename($fileName);
+ $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
+ if (preg_match("#^(.+)-(\d\d\d\d-\d\d-\d\d)-(\d\d)-(\d\d)-(\d\d)$#", $text, $matches)) {
+ $deleted = strtotime("$matches[2] $matches[3]:$matches[4]:$matches[5]");
}
- return $fileName;
+ return $deleted;
}
- // Normalise file/directory token
- public function normaliseToken($text, $fileExtension = "", $removeExtension = false) {
- if (!empty($fileExtension)) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
- if (preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !empty($matches[1])) $text = $matches[1];
- return preg_replace("/[^\pL\d\-\_]/u", "-", $text).($removeExtension ? "" : $fileExtension);
+ // Return file type
+ public function getFileType($fileName) {
+ return strtoloweru(($pos = strrposu($fileName, ".")) ? substru($fileName, $pos+1) : "");
}
- // Normalise name
- public function normaliseName($text, $removePrefix = false, $removeExtension = false, $filterStrict = false) {
- if ($removeExtension) $text = ($pos = strrposu($text, ".")) ? substru($text, 0, $pos) : $text;
- if ($removePrefix && preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches) && !empty($matches[1])) $text = $matches[1];
- if ($filterStrict) $text = strtoloweru($text);
- return preg_replace("/[^\pL\d\-\_]/u", "-", $text);
+ // Return file group
+ public function getFileGroup($fileName, $path) {
+ $group = "none";
+ if (preg_match("#^$path(.+?)\/#", $fileName, $matches)) $group = strtoloweru($matches[1]);
+ return $group;
}
- // Normalise prefix
- public function normalisePrefix($text) {
- $prefix = "";
- if (preg_match("/^([\d\-\_\.]*)(.*)$/", $text, $matches)) $prefix = $matches[1];
- if (!empty($prefix) && !preg_match("/[\-\_\.]$/", $prefix)) $prefix .= "-";
- return $prefix;
+ // Return number of bytes
+ public function getNumberBytes($text) {
+ $bytes = intval($text);
+ switch (strtoupperu(substru($text, -1))) {
+ case "G": $bytes *= 1024*1024*1024; break;
+ case "M": $bytes *= 1024*1024; break;
+ case "K": $bytes *= 1024; break;
+ }
+ return $bytes;
}
- // Normalise array, make keys with same upper/lower case
- public function normaliseUpperLower($input) {
- $array = array();
- foreach ($input as $key=>$value) {
- if (empty($key) || strempty($value)) continue;
- $keySearch = strtoloweru($key);
- foreach ($array as $keyNew=>$valueNew) {
- if (strtoloweru($keyNew)==$keySearch) {
- $key = $keyNew;
- break;
- }
- }
- $array[$key] += $value;
+ // Return lines from text, including newline
+ public function getTextLines($text) {
+ $lines = preg_split("/\n/", $text);
+ foreach ($lines as &$line) {
+ $line = $line."\n";
}
- return $array;
+ if (is_string_empty($text) || substru($text, -1, 1)=="\n") array_pop($lines);
+ return $lines;
}
- // Normalise location, make absolute location
- public function normaliseLocation($location, $pageLocation, $filterStrict = true) {
- if (!preg_match("/^\w+:/", trim(html_entity_decode($location, ENT_QUOTES, "UTF-8")))) {
- $pageBase = $this->yellow->page->base;
- $mediaBase = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreMediaLocation");
- if (!preg_match("/^\#/", $location)) {
- if (!preg_match("/^\//", $location)) {
- $location = $this->getDirectoryLocation($pageBase.$pageLocation).$location;
- } elseif (!preg_match("#^($pageBase|$mediaBase)#", $location)) {
- $location = $pageBase.$location;
+ // Return settings from text
+ function getTextSettings($text, $blockStart) {
+ $settings = new YellowArray();
+ if (is_string_empty($blockStart)) {
+ foreach ($this->getTextLines($text) as $line) {
+ if (preg_match("/^\#/", $line)) continue;
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $settings[$matches[1]] = $matches[2];
+ }
}
}
- $location = strreplaceu("/./", "/", $location);
- $location = strreplaceu(":", $this->yellow->toolbox->getLocationArgumentsSeparator(), $location);
} else {
- if ($filterStrict && !preg_match("/^(http|https|ftp|mailto):/", $location)) $location = "error-xss-filter";
+ $blockKey = "";
+ foreach ($this->getTextLines($text) as $line) {
+ if (preg_match("/^\#/", $line)) continue;
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
+ $blockKey = $matches[2];
+ $settings[$blockKey] = new YellowArray();
+ }
+ if (!is_string_empty($blockKey) && !is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $settings[$blockKey][$matches[1]] = $matches[2];
+ }
+ }
+ }
}
- return $location;
+ return $settings;
}
- // Normalise URL, make absolute URL
- public function normaliseUrl($scheme, $address, $base, $location, $filterStrict = true) {
- if (!preg_match("/^\w+:/", $location)) {
- $url = "$scheme://$address$base$location";
+ // Set settings in text
+ function setTextSettings($text, $blockStart, $blockKey, $settings) {
+ $textNew = "";
+ if (is_string_empty($blockStart)) {
+ foreach ($this->getTextLines($text) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && isset($settings[$matches[1]])) {
+ $textNew .= "$matches[1]: ".$settings[$matches[1]]."\n";
+ unset($settings[$matches[1]]);
+ continue;
+ }
+ }
+ $textNew .= $line;
+ }
+ foreach ($settings as $key=>$value) {
+ $textNew .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
+ }
} else {
- if ($filterStrict && !preg_match("/^(http|https|ftp|mailto):/", $location)) $location = "error-xss-filter";
- $url = $location;
+ $scan = false;
+ $textStart = $textMiddle = $textEnd = "";
+ foreach ($this->getTextLines($text) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
+ $scan = lcfirst($matches[2])==lcfirst($blockKey);
+ }
+ }
+ if (!$scan && is_string_empty($textMiddle)) {
+ $textStart .= $line;
+ } elseif ($scan) {
+ $textMiddle .= $line;
+ } else {
+ $textEnd .= $line;
+ }
+ }
+ $textSettings = "";
+ foreach ($this->getTextLines($textMiddle) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && isset($settings[$matches[1]])) {
+ $textSettings .= "$matches[1]: ".$settings[$matches[1]]."\n";
+ unset($settings[$matches[1]]);
+ continue;
+ }
+ $textSettings .= $line;
+ }
+ }
+ foreach ($settings as $key=>$value) {
+ $textSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
+ }
+ if (!is_string_empty($textMiddle)) {
+ $textMiddle = $textSettings;
+ if (!is_string_empty($textEnd)) $textMiddle .= "\n";
+ } else {
+ if (!is_string_empty($textStart)) $textEnd .= "\n";
+ $textEnd .= $textSettings;
+ }
+ $textNew = $textStart.$textMiddle.$textEnd;
}
- return $url;
+ return $textNew;
}
-
- // Return URL information
- public function getUrlInformation($url) {
- $scheme = $address = $base = "";
- if (preg_match("#^(\w+)://([^/]+)(.*)$#", rtrim($url, "/"), $matches)) {
- $scheme = $matches[1];
- $address = $matches[2];
- $base = $matches[3];
+
+ // Remove settings from text
+ function unsetTextSettings($text, $blockStart, $blockKey) {
+ $textNew = "";
+ if (!is_string_empty($blockStart)) {
+ $scan = false;
+ $textStart = $textMiddle = $textEnd = "";
+ foreach ($this->getTextLines($text) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$blockStart && !is_string_empty($matches[2])) {
+ $scan = lcfirst($matches[2])==lcfirst($blockKey);
+ }
+ }
+ if (!$scan && is_string_empty($textMiddle)) {
+ $textStart .= $line;
+ } elseif ($scan) {
+ $textMiddle .= $line;
+ } else {
+ $textEnd .= $line;
+ }
+ }
+ $textNew = rtrim($textStart.$textEnd)."\n";
}
- return array($scheme, $address, $base);
+ return $textNew;
}
- // Return directory location
- public function getDirectoryLocation($location) {
- return ($pos = strrposu($location, "/")) ? substru($location, 0, $pos+1) : "/";
+ // Return array of specific size from text
+ public function getTextList($text, $separator, $size) {
+ $tokens = explode($separator, $text, $size);
+ return array_pad($tokens, $size, "");
}
- // Return redirect location
- public function getRedirectLocation($location) {
- if ($this->isFileLocation($location)) {
- $location = "$location/";
- } else {
- $languageDefault = $this->yellow->system->get("language");
- $language = $this->yellow->toolbox->detectBrowserLanguage($this->yellow->content->getLanguages(), $languageDefault);
- $location = "/$language/";
+ // Return array of variable size from text, space separated
+ public function getTextArguments($text, $optional = "-", $sizeMin = 9) {
+ $text = preg_replace("/\s+/s", " ", trim($text));
+ $tokens = str_getcsv($text, " ", "\"");
+ foreach ($tokens as $key=>$value) {
+ if (is_null($value) || $value==$optional) $tokens[$key] = "";
}
- return $location;
- }
-
- // Check if clean URL is requested
- public function isRequestCleanUrl($location) {
- return isset($_REQUEST["clean-url"]) && substru($location, -1, 1)=="/";
+ return array_pad($tokens, $sizeMin, "");
}
- // Check if location is specifying root
- public function isRootLocation($location) {
- return substru($location, 0, 1)!="/";
+ // Return text from array, space separated
+ public function getTextString($tokens, $optional = "-") {
+ $text = "";
+ foreach ($tokens as $token) {
+ if (preg_match("/\s/", $token)) $token = "\"$token\"";
+ if (is_string_empty($token)) $token = $optional;
+ if (!is_string_empty($text)) $text .= " ";
+ $text .= $token;
+ }
+ return $text;
}
-
- // Check if location is specifying file or directory
- public function isFileLocation($location) {
- return substru($location, -1, 1)!="/";
+
+ // Return number of words in text
+ public function getTextWords($text) {
+ $text = preg_replace("/([\p{Han}\p{Hiragana}\p{Katakana}]{3})/u", "$1 ", $text);
+ $text = preg_replace("/(\pL|\p{N})/u", "x", $text);
+ return str_word_count($text);
}
- // Check if location can be redirected into directory
- public function isRedirectLocation($location) {
- $redirect = false;
- if ($this->isFileLocation($location)) {
- $redirect = is_dir($this->findFileFromLocation("$location/", true));
- } elseif ($location=="/") {
- $redirect = $this->yellow->system->get("coreMultiLanguageMode");
+ // Return text truncated at word boundary
+ public function getTextTruncated($text, $lengthMax) {
+ if (strlenu($text)>$lengthMax-1) {
+ $text = substru($text, 0, $lengthMax);
+ $pos = strrposu($text, " ");
+ $text = substru($text, 0, $pos ? $pos : $lengthMax-1)."…";
}
- return $redirect;
+ return $text;
}
- // Check if location contains nested directories
- public function isNestedLocation($location, $fileName, $checkHomeLocation = false) {
- $nested = false;
- if (!$checkHomeLocation || $location==$this->yellow->content->getHomeLocation($location)) {
- $path = dirname($fileName);
- if (count($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false))) $nested = true;
+ // Create text description, with or without HTML
+ public function createTextDescription($text, $lengthMax = 0, $removeHtml = true, $endMarker = "", $endMarkerText = "") {
+ $output = "";
+ $elementsBlock = array("blockquote", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "li", "ol", "p", "pre", "ul");
+ $elementsVoid = array("area", "br", "col", "embed", "hr", "img", "input", "param", "source", "wbr");
+ if ($lengthMax==0) $lengthMax = strlenu($text);
+ if ($removeHtml) {
+ $hiddenLevel = 0;
+ $offsetBytes = 0;
+ while (true) {
+ $elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
+ $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
+ $elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
+ $elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
+ $elementName = isset($matches[2][0]) ? $matches[2][0] : "";
+ $elementAttributes = isset($matches[3][0]) ? $matches[3][0] : "";
+ $elementEnd = isset($matches[4][0]) ? $matches[4][0] : "";
+ if (!is_string_empty($elementBefore) && !$hiddenLevel) {
+ $rawText = preg_replace("/\s+/s", " ", html_entity_decode($elementBefore, ENT_QUOTES, "UTF-8"));
+ if (is_string_empty($elementStart) && in_array(strtolower($elementName), $elementsBlock)) $rawText = rtrim($rawText)." ";
+ if (substru($rawText, 0, 1)==" " && (is_string_empty($output) || substru($output, -1)==" ")) $rawText = ltrim($rawText);
+ $output .= $this->getTextTruncated($rawText, $lengthMax);
+ $lengthMax -= strlenu($rawText);
+ }
+ if (!is_string_empty($elementRawData) && $elementRawData==$endMarker) {
+ $output .= $endMarkerText;
+ $lengthMax = 0;
+ }
+ if ($lengthMax<=0 || !$elementFound) break;
+ if ($hiddenLevel>0 ||
+ preg_match("/aria-hidden=\"true\"/i", $elementAttributes) ||
+ preg_match("/role=\"doc-noteref\"/i", $elementAttributes)) {
+ if (!is_string_empty($elementName) && is_string_empty($elementEnd) && !in_array(strtolower($elementName), $elementsVoid)) {
+ if (is_string_empty($elementStart)) {
+ ++$hiddenLevel;
+ } else {
+ --$hiddenLevel;
+ }
+ }
+ }
+ $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
+ }
+ $output = preg_replace("/\s+\…$/s", "…", $output);
+ } else {
+ $elementsOpen = array();
+ $offsetBytes = 0;
+ while (true) {
+ $elementFound = preg_match("/&.*?\;|<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
+ $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
+ $elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
+ $elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
+ $elementName = isset($matches[2][0]) ? $matches[2][0] : "";
+ $elementEnd = isset($matches[4][0]) ? $matches[4][0] : "";
+ if (!is_string_empty($elementBefore)) {
+ $output .= $this->getTextTruncated($elementBefore, $lengthMax);
+ $lengthMax -= strlenu($elementBefore);
+ }
+ if (!is_string_empty($elementRawData) && $elementRawData==$endMarker) {
+ $output .= $endMarkerText;
+ $lengthMax = 0;
+ }
+ if ($lengthMax<=0 || !$elementFound) break;
+ if (!is_string_empty($elementName) && is_string_empty($elementEnd) && !in_array(strtolower($elementName), $elementsVoid)) {
+ if (is_string_empty($elementStart)) {
+ array_push($elementsOpen, $elementName);
+ } else {
+ array_pop($elementsOpen);
+ }
+ }
+ $output .= $elementRawData;
+ if ($elementRawData[0]=="&") --$lengthMax;
+ $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
+ }
+ $output = preg_replace("/\s+\…$/s", "…", $output);
+ for ($i=count($elementsOpen)-1; $i>=0; --$i) {
+ $output .= "</".$elementsOpen[$i].">";
+ }
}
- return $nested;
+ return trim($output);
}
- // Check if location is available
- public function isAvailableLocation($location, $fileName) {
- $available = true;
- $pathBase = $this->yellow->system->get("coreContentDirectory");
- if (substru($fileName, 0, strlenu($pathBase))==$pathBase) {
- $sharedLocation = $this->yellow->content->getHomeLocation($location).$this->yellow->system->get("coreContentSharedDirectory");
- if (substru($location, 0, strlenu($sharedLocation))==$sharedLocation) $available = false;
- }
- return $available;
+ // Create title from text
+ public function createTextTitle($text) {
+ if (preg_match("/^.*\/([\pL\d\-\_]+)/u", $text, $matches)) $text = str_replace("-", " ", ucfirst($matches[1]));
+ return $text;
}
-
- // Check if location is within current HTTP request
- public function isActiveLocation($location, $currentLocation) {
- if ($this->isFileLocation($location)) {
- $active = $currentLocation==$location;
- } else {
- if ($location==$this->yellow->content->getHomeLocation($location)) {
- $active = $this->getDirectoryLocation($currentLocation)==$location;
+
+ // Create random text for cryptography
+ public function createSalt($length, $bcryptFormat = false) {
+ $dataBuffer = $salt = "";
+ $dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
+ if (is_string_empty($dataBuffer) && function_exists("random_bytes")) {
+ $dataBuffer = @random_bytes($dataBufferSize);
+ }
+ if (is_string_empty($dataBuffer) && function_exists("openssl_random_pseudo_bytes")) {
+ $dataBuffer = @openssl_random_pseudo_bytes($dataBufferSize);
+ }
+ if (strlenb($dataBuffer)==$dataBufferSize) {
+ if ($bcryptFormat) {
+ $salt = substrb(base64_encode($dataBuffer), 0, $length);
+ $base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ $bcrypt64Chars = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ $salt = strtr($salt, $base64Chars, $bcrypt64Chars);
} else {
- $active = substru($currentLocation, 0, strlenu($location))==$location;
+ $salt = substrb(bin2hex($dataBuffer), 0, $length);
}
}
- return $active;
- }
-
- // Check if file is valid
- public function isValidFile($fileName) {
- $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
- $mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
- $systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
- return substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory") ||
- substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory") ||
- substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
+ return $salt;
}
- // Check if content file
- public function isContentFile($fileName) {
- $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
- return substru($fileName, 0, $contentDirectoryLength)==$this->yellow->system->get("coreContentDirectory");
+ // Create hash with random salt, bcrypt or sha256
+ public function createHash($text, $algorithm, $cost = 0) {
+ $hash = "";
+ switch ($algorithm) {
+ case "bcrypt": $prefix = sprintf("$2y$%02d$", $cost);
+ $salt = $this->createSalt(22, true);
+ $hash = crypt($text, $prefix.$salt);
+ if (is_string_empty($salt) || strlenb($hash)!=60) $hash = "";
+ break;
+ case "sha256": $prefix = "$5y$";
+ $salt = $this->createSalt(32);
+ $hash = "$prefix$salt".hash("sha256", $salt.$text);
+ if (is_string_empty($salt) || strlenb($hash)!=100) $hash = "";
+ break;
+ }
+ return $hash;
}
- // Check if media file
- public function isMediaFile($fileName) {
- $mediaDirectoryLength = strlenu($this->yellow->system->get("coreMediaDirectory"));
- return substru($fileName, 0, $mediaDirectoryLength)==$this->yellow->system->get("coreMediaDirectory");
+ // Verify that text matches hash
+ public function verifyHash($text, $algorithm, $hash) {
+ $hashCalculated = "";
+ switch ($algorithm) {
+ case "bcrypt": if (substrb($hash, 0, 4)=="$2y$" || substrb($hash, 0, 4)=="$2a$") {
+ $hashCalculated = crypt($text, $hash);
+ }
+ break;
+ case "sha256": if (substrb($hash, 0, 4)=="$5y$") {
+ $prefix = "$5y$";
+ $salt = substrb($hash, 4, 32);
+ $hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
+ }
+ break;
+ }
+ return $this->verifyToken($hashCalculated, $hash);
}
- // Check if system file
- public function isSystemFile($fileName) {
- $systemDirectoryLength = strlenu($this->yellow->system->get("coreSystemDirectory"));
- return substru($fileName, 0, $systemDirectoryLength)==$this->yellow->system->get("coreSystemDirectory");
- }
-}
-
-class YellowToolbox {
-
- // Return browser cookie from from current HTTP request
- public function getCookie($key) {
- return isset($_COOKIE[$key]) ? $_COOKIE[$key] : "";
- }
-
- // Return server argument from current HTTP request
- public function getServer($key) {
- return isset($_SERVER[$key]) ? $_SERVER[$key] : "";
- }
-
- // Return location arguments from current HTTP request
- public function getLocationArguments() {
- return $this->getServer("LOCATION_ARGUMENTS");
+ // Verify that token is not empty and identical, timing attack safe string comparison
+ public function verifyToken($tokenExpected, $tokenReceived) {
+ $ok = false;
+ $lengthExpected = strlenb($tokenExpected);
+ $lengthReceived = strlenb($tokenReceived);
+ if ($lengthExpected!=0 && $lengthReceived!=0) {
+ $ok = $lengthExpected==$lengthReceived;
+ for ($i=0; $i<$lengthReceived; ++$i) {
+ $ok &= $tokenExpected[$i<$lengthExpected ? $i : 0]==$tokenReceived[$i];
+ }
+ }
+ return $ok;
}
- // Return location arguments from current HTTP request, modify existing arguments
- public function getLocationArgumentsNew($key, $value) {
- $locationArguments = "";
- $found = false;
- $separator = $this->getLocationArgumentsSeparator();
- foreach (explode("/", $this->getServer("LOCATION_ARGUMENTS")) as $token) {
- if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
- if ($matches[1]==$key) {
- $matches[2] = $value;
- $found = true;
- }
- if (!empty($matches[1]) && !strempty($matches[2])) {
- if (!empty($locationArguments)) $locationArguments .= "/";
- $locationArguments .= "$matches[1]:$matches[2]";
+ // Return meta data from raw data
+ public function getMetaData($rawData, $key) {
+ $value = "";
+ if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
+ $key = lcfirst($key);
+ foreach ($this->getTextLines($parts[2]) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$key && !is_string_empty($matches[2])) {
+ $value = $matches[2];
+ break;
+ }
}
}
}
- if (!$found && !empty($key) && !strempty($value)) {
- if (!empty($locationArguments)) $locationArguments .= "/";
- $locationArguments .= "$key:$value";
- }
- if (!empty($locationArguments)) {
- $locationArguments = $this->normaliseArguments($locationArguments, false, false);
- if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
- }
- return $locationArguments;
+ return $value;
}
- // Return location arguments from current HTTP request, convert form parameters
- public function getLocationArgumentsCleanUrl() {
- $locationArguments = "";
- foreach (array_merge($_GET, $_POST) as $key=>$value) {
- if (!empty($key) && !strempty($value)) {
- if (!empty($locationArguments)) $locationArguments .= "/";
- $key = strreplaceu(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $key);
- $value = strreplaceu(array("/", ":", "="), array("\x1c", "\x1d", "\x1e"), $value);
- $locationArguments .= "$key:$value";
+ // Set meta data in raw data
+ public function setMetaData($rawData, $key, $value) {
+ if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
+ $found = false;
+ $key = lcfirst($key);
+ $rawDataMiddle = "";
+ foreach ($this->getTextLines($parts[2]) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$key) {
+ $rawDataMiddle .= "$matches[1]: $value\n";
+ $found = true;
+ continue;
+ }
+ }
+ $rawDataMiddle .= $line;
}
+ if (!$found) $rawDataMiddle .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
+ $rawDataNew = $parts[1]."---\n".$rawDataMiddle."---\n".$parts[3];
+ } else {
+ $rawDataNew = $rawData;
}
- if (!empty($locationArguments)) {
- $locationArguments = $this->normaliseArguments($locationArguments, false, false);
- if (!$this->isLocationArgumentsPagination($locationArguments)) $locationArguments .= "/";
- }
- return $locationArguments;
- }
-
- // Return location arguments separator
- public function getLocationArgumentsSeparator() {
- return (strtoupperu(substru(PHP_OS, 0, 3))!="WIN") ? ":" : "=";
+ return $rawDataNew;
}
- // Normalise path or location, take care of relative path tokens
- public function normaliseTokens($text, $prependSlash = false) {
- $textFiltered = "";
- if ($prependSlash && substru($text, 0, 1)!="/") $textFiltered .= "/";
- $textLength = strlenb($text);
- for ($pos=0; $pos<$textLength; ++$pos) {
- if (($text[$pos]=="/" || $pos==0) && $pos+1<$textLength) {
- if ($text[$pos+1]=="/") continue;
- if ($text[$pos+1]==".") {
- $posNew = $pos+1;
- while ($text[$posNew]==".") {
- ++$posNew;
- }
- if ($text[$posNew]=="/" || $text[$posNew]=="") {
- $pos = $posNew-1;
- continue;
- }
+ // Remove meta data in raw data
+ public function unsetMetaData($rawData, $key) {
+ if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
+ $key = lcfirst($key);
+ $rawDataMiddle = "";
+ foreach ($this->getTextLines($parts[2]) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])==$key) continue;
}
+ $rawDataMiddle .= $line;
}
- $textFiltered .= $text[$pos];
+ $rawDataNew = $parts[1]."---\n".$rawDataMiddle."---\n".$parts[3];
+ } else {
+ $rawDataNew = $rawData;
}
- return $textFiltered;
+ return $rawDataNew;
}
- // Normalise location arguments
- public function normaliseArguments($text, $appendSlash = true, $filterStrict = true) {
- if ($appendSlash) $text .= "/";
- if ($filterStrict) $text = strreplaceu(" ", "-", strtoloweru($text));
- $text = strreplaceu(":", $this->getLocationArgumentsSeparator(), $text);
- return strreplaceu(array("%2F","%3A","%3D"), array("/",":","="), rawurlencode($text));
+ // Return troubleshooting URL
+ public function getTroubleshootingUrl() {
+ return "https://datenstrom.se/yellow/help/troubleshooting";
+ }
+
+ // Detect server URL
+ public function detectServerUrl() {
+ $scheme = "http";
+ if ($this->getServer("REQUEST_SCHEME")=="https" || $this->getServer("HTTPS")=="on") $scheme = "https";
+ if ($this->getServer("HTTP_X_FORWARDED_PROTO")=="https") $scheme = "https";
+ $address = $this->getServer("SERVER_NAME");
+ $port = $this->getServer("SERVER_PORT");
+ if ($port!=80 && $port!=443) $address .= ":$port";
+ $base = "";
+ if (preg_match("/^(.*)\/.*\.php$/", $this->getServer("SCRIPT_NAME"), $matches)) $base = $matches[1];
+ return "$scheme://$address$base/";
}
- // Normalise elements and attributes in html/svg data
- public function normaliseData($text, $type = "html", $filterStrict = true) {
- $output = "";
- $elementsHtml = array(
- "a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "image", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meta", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "shadow", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr");
- $elementsSvg = array(
- "svg", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "feblend", "fecolormatrix", "fecomponenttransfer", "fecomposite", "feconvolvematrix", "fediffuselighting", "fedisplacementmap", "fedistantlight", "feflood", "fefunca", "fefuncb", "fefuncg", "fefuncr", "fegaussianblur", "femerge", "femergenode", "femorphology", "feoffset", "fepointlight", "fespecularlighting", "fespotlight", "fetile", "feturbulence", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "use", "view", "vkern");
- $attributesHtml = array(
- "accept", "action", "align", "allowfullscreen", "alt", "autocomplete", "background", "bgcolor", "border", "cellpadding", "cellspacing", "charset", "checked", "cite", "class", "clear", "color", "cols", "colspan", "content", "controls", "coords", "crossorigin", "datetime", "default", "dir", "disabled", "download", "enctype", "face", "for", "frameborder", "headers", "height", "hidden", "high", "href", "hreflang", "id", "integrity", "ismap", "label", "lang", "list", "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "name", "noshade", "novalidate", "nowrap", "open", "optimum", "pattern", "placeholder", "poster", "prefix", "preload", "property", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "width", "xmlns");
- $attributesSvg = array(
- "accent-height", "accumulate", "additivive", "alignment-baseline", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "datenstrom", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "fill", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "specularconstant", "specularexponent", "spreadmethod", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "tabindex", "targetx", "targety", "transform", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xlink:href", "xml:id", "xml:space", "xmlns", "y", "y1", "y2", "z", "zoomandpan");
- $elementsSafe = $elementsHtml;
- $attributesSafe = $attributesHtml;
- if ($type=="svg") {
- $elementsSafe = array_merge($elementsHtml, $elementsSvg);
- $attributesSafe = array_merge($attributesHtml, $attributesSvg);
- }
- $offsetBytes = 0;
- while (true) {
- $elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
- $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
- $elementStart = $elementFound ? $matches[1][0] : "";
- $elementName = $elementFound ? $matches[2][0]: "";
- $elementMiddle = $elementFound ? $matches[3][0]: "";
- $elementEnd = $elementFound ? $matches[4][0]: "";
- $output .= $elementBefore;
- if (substrb($elementName, 0, 1)=="!") {
- $output .= "<$elementName$elementMiddle>";
- } elseif (in_array(strtolower($elementName), $elementsSafe)) {
- $elementAttributes = $this->getTextAttributes($elementMiddle);
- foreach ($elementAttributes as $key=>$value) {
- if (!in_array(strtolower($key), $attributesSafe) && !preg_match("/^(aria|data)-/i", $key)) {
- unset($elementAttributes[$key]);
- }
- }
- if ($filterStrict) {
- $href = isset($elementAttributes["href"]) ? $elementAttributes["href"] : "";
- if (preg_match("/^\w+:/", $href) && !preg_match("/^(http|https|ftp|mailto):/", $href)) {
- $elementAttributes["href"] = "error-xss-filter";
- }
- $href = isset($elementAttributes["xlink:href"]) ? $elementAttributes["xlink:href"] : "";
- if (preg_match("/^\w+:/", $href) && !preg_match("/^(http|https|ftp|mailto):/", $href)) {
- $elementAttributes["xlink:href"] = "error-xss-filter";
+ // Detect server location
+ public function detectServerLocation() {
+ if (isset($_SERVER["REQUEST_URI"])) {
+ $location = $_SERVER["REQUEST_URI"];
+ $location = rawurldecode(($pos = strposu($location, "?")) ? substru($location, 0, $pos) : $location);
+ $location = $this->yellow->lookup->normalisePath($location);
+ if (substru($location, 0, 1)!="/") $location = "/".$location;
+ $separator = $this->getLocationArgumentsSeparator();
+ if (preg_match("/^(.*?\/)([^\/]+$separator.*)$/", $location, $matches)) {
+ $_SERVER["LOCATION"] = $location = $matches[1];
+ $_SERVER["LOCATION_ARGUMENTS"] = $matches[2];
+ foreach (explode("/", $matches[2]) as $token) {
+ if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $matches[1] = str_replace(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[1]);
+ $matches[2] = str_replace(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[2]);
+ $_REQUEST[$matches[1]] = $matches[2];
+ }
}
}
- $output .= "<$elementStart$elementName";
- foreach ($elementAttributes as $key=>$value) $output .= " $key=\"$value\"";
- if (!empty($elementEnd)) $output .= " ";
- $output .= "$elementEnd>";
+ } else {
+ $_SERVER["LOCATION"] = $location;
+ $_SERVER["LOCATION_ARGUMENTS"] = "";
}
- if (!$elementFound) break;
- $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
}
- return $output;
+ return $this->getServer("LOCATION");
}
- // Normalise text lines, convert line endings
- public function normaliseLines($text, $endOfLine = "lf") {
- if ($endOfLine=="lf") {
- $text = preg_replace("/\R/u", "\n", $text);
- } else {
- $text = preg_replace("/\R/u", "\r\n", $text);
+ // Detect server sitename
+ public function detectServerSitename() {
+ $sitename = "Localhost";
+ if (preg_match("#^(www\.)?([\w\-]+)#", $this->getServer("SERVER_NAME"), $matches)) {
+ $sitename = ucfirst($matches[2]);
}
- return $text;
+ return $sitename;
}
- // Normalise text into UTF-8 NFC
- public function normaliseUnicode($text) {
- if (PHP_OS=="Darwin" && !mb_check_encoding($text, "ASCII")) {
- $utf8nfc = preg_match("//u", $text) && !preg_match("/[^\\x00-\\x{2FF}]/u", $text);
- if (!$utf8nfc) $text = iconv("UTF-8-MAC", "UTF-8", $text);
+ // Detect server timezone
+ public function detectServerTimezone() {
+ $timezone = ini_get("date.timezone");
+ if (is_string_empty($timezone)) {
+ if (PHP_OS=="Darwin") {
+ if (preg_match("#zoneinfo/(.*)#", @readlink("/etc/localtime"), $matches)) $timezone = $matches[1];
+ } else {
+ if (preg_match("/^(\S+)\/(\S+)/", $this->readFile("/etc/timezone"), $matches)) $timezone = $matches[1];
+ }
}
- return $text;
- }
-
- // Return human readable HTTP date
- public function getHttpDateFormatted($timestamp) {
- return gmdate("D, d M Y H:i:s", $timestamp)." GMT";
+ if (!in_array($timezone, timezone_identifiers_list())) $timezone = "UTC";
+ return $timezone;
}
- // Return human readable HTTP server status
- public function getHttpStatusFormatted($statusCode, $shortFormat = false) {
- switch ($statusCode) {
- case 0: $text = "No data"; break;
- case 200: $text = "OK"; break;
- case 301: $text = "Moved permanently"; break;
- case 302: $text = "Moved temporarily"; break;
- case 303: $text = "Reload please"; break;
- case 304: $text = "Not modified"; break;
- case 400: $text = "Bad request"; break;
- case 403: $text = "Forbidden"; break;
- case 404: $text = "Not found"; break;
- case 430: $text = "Login failed"; break;
- case 434: $text = "Not existing"; break;
- case 500: $text = "Server error"; break;
- case 503: $text = "Service unavailable"; break;
- default: $text = "Error $statusCode";
+ // Detect server name, version and operating system
+ public function detectServerInformation() {
+ $name = "Unknown";
+ $version = "x.x.x";
+ $os = PHP_OS;
+ if (preg_match("/^(\S+)\/(\S+)/", $this->getServer("SERVER_SOFTWARE"), $matches)) {
+ $name = $matches[1];
+ $version = $matches[2];
+ } elseif (preg_match("/^(\S+)/", $this->getServer("SERVER_SOFTWARE"), $matches)) {
+ $name = $matches[1];
}
- $serverProtocol = $this->getServer("SERVER_PROTOCOL");
- if (!preg_match("/^HTTP\//", $serverProtocol)) $serverProtocol = "HTTP/1.1";
- return $shortFormat ? $text : "$serverProtocol $statusCode $text";
+ if (PHP_SAPI=="cli" || PHP_SAPI=="cli-server") {
+ $name = "Built-in";
+ $version = PHP_VERSION;
+ }
+ if (PHP_OS=="Darwin") {
+ $os = "Mac";
+ } elseif (strtoupperu(substru(PHP_OS, 0, 3))=="WIN") {
+ $os = "Windows";
+ }
+ return array($name, $version, $os);
}
- // Return MIME content type
- public function getMimeContentType($fileName) {
- $contentType = "";
- $contentTypes = array(
- "css" => "text/css",
- "gif" => "image/gif",
- "html" => "text/html; charset=utf-8",
- "ico" => "image/x-icon",
- "js" => "application/javascript",
- "json" => "application/json",
- "jpg" => "image/jpeg",
- "md" => "text/markdown",
- "png" => "image/png",
- "svg" => "image/svg+xml",
- "txt" => "text/plain",
- "woff" => "application/font-woff",
- "woff2" => "application/font-woff2",
- "xml" => "text/xml; charset=utf-8");
- $fileType = $this->getFileType($fileName);
- if (empty($fileType)) {
- $contentType = $contentTypes["html"];
- } elseif (array_key_exists($fileType, $contentTypes)) {
- $contentType = $contentTypes[$fileType];
+ // Detect browser language
+ public function detectBrowserLanguage($languages, $languageDefault) {
+ $languageFound = $languageDefault;
+ foreach (preg_split("/\s*,\s*/", $this->getServer("HTTP_ACCEPT_LANGUAGE")) as $text) {
+ list($language, $dummy) = $this->getTextList($text, ";", 2);
+ if (!is_string_empty($language) && in_array($language, $languages)) {
+ $languageFound = $language;
+ break;
+ }
}
- return $contentType;
+ return $languageFound;
}
- // Return file type
- public function getFileType($fileName) {
- return strtoloweru(($pos = strrposu($fileName, ".")) ? substru($fileName, $pos+1) : "");
- }
-
- // Return file group
- public function getFileGroup($fileName, $path) {
- $group = "none";
- if (preg_match("#^$path(.+?)\/#", $fileName, $matches)) $group = strtoloweru($matches[1]);
- return $group;
- }
-
- // Return number of bytes
- public function getNumberBytes($string) {
- $bytes = intval($string);
- switch (strtoupperu(substru($string, -1))) {
- case "G": $bytes *= 1024*1024*1024; break;
- case "M": $bytes *= 1024*1024; break;
- case "K": $bytes *= 1024; break;
- }
- return $bytes;
- }
-
- // Return files and directories
- public function getDirectoryEntries($path, $regex = "/.*/", $sort = true, $directories = true, $includePath = true) {
- $entries = array();
- $directoryHandle = @opendir($path);
- if ($directoryHandle) {
- $path = rtrim($path, "/");
- while (($entry = readdir($directoryHandle))!==false) {
- if (substru($entry, 0, 1)==".") continue;
- $entry = $this->normaliseUnicode($entry);
- if (preg_match($regex, $entry)) {
- if ($directories) {
- if (is_dir("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
- } else {
- if (is_file("$path/$entry")) array_push($entries, $includePath ? "$path/$entry" : $entry);
- }
- }
+ // Detect terminal width and height
+ public function detectTerminalInformation() {
+ $width = $height = 0;
+ if (strtoupperu(substru(PHP_OS, 0, 3))=="WIN") {
+ exec("powershell \$Host.UI.RawUI.WindowSize.Width", $outputLines, $returnStatus);
+ if ($returnStatus==0 && !is_array_empty($outputLines)) {
+ $width = intval(end($outputLines));
}
- if ($sort) natcasesort($entries);
- closedir($directoryHandle);
- }
- return $entries;
- }
-
- // Return files and directories recursively
- public function getDirectoryEntriesRecursive($path, $regex = "/.*/", $sort = true, $directories = true, $levelMax = 0) {
- --$levelMax;
- $entries = $this->getDirectoryEntries($path, $regex, $sort, $directories);
- if ($levelMax!=0) {
- foreach ($this->getDirectoryEntries($path, "/.*/", $sort, true) as $entry) {
- $entries = array_merge($entries, $this->getDirectoryEntriesRecursive($entry, $regex, $sort, $directories, $levelMax));
+ exec("powershell \$Host.UI.RawUI.WindowSize.Height", $outputLines, $returnStatus);
+ if ($returnStatus==0 && !is_array_empty($outputLines)) {
+ $height = intval(end($outputLines));
+ }
+ } else {
+ exec("stty size", $outputLines, $returnStatus);
+ if ($returnStatus==0 && preg_match("/^(\d+)\s+(\d+)/", implode("\n", $outputLines), $matches)) {
+ $width = intval($matches[2]);
+ $height = intval($matches[1]);
}
}
- return $entries;
+ return array($width, $height);
}
- // Read file, empty string if not found
- public function readFile($fileName, $sizeMax = 0) {
- $fileData = "";
+ // Detect image width, height, orientation and type for GIF/JPG/PNG/SVG
+ public function detectImageInformation($fileName, $fileType = "") {
+ $width = $height = $orientation = 0;
+ $type = "";
$fileHandle = @fopen($fileName, "rb");
if ($fileHandle) {
- clearstatcache(true, $fileName);
- $fileSize = $sizeMax ? $sizeMax : filesize($fileName);
- if ($fileSize) $fileData = fread($fileHandle, $fileSize);
- fclose($fileHandle);
- }
- return $fileData;
- }
-
- // Create file
- public function createFile($fileName, $fileData, $mkdir = false) {
- $ok = false;
- if ($mkdir) {
- $path = dirname($fileName);
- if (!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
- }
- $fileHandle = @fopen($fileName, "wb");
- if ($fileHandle) {
- clearstatcache(true, $fileName);
- if (flock($fileHandle, LOCK_EX)) {
- ftruncate($fileHandle, 0);
- fwrite($fileHandle, $fileData);
- flock($fileHandle, LOCK_UN);
+ if (is_string_empty($fileType)) $fileType = $this->getFileType($fileName);
+ if ($fileType=="gif") {
+ $dataSignature = fread($fileHandle, 6);
+ $dataHeader = fread($fileHandle, 7);
+ if (!feof($fileHandle) && ($dataSignature=="GIF87a" || $dataSignature=="GIF89a")) {
+ $width = (ord($dataHeader[1])<<8) + ord($dataHeader[0]);
+ $height = (ord($dataHeader[3])<<8) + ord($dataHeader[2]);
+ $type = $fileType;
+ }
+ } elseif ($fileType=="jpg") {
+ $dataBufferSizeMax = filesize($fileName);
+ $dataBufferSize = min($dataBufferSizeMax, 4096);
+ if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
+ $dataSignature = substrb($dataBuffer, 0, 2);
+ if (!feof($fileHandle) && $dataSignature=="\xff\xd8") {
+ for ($pos=2; $pos+8<$dataBufferSize; $pos+=$length) {
+ if ($dataBuffer[$pos]!="\xff") break;
+ $dataMarker = $dataBuffer[$pos+1];
+ if ($dataMarker=="\xe1") {
+ $orientation = $this->getImageOrientationFromBuffer($dataBuffer, $pos+4, $dataBufferSize);
+ }
+ if (($dataMarker>="\xc0" && $dataMarker<="\xc3") ||
+ ($dataMarker>="\xc5" && $dataMarker<="\xc7") ||
+ ($dataMarker>="\xc9" && $dataMarker<="\xcb") ||
+ ($dataMarker>="\xcd" && $dataMarker<="\xcf")) {
+ $width = (ord($dataBuffer[$pos+7])<<8) + ord($dataBuffer[$pos+8]);
+ $height = (ord($dataBuffer[$pos+5])<<8) + ord($dataBuffer[$pos+6]);
+ $type = $fileType;
+ break;
+ }
+ $length = (ord($dataBuffer[$pos+2])<<8) + ord($dataBuffer[$pos+3]) + 2;
+ while ($pos+$length+8>=$dataBufferSize) {
+ if ($dataBufferSize==$dataBufferSizeMax) break;
+ $dataBufferDiff = min($dataBufferSizeMax, $dataBufferSize*2) - $dataBufferSize;
+ $dataBufferSize += $dataBufferDiff;
+ $dataBufferChunk = fread($fileHandle, $dataBufferDiff);
+ if (feof($fileHandle) || $dataBufferChunk===false) {
+ $dataBufferSize = 0;
+ break;
+ }
+ $dataBuffer .= $dataBufferChunk;
+ }
+ }
+ }
+ } elseif ($fileType=="png") {
+ $dataSignature = fread($fileHandle, 8);
+ $dataHeader = fread($fileHandle, 16);
+ if (!feof($fileHandle) && $dataSignature=="\x89PNG\r\n\x1a\n") {
+ $width = (ord($dataHeader[10])<<8) + ord($dataHeader[11]);
+ $height = (ord($dataHeader[14])<<8) + ord($dataHeader[15]);
+ $type = $fileType;
+ }
+ } elseif ($fileType=="svg") {
+ $dataBufferSizeMax = filesize($fileName);
+ $dataBufferSize = min($dataBufferSizeMax, 4096);
+ if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
+ if (!feof($fileHandle) && preg_match("/<svg(\s.*?)>/s", $dataBuffer, $matches)) {
+ if (preg_match("/\swidth=\"(\d+)\"/s", $matches[1], $tokens)) $width = $tokens[1];
+ if (preg_match("/\sheight=\"(\d+)\"/s", $matches[1], $tokens)) $height = $tokens[1];
+ $type = $fileType;
+ }
}
fclose($fileHandle);
- $ok = true;
- }
- return $ok;
- }
-
- // Append file
- public function appendFile($fileName, $fileData, $mkdir = false) {
- $ok = false;
- if ($mkdir) {
- $path = dirname($fileName);
- if (!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
}
- $fileHandle = @fopen($fileName, "ab");
- if ($fileHandle) {
- clearstatcache(true, $fileName);
- if (flock($fileHandle, LOCK_EX)) {
- fwrite($fileHandle, $fileData);
- flock($fileHandle, LOCK_UN);
+ return array($width, $height, $orientation, $type);
+ }
+
+ // Return image orientation from Exif
+ public function getImageOrientationFromBuffer($dataBuffer, $pos, $size) {
+ $orientation = 0;
+ $dataSignature = substrb($dataBuffer, $pos, 6);
+ if ($dataSignature=="\x45\x78\x69\x66\x00\x00" && $pos+14<=$size) {
+ $startPos = $pos+6;
+ $bigEndian = $dataBuffer[$startPos]=="M";
+ $ifdOffset = $this->getLongFromBuffer($dataBuffer, $startPos+4, $bigEndian);
+ $ifdStartPos = $startPos+$ifdOffset;
+ $ifdCount = $ifdStartPos+2<=$size ? $this->getShortFromBuffer($dataBuffer, $ifdStartPos, $bigEndian) : 0;
+ $pos = $ifdStartPos+2;
+ while ($ifdCount && $pos+12<=$size) {
+ $ifdTag = $this->getShortFromBuffer($dataBuffer, $pos, $bigEndian);
+ $ifdFormat = $this->getShortFromBuffer($dataBuffer, $pos+2, $bigEndian);
+ if ($ifdTag==0x8769 && $ifdFormat==4) {
+ $ifdOffset = $this->getLongFromBuffer($dataBuffer, $pos+8, $bigEndian);
+ $ifdStartPos = $startPos+$ifdOffset;
+ $ifdCount = $ifdStartPos+2<=$size ? $this->getShortFromBuffer($dataBuffer, $ifdStartPos, $bigEndian) : 0;
+ $pos = $ifdStartPos+2;
+ continue;
+ }
+ if ($ifdTag==0x0112 && $ifdFormat==3) {
+ $orientation = $this->getShortFromBuffer($dataBuffer, $pos+8, $bigEndian);
+ break;
+ }
+ --$ifdCount;
+ $pos += 12;
}
- fclose($fileHandle);
- $ok = true;
}
- return $ok;
+ return $orientation;
}
- // Copy file
- public function copyFile($fileNameSource, $fileNameDestination, $mkdir = false) {
- clearstatcache();
- if ($mkdir) {
- $path = dirname($fileNameDestination);
- if (!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ // Return unsigned short value from buffer
+ public function getShortFromBuffer($dataBuffer, $pos, $bigEndian) {
+ if ($bigEndian) {
+ $value = (ord($dataBuffer[$pos])<<8) + ord($dataBuffer[$pos+1]);
+ } else {
+ $value = (ord($dataBuffer[$pos+1])<<8) + ord($dataBuffer[$pos]);
}
- return @copy($fileNameSource, $fileNameDestination);
+ return $value;
}
- // Rename file
- public function renameFile($fileNameSource, $fileNameDestination, $mkdir = false) {
- clearstatcache();
- if ($mkdir) {
- $path = dirname($fileNameDestination);
- if (!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ // Return unsigned long value from buffer
+ public function getLongFromBuffer($dataBuffer, $pos, $bigEndian) {
+ if ($bigEndian) {
+ $value = (ord($dataBuffer[$pos])<<24) + (ord($dataBuffer[$pos+1])<<16) +
+ (ord($dataBuffer[$pos+2])<<8) + ord($dataBuffer[$pos+3]);
+ } else {
+ $value = (ord($dataBuffer[$pos+3])<<24) + (ord($dataBuffer[$pos+2])<<16) +
+ (ord($dataBuffer[$pos+1])<<8) + ord($dataBuffer[$pos]);
}
- return @rename($fileNameSource, $fileNameDestination);
- }
-
- // Rename directory
- public function renameDirectory($pathSource, $pathDestination, $mkdir = false) {
- return $pathSource==$pathDestination || $this->renameFile($pathSource, $pathDestination, $mkdir);
+ return $value;
}
- // Delete file
- public function deleteFile($fileName, $pathTrash = "") {
- clearstatcache();
- if (empty($pathTrash)) {
- $ok = @unlink($fileName);
- } else {
- if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
- $fileNameDestination = $pathTrash;
- $fileNameDestination .= pathinfo($fileName, PATHINFO_FILENAME);
- $fileNameDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s", filemtime($fileName)));
- $fileNameDestination .= ".".pathinfo($fileName, PATHINFO_EXTENSION);
- $ok = @rename($fileName, $fileNameDestination);
+ // Send email message
+ public function mail($action, $headers, $message) {
+ $statusCode = 0;
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onMail")) {
+ $statusCode = $value["object"]->onMail($action, $headers, $message);
+ if ($statusCode!=0) break;
+ }
}
- return $ok;
- }
-
- // Delete directory
- public function deleteDirectory($path, $pathTrash = "") {
- clearstatcache();
- if (empty($pathTrash)) {
- $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
- $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST);
- foreach ($files as $file) {
- if ($file->getType()=="dir") {
- @rmdir($file->getPathname());
+ if ($statusCode==0) {
+ $text = $this->yellow->lookup->normaliseHeaders($headers, "mime");
+ $to = $subject = $remaining = $key = "";
+ foreach (preg_split("/\r\n/", $text) as $line) {
+ if (preg_match("/^(.*?):\s*(.*?)$/", $line, $matches) && !is_string_empty($matches[1])) {
+ $key = $matches[1];
+ $fragment = $matches[2];
} else {
- @unlink($file->getPathname());
+ $fragment = $line;
}
+ if ($key=="To") { $to .= $fragment; continue; }
+ if ($key=="Subject") { $subject .= $fragment; continue; }
+ $remaining .= $line."\r\n";
}
- $ok = @rmdir($path);
- } else {
- if (!is_dir($pathTrash)) @mkdir($pathTrash, 0777, true);
- $pathDestination = $pathTrash;
- $pathDestination .= basename($path);
- $pathDestination .= "-".str_replace(array(" ", ":"), "-", date("Y-m-d H:i:s", filemtime($path)));
- $ok = @rename($path, $pathDestination);
+ $statusCode = mail($to, $subject, $message, $remaining) ? 200 : 500;
}
- return $ok;
+ return $statusCode==200;
+ }
+
+ // Write information to log file
+ public function log($action, $message) {
+ $statusCode = 0;
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onLog")) {
+ $statusCode = $value["object"]->onLog($action, $message);
+ if ($statusCode!=0) break;
+ }
+ }
+ if ($statusCode==0) {
+ $line = date("Y-m-d H:i:s")." ".trim($action)." ".trim($message)."\n";
+ $this->appendFile($this->yellow->system->get("coreServerInstallDirectory").
+ $this->yellow->system->get("coreExtensionDirectory").
+ $this->yellow->system->get("coreWebsiteFile"), $line);
+ }
+ }
+
+ // Start timer
+ public function timerStart(&$time) {
+ $time = microtime(true);
}
- // Set file modification date, Unix time
- public function modifyFile($fileName, $modified) {
- clearstatcache(true, $fileName);
- return @touch($fileName, $modified);
+ // Stop timer and calculate elapsed time in milliseconds
+ public function timerStop(&$time) {
+ $time = intval((microtime(true)-$time) * 1000);
}
- // Return file modification date, Unix time
- public function getFileModified($fileName) {
- return is_file($fileName) ? filemtime($fileName) : 0;
+ // Check if there are location arguments in current HTTP request
+ public function isLocationArguments($location = "") {
+ if (is_string_empty($location)) $location = $this->getServer("LOCATION").$this->getServer("LOCATION_ARGUMENTS");
+ $separator = $this->getLocationArgumentsSeparator();
+ return preg_match("/[^\/]+$separator.*$/", $location);
}
- // Return lines from text string, including newline
- public function getTextLines($text) {
- $lines = preg_split("/\n/", $text);
- foreach ($lines as &$line) {
- $line = $line."\n";
- }
- if (strempty($text) || substru($text, -1, 1)=="\n") array_pop($lines);
- return $lines;
+ // Check if there are pagination arguments in current HTTP request
+ public function isLocationArgumentsPagination($location) {
+ $separator = $this->getLocationArgumentsSeparator();
+ return preg_match("/^(.*\/)?page$separator.*$/", $location);
+ }
+
+ // Check if unmodified since last HTTP request
+ public function isNotModified($lastModifiedFormatted) {
+ return $this->getServer("HTTP_IF_MODIFIED_SINCE")==$lastModifiedFormatted;
}
- // Return attributes from text string
- public function getTextAttributes($text) {
- $tokens = array();
- $posStart = $posQuote = 0;
- $textLength = strlenb($text);
- for ($pos=0; $pos<$textLength; ++$pos) {
- if ($text[$pos]==" " && !$posQuote) {
- if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
- $posStart = $pos+1;
- }
- if ($text[$pos]=="=" && !$posQuote) {
- if ($pos>$posStart) array_push($tokens, substrb($text, $posStart, $pos-$posStart));
- array_push($tokens, "=");
- $posStart = $pos+1;
- }
- if ($text[$pos]=="\"") {
- if ($posQuote) {
- if ($pos>$posQuote) array_push($tokens, substrb($text, $posQuote+1, $pos-$posQuote-1));
- $posQuote = 0;
- $posStart = $pos+1;
- } else {
- if ($pos==$posStart) $posQuote = $pos;
- }
- }
- }
- if ($pos>$posStart && !$posQuote) {
- array_push($tokens, substrb($text, $posStart, $pos-$posStart));
- }
- $attributes = array();
- for ($i=0; $i<count($tokens); ++$i) {
- if ($i+2<count($tokens) && $tokens[$i+1]=="=") {
- $key = $tokens[$i];
- $value = $tokens[$i+2];
- $i += 2;
- } else {
- $key = $value = $tokens[$i];
- }
- if (!strempty($key) && !strempty($value)) {
- $attributes[$key] = $value;
- }
- }
- return $attributes;
+ // TODO: remove later, for backwards compatibility
+ public function normaliseArguments($text, $appendSlash = true, $filterStrict = true) { return $this->yellow->lookup->normaliseArguments($text, $appendSlash, $filterStrict); }
+ public function normalisePath($text) { return $this->yellow->lookup->normalisePath($text); }
+}
+
+class YellowPage {
+ public $yellow; // access to API
+ public $scheme; // server scheme
+ public $address; // server address
+ public $base; // base location
+ public $location; // page location
+ public $fileName; // content file name
+ public $rawData; // raw data of page
+ public $metaDataOffsetBytes; // meta data offset
+ public $metaData; // meta data
+ public $pageCollections; // additional pages
+ public $sharedPages; // shared pages
+ public $headerData; // response header
+ public $outputData; // response output
+ public $parser; // content parser
+ public $parserData; // content data of page
+ public $statusCode; // status code
+ public $errorMessage; // error message
+ public $lastModified; // last modification date
+ public $available; // page is available? (boolean)
+ public $visible; // page is visible location? (boolean)
+ public $active; // page is active location? (boolean)
+ public $cacheable; // page is cacheable? (boolean)
+
+ public function __construct($yellow) {
+ $this->yellow = $yellow;
+ $this->scheme = "";
+ $this->address = "";
+ $this->base = "";
+ $this->location = "";
+ $this->fileName = "";
+ $this->metaData = new YellowArray();
+ $this->pageCollections = array();
+ $this->sharedPages = array();
+ $this->headerData = array();
+ }
+
+ // Set request information
+ public function setRequestInformation($scheme, $address, $base, $location, $fileName, $cacheable) {
+ $this->scheme = $scheme;
+ $this->address = $address;
+ $this->base = $base;
+ $this->location = $location;
+ $this->fileName = $fileName;
+ $this->cacheable = $cacheable;
}
- // Return array of specific size from text string
- public function getTextList($text, $separator, $size) {
- $tokens = explode($separator, $text, $size);
- return array_pad($tokens, $size, null);
+ // Parse page meta
+ public function parseMeta($rawData, $statusCode = 0, $errorMessage = "") {
+ $this->rawData = $rawData;
+ $this->parser = null;
+ $this->parserData = "";
+ $this->statusCode = $statusCode;
+ $this->errorMessage = $errorMessage;
+ $this->lastModified = 0;
+ $this->available = true;
+ $this->visible = true;
+ $this->active = $this->yellow->lookup->isActiveLocation($this->location, $this->yellow->page->location);
+ $this->parseMetaData();
}
- // Return arguments from text string, space separated
- public function getTextArguments($text, $optional = "-", $sizeMin = 9) {
- $text = preg_replace("/\s+/s", " ", trim($text));
- $tokens = str_getcsv($text, " ", "\"");
- foreach ($tokens as $key=>$value) {
- if ($value==$optional) $tokens[$key] = "";
+ // Parse page meta update
+ public function parseMetaUpdate() {
+ if ($this->statusCode==0) {
+ $this->rawData = $this->yellow->toolbox->readFile($this->fileName);
+ $this->statusCode = 200;
+ $this->parseMetaData();
}
- return array_pad($tokens, $sizeMin, null);
}
- // Return text string from arguments, space separated
- public function getTextString($tokens, $optional = "-") {
- $text = "";
- foreach ($tokens as $token) {
- if (preg_match("/\s/", $token)) $token = "\"$token\"";
- if (empty($token)) $token = $optional;
- if (!empty($text)) $text .= " ";
- $text .= $token;
+ // Parse page meta data
+ public function parseMetaData() {
+ $this->metaData = new YellowArray();
+ $this->metaDataOffsetBytes = 0;
+ if (!is_null($this->rawData)) {
+ $this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
+ $this->set("language", $this->yellow->lookup->findContentLanguage($this->fileName, $this->yellow->system->get("language")));
+ $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
+ $this->parseMetaDataRaw(array("sitename", "author", "layout", "theme", "parser", "status"));
+ $this->parseMetaDataShared();
+ $titleHeader = ($this->location==$this->yellow->content->getHomeLocation($this->location)) ?
+ $this->get("sitename") : $this->get("title")." - ".$this->get("sitename");
+ if (!$this->isExisting("titleContent")) $this->set("titleContent", $this->get("title"));
+ if (!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
+ if (!$this->isExisting("titleHeader")) $this->set("titleHeader", $titleHeader);
+ if ($this->get("status")=="unlisted") $this->visible = false;
+ if ($this->get("status")=="shared") $this->available = false;
+ } else {
+ $this->set("size", filesize($this->fileName));
+ $this->set("type", $this->yellow->toolbox->getFileType($this->fileName));
+ $this->set("group", $this->yellow->toolbox->getFileGroup($this->fileName, $this->yellow->system->get("coreMediaDirectory")));
+ $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
+ }
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParseMetaData")) $value["object"]->onParseMetaData($this);
}
- return $text;
- }
-
- // Return number of words in text string
- public function getTextWords($text) {
- $text = preg_replace("/([\p{Han}\p{Hiragana}\p{Katakana}]{3})/u", "$1 ", $text);
- $text = preg_replace("/(\pL|\p{N})/u", "x", $text);
- return str_word_count($text);
}
- // Return text string truncated at word boundary
- public function getTextTruncated($text, $lengthMax) {
- if (strlenu($text)>$lengthMax-1) {
- $text = substru($text, 0, $lengthMax);
- $pos = strrposu($text, " ");
- $text = substru($text, 0, $pos ? $pos : $lengthMax-1)."…";
+ // Parse page meta data from raw data
+ public function parseMetaDataRaw($defaultKeys) {
+ foreach ($defaultKeys as $key) {
+ $value = $this->yellow->system->get($key);
+ if (!is_string_empty($key) && !is_string_empty($value)) $this->set($key, $value);
+ }
+ if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+/s", $this->rawData, $parts)) {
+ $this->metaDataOffsetBytes = strlenb($parts[0]);
+ foreach (preg_split("/[\r\n]+/", $parts[2]) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) $this->set($matches[1], $matches[2]);
+ }
+ }
+ } elseif (preg_match("/^(\xEF\xBB\xBF)?([^\r\n]+)[\r\n]+=+[\r\n]+/", $this->rawData, $parts)) {
+ $this->metaDataOffsetBytes = strlenb($parts[0]);
+ $this->set("title", $parts[2]);
}
- return $text;
}
- // Create description from text string
- public function createTextDescription($text, $lengthMax = 0, $removeHtml = true, $endMarker = "", $endMarkerText = "") {
- $output = "";
- $elementsBlock = array("blockquote", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "li", "ol", "p", "pre", "ul");
- $elementsVoid = array("area", "br", "col", "embed", "hr", "img", "input", "param", "source", "wbr");
- if ($lengthMax==0) $lengthMax = strlenu($text);
- if ($removeHtml) {
- $offsetBytes = 0;
- while (true) {
- $elementFound = preg_match("/<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
- $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
- $elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
- $elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
- $elementName = isset($matches[2][0]) ? $matches[2][0] : "";
- if (!strempty($elementBefore)) {
- $rawText = preg_replace("/\s+/s", " ", html_entity_decode($elementBefore, ENT_QUOTES, "UTF-8"));
- if (empty($elementStart) && in_array(strtolower($elementName), $elementsBlock)) $rawText = rtrim($rawText)." ";
- if (substru($rawText, 0, 1)==" " && (empty($output) || substru($output, -1)==" ")) $rawText = ltrim($rawText);
- $output .= $this->getTextTruncated($rawText, $lengthMax);
- $lengthMax -= strlenu($rawText);
- }
- if (!empty($elementRawData) && $elementRawData==$endMarker) {
- $output .= $endMarkerText;
- $lengthMax = 0;
- }
- if ($lengthMax<=0 || !$elementFound) break;
- $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
+ // Parse page meta data for shared pages
+ public function parseMetaDataShared() {
+ $this->sharedPages["main"] = $this;
+ if (!$this->yellow->lookup->isSharedLocation($this->location) && $this->statusCode!=0) {
+ foreach ($this->yellow->content->getShared($this->location) as $page) {
+ $this->sharedPages[basename($page->location)] = $page;
+ $page->sharedPages["main"] = $this;
}
- $output = preg_replace("/\s+\…$/s", "…", $output);
- } else {
- $elementsOpen = array();
- $offsetBytes = 0;
- while (true) {
- $elementFound = preg_match("/&.*?\;|<(\/?)([\!\?\w]+)(.*?)(\/?)>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
- $elementBefore = $elementFound ? substrb($text, $offsetBytes, $matches[0][1] - $offsetBytes) : substrb($text, $offsetBytes);
- $elementRawData = isset($matches[0][0]) ? $matches[0][0] : "";
- $elementStart = isset($matches[1][0]) ? $matches[1][0] : "";
- $elementName = isset($matches[2][0]) ? $matches[2][0] : "";
- $elementEnd = isset($matches[4][0]) ? $matches[4][0] : "";
- if (!strempty($elementBefore)) {
- $output .= $this->getTextTruncated($elementBefore, $lengthMax);
- $lengthMax -= strlenu($elementBefore);
- }
- if (!empty($elementRawData) && $elementRawData==$endMarker) {
- $output .= $endMarkerText;
- $lengthMax = 0;
- }
- if ($lengthMax<=0 || !$elementFound) break;
- if (!empty($elementName) && empty($elementEnd) && !in_array(strtolower($elementName), $elementsVoid)) {
- if (empty($elementStart)) {
- array_push($elementsOpen, $elementName);
- } else {
- array_pop($elementsOpen);
+ }
+ if ($this->yellow->lookup->isSharedLocation($this->location)) {
+ $this->set("status", "shared");
+ }
+ }
+
+ // Parse page content on demand
+ public function parseContent() {
+ if (!is_null($this->rawData) && !is_object($this->parser)) {
+ if ($this->yellow->extension->isExisting($this->get("parser"))) {
+ $value = $this->yellow->extension->data[$this->get("parser")];
+ if (method_exists($value["object"], "onParseContentRaw")) {
+ $this->parser = $value["object"];
+ $this->parserData = $this->getContentRaw();
+ $this->parserData = $this->parser->onParseContentRaw($this, $this->parserData);
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParseContentHtml")) {
+ $output = $value["object"]->onParseContentHtml($this, $this->parserData);
+ if (!is_null($output)) $this->parserData = $output;
+ }
}
}
- $output .= $elementRawData;
- if ($elementRawData[0]=="&") --$lengthMax;
- $offsetBytes = $matches[0][1] + strlenb($matches[0][0]);
+ } else {
+ $this->parserData = $this->getContentRaw();
+ $this->parserData = preg_replace("/\[yellow error\]/i", $this->errorMessage, $this->parserData);
}
- $output = preg_replace("/\s+\…$/s", "…", $output);
- for ($i=count($elementsOpen)-1; $i>=0; --$i) {
- $output .= "</".$elementsOpen[$i].">";
+ if (!$this->isExisting("description")) {
+ $description = $this->yellow->toolbox->createTextDescription($this->parserData, 150);
+ $this->set("description", !is_string_empty($description) ? $description : $this->get("title"));
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=3) {
+ echo "YellowPage::parseContent location:".$this->location."<br/>\n";
}
}
- return trim($output);
}
- // Create title from text string
- public function createTextTitle($text) {
- if (preg_match("/^.*\/([\pL\d\-\_]+)/u", $text, $matches)) $text = strreplaceu("-", " ", ucfirst($matches[1]));
- return $text;
+ // Parse page content shortcut
+ public function parseContentShortcut($name, $text, $type) {
+ $output = null;
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParseContentShortcut")) {
+ $output = $value["object"]->onParseContentShortcut($this, $name, $text, $type);
+ if (!is_null($output)) break;
+ }
+ }
+ if (is_null($output)) {
+ if ($name=="yellow" && $type=="inline" && $text=="error") {
+ $output = $this->errorMessage;
+ }
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=3 && !is_string_empty($name)) {
+ echo "YellowPage::parseContentShortcut name:$name type:$type<br/>\n";
+ }
+ return $output;
}
-
- // Create random text for cryptography
- public function createSalt($length, $bcryptFormat = false) {
- $dataBuffer = $salt = "";
- $dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
- if (empty($dataBuffer) && function_exists("random_bytes")) {
- $dataBuffer = @random_bytes($dataBufferSize);
+
+ // Parse page
+ public function parsePage() {
+ $this->parsePageLayout($this->get("layout"));
+ if (!$this->isCacheable()) $this->setHeader("Cache-Control", "no-cache, no-store");
+ if (!$this->isHeader("Content-Type")) $this->setHeader("Content-Type", "text/html; charset=utf-8");
+ if (!$this->isHeader("Content-Modified")) $this->setHeader("Content-Modified", $this->getModified(true));
+ if (!$this->isHeader("Last-Modified")) $this->setHeader("Last-Modified", $this->getLastModified(true));
+ $fileNameTheme = $this->yellow->system->get("coreThemeDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
+ if (!is_file($fileNameTheme)) {
+ $this->error(500, "Theme '".$this->get("theme")."' does not exist!");
}
- if (empty($dataBuffer) && function_exists("mcrypt_create_iv")) {
- $dataBuffer = @mcrypt_create_iv($dataBufferSize, MCRYPT_DEV_URANDOM);
+ if (!$this->yellow->language->isExisting($this->get("language"))) {
+ $this->error(500, "Language '".$this->get("language")."' does not exist!");
}
- if (empty($dataBuffer) && function_exists("openssl_random_pseudo_bytes")) {
- $dataBuffer = @openssl_random_pseudo_bytes($dataBufferSize);
+ if (!is_object($this->parser)) {
+ $this->error(500, "Parser '".$this->get("parser")."' does not exist!");
}
- if (strlenb($dataBuffer)==$dataBufferSize) {
- if ($bcryptFormat) {
- $salt = substrb(base64_encode($dataBuffer), 0, $length);
- $base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- $bcrypt64Chars = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- $salt = strtr($salt, $base64Chars, $bcrypt64Chars);
- } else {
- $salt = substrb(bin2hex($dataBuffer), 0, $length);
+ if ($this->yellow->lookup->isNestedLocation($this->location, $this->fileName, true)) {
+ $this->error(500, "Folder '".dirname($this->fileName)."' may not contain subfolders!");
+ }
+ if ($this->yellow->lookup->getRequestHandler()=="core" && $this->isExisting("redirect") && $this->statusCode==200) {
+ $location = $this->yellow->lookup->normaliseLocation($this->get("redirect"), $this->location);
+ $location = $this->yellow->lookup->normaliseUrl($this->scheme, $this->address, "", $location);
+ $this->status(301, $location);
+ }
+ if ($this->yellow->lookup->getRequestHandler()=="core" && !$this->isAvailable() && $this->statusCode==200) {
+ $this->error(404);
+ }
+ if ($this->isExisting("pageClean")) $this->outputData = null;
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParsePageOutput")) {
+ $output = $value["object"]->onParsePageOutput($this, $this->outputData);
+ if (!is_null($output)) $this->outputData = $output;
}
}
- return $salt;
}
- // Create hash with random salt, bcrypt or sha256
- public function createHash($text, $algorithm, $cost = 0) {
- $hash = "";
- switch ($algorithm) {
- case "bcrypt": $prefix = sprintf("$2y$%02d$", $cost);
- $salt = $this->createSalt(22, true);
- $hash = crypt($text, $prefix.$salt);
- if (empty($salt) || strlenb($hash)!=60) $hash = "";
- break;
- case "sha256": $prefix = "$5y$";
- $salt = $this->createSalt(32);
- $hash = "$prefix$salt".hash("sha256", $salt.$text);
- if (empty($salt) || strlenb($hash)!=100) $hash = "";
- break;
+ // Parse page layout
+ public function parsePageLayout($name) {
+ $this->outputData = null;
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParsePageLayout")) {
+ $value["object"]->onParsePageLayout($this, $name);
+ }
+ }
+ if (is_null($this->outputData)) {
+ ob_start();
+ $this->includeLayout($name);
+ $this->outputData = ob_get_contents();
+ ob_end_clean();
}
- return $hash;
}
- // Verify that text matches hash
- public function verifyHash($text, $algorithm, $hash) {
- $hashCalculated = "";
- switch ($algorithm) {
- case "bcrypt": if (substrb($hash, 0, 4)=="$2y$" || substrb($hash, 0, 4)=="$2a$") {
- $hashCalculated = crypt($text, $hash);
- }
- break;
- case "sha256": if (substrb($hash, 0, 4)=="$5y$") {
- $prefix = "$5y$";
- $salt = substrb($hash, 4, 32);
- $hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
- }
- break;
+ // Include page layout
+ public function includeLayout($name) {
+ $fileNameLayoutNormal = $this->yellow->system->get("coreLayoutDirectory").$this->yellow->lookup->normaliseName($name).".html";
+ $fileNameLayoutTheme = $this->yellow->system->get("coreLayoutDirectory").
+ $this->yellow->lookup->normaliseName($this->get("theme"))."-".$this->yellow->lookup->normaliseName($name).".html";
+ if (is_file($fileNameLayoutTheme)) {
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowPage::includeLayout file:$fileNameLayoutTheme<br/>\n";
+ }
+ $this->setLastModified(filemtime($fileNameLayoutTheme));
+ require($fileNameLayoutTheme);
+ } elseif (is_file($fileNameLayoutNormal)) {
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowPage::includeLayout file:$fileNameLayoutNormal<br/>\n";
+ }
+ $this->setLastModified(filemtime($fileNameLayoutNormal));
+ require($fileNameLayoutNormal);
+ } else {
+ $this->error(500, "Layout '$name' does not exist!");
+ echo "Layout error<br/>\n";
}
- return $this->verifyToken($hashCalculated, $hash);
}
- // Verify that token is not empty and identical, timing attack safe text string comparison
- public function verifyToken($tokenExpected, $tokenReceived) {
- $ok = false;
- $lengthExpected = strlenb($tokenExpected);
- $lengthReceived = strlenb($tokenReceived);
- if ($lengthExpected!=0 && $lengthReceived!=0) {
- $ok = $lengthExpected==$lengthReceived;
- for ($i=0; $i<$lengthReceived; ++$i) {
- $ok &= $tokenExpected[$i<$lengthExpected ? $i : 0]==$tokenReceived[$i];
+ // Set page setting
+ public function set($key, $value) {
+ $this->metaData[$key] = $value;
+ }
+
+ // Return page setting
+ public function get($key) {
+ return $this->isExisting($key) ? $this->metaData[$key] : "";
+ }
+
+ // Return page setting, HTML encoded
+ public function getHtml($key) {
+ return htmlspecialchars($this->get($key));
+ }
+
+ // Return page setting as language specific date
+ public function getDate($key, $format = "") {
+ if (!is_string_empty($format)) {
+ $format = $this->yellow->language->getText($format);
+ } else {
+ $format = $this->yellow->language->getText("coreDateFormatMedium");
+ }
+ return $this->yellow->language->getDateFormatted(strtotime($this->get($key)), $format);
+ }
+
+ // Return page setting as language specific date, HTML encoded
+ public function getDateHtml($key, $format = "") {
+ return htmlspecialchars($this->getDate($key, $format));
+ }
+
+ // Return page setting as language specific date, relative to today
+ public function getDateRelative($key, $format = "", $daysLimit = 30) {
+ if (!is_string_empty($format)) {
+ $format = $this->yellow->language->getText($format);
+ } else {
+ $format = $this->yellow->language->getText("coreDateFormatMedium");
+ }
+ return $this->yellow->language->getDateRelative(strtotime($this->get($key)), $format, $daysLimit);
+ }
+
+ // Return page setting as language specific date, relative to today, HTML encoded
+ public function getDateRelativeHtml($key, $format = "", $daysLimit = 30) {
+ return htmlspecialchars($this->getDateRelative($key, $format, $daysLimit));
+ }
+
+ // Return page setting as date
+ public function getDateFormatted($key, $format) {
+ return $this->yellow->language->getDateFormatted(strtotime($this->get($key)), $format);
+ }
+
+ // Return page setting as date, HTML encoded
+ public function getDateFormattedHtml($key, $format) {
+ return htmlspecialchars($this->getDateFormatted($key, $format));
+ }
+
+ // Return page content data, raw format
+ public function getContentRaw() {
+ $this->parseMetaUpdate();
+ return substrb($this->rawData, $this->metaDataOffsetBytes);
+ }
+
+ // Return page content data, HTML encoded or raw format
+ public function getContentHtml() {
+ $this->parseContent();
+ return $this->parserData;
+ }
+
+ // Return page extra data, HTML encoded
+ public function getExtraHtml($name) {
+ $output = "";
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onParsePageExtra")) {
+ $outputExtension = $value["object"]->onParsePageExtra($this, $name);
+ if (!is_null($outputExtension)) $output .= $outputExtension;
}
}
- return $ok;
+ if ($name=="header") {
+ $fileNameTheme = $this->yellow->system->get("coreThemeDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
+ if (is_file($fileNameTheme)) {
+ $locationTheme = $this->yellow->system->get("coreServerBase").
+ $this->yellow->system->get("coreThemeLocation").$this->yellow->lookup->normaliseName($this->get("theme")).".css";
+ $output .= "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"$locationTheme\" />\n";
+ }
+ $fileNameScript = $this->yellow->system->get("coreThemeDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".js";
+ if (is_file($fileNameScript)) {
+ $locationScript = $this->yellow->system->get("coreServerBase").
+ $this->yellow->system->get("coreThemeLocation").$this->yellow->lookup->normaliseName($this->get("theme")).".js";
+ $output .= "<script type=\"text/javascript\" src=\"$locationScript\"></script>\n";
+ }
+ $fileNameFavicon = $this->yellow->system->get("coreThemeDirectory").$this->yellow->lookup->normaliseName($this->get("theme")).".png";
+ if (is_file($fileNameFavicon)) {
+ $locationFavicon = $this->yellow->system->get("coreServerBase").
+ $this->yellow->system->get("coreThemeLocation").$this->yellow->lookup->normaliseName($this->get("theme")).".png";
+ $output .= "<link rel=\"icon\" type=\"image/png\" href=\"$locationFavicon\" />\n";
+ }
+ }
+ return $output;
}
- // Return meta data from raw data
- public function getMetaData($rawData, $key) {
- $value = "";
- if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
- $key = lcfirst($key);
- foreach ($this->getTextLines($parts[2]) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])==$key && !strempty($matches[2])) {
- $value = $matches[2];
+ // Return parent page, null if none
+ public function getParent() {
+ $parentLocation = $this->yellow->content->getParentLocation($this->location);
+ return $this->yellow->content->find($parentLocation);
+ }
+
+ // Return top-level parent page, null if none
+ public function getParentTop($homeFallback = false) {
+ $parentTopLocation = $this->yellow->content->getParentTopLocation($this->location);
+ if (!$this->yellow->content->find($parentTopLocation) && $homeFallback) {
+ $parentTopLocation = $this->yellow->content->getHomeLocation($this->location);
+ }
+ return $this->yellow->content->find($parentTopLocation);
+ }
+
+ // Return page collection with pages on the same level
+ public function getSiblings($showInvisible = false) {
+ $parentLocation = $this->yellow->content->getParentLocation($this->location);
+ return $this->yellow->content->getChildren($parentLocation, $showInvisible);
+ }
+
+ // Return page collection with child pages
+ public function getChildren($showInvisible = false) {
+ return $this->yellow->content->getChildren($this->location, $showInvisible);
+ }
+
+ // Return page collection with child pages recursively
+ public function getChildrenRecursive($showInvisible = false, $levelMax = 0) {
+ return $this->yellow->content->getChildrenRecursive($this->location, $showInvisible, $levelMax);
+ }
+
+ // Set page collection with additional pages
+ public function setPages($key, $pages) {
+ $this->pageCollections[$key] = $pages;
+ }
+
+ // Return page collection with additional pages
+ public function getPages($key) {
+ return isset($this->pageCollections[$key]) ? $this->pageCollections[$key] : new YellowPageCollection($this->yellow);
+ }
+
+ // Set shared page
+ public function setPage($key, $page) {
+ $this->sharedPages[$key] = $page;
+ }
+
+ // Return shared page
+ public function getPage($key) {
+ return isset($this->sharedPages[$key]) ? $this->sharedPages[$key] : new YellowPage($this->yellow);
+ }
+
+ // Return page URL
+ public function getUrl() {
+ return $this->yellow->lookup->normaliseUrl($this->scheme, $this->address, $this->base, $this->location);
+ }
+
+ // Return page base
+ public function getBase($multiLanguage = false) {
+ return $multiLanguage ? rtrim($this->base.$this->yellow->content->getHomeLocation($this->location), "/") : $this->base;
+ }
+
+ // Return page location
+ public function getLocation($absoluteLocation = false) {
+ return $absoluteLocation ? $this->base.$this->location : $this->location;
+ }
+
+ // Set page request argument
+ public function setRequest($key, $value) {
+ $_REQUEST[$key] = $value;
+ }
+
+ // Return page request argument
+ public function getRequest($key) {
+ return isset($_REQUEST[$key]) ? $_REQUEST[$key] : "";
+ }
+
+ // Return page request argument, HTML encoded
+ public function getRequestHtml($key) {
+ return htmlspecialchars($this->getRequest($key));
+ }
+
+ // Set page response header
+ public function setHeader($key, $value) {
+ $this->headerData[$key] = $value;
+ }
+
+ // Return page response header
+ public function getHeader($key) {
+ return $this->isHeader($key) ? $this->headerData[$key] : "";
+ }
+
+ // Set page response output
+ public function setOutput($output) {
+ $this->outputData = $output;
+ }
+
+ // Return page modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ $modified = strtotime($this->get("modified"));
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
+ }
+
+ // Set last modification date, Unix time
+ public function setLastModified($modified) {
+ $this->lastModified = max($this->lastModified, $modified);
+ }
+
+ // Return last modification date, Unix time or HTTP format
+ public function getLastModified($httpFormat = false) {
+ $lastModified = max($this->lastModified, $this->getModified(), $this->yellow->system->getModified(),
+ $this->yellow->language->getModified(), $this->yellow->extension->getModified());
+ foreach ($this->pageCollections as $pages) $lastModified = max($lastModified, $pages->getModified());
+ foreach ($this->sharedPages as $page) $lastModified = max($lastModified, $page->getModified());
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($lastModified) : $lastModified;
+ }
+
+ // Return raw data for error page
+ public function getRawDataError() {
+ $statusCode = $this->statusCode;
+ $sharedLocation = $this->yellow->content->getHomeLocation($this->location)."shared/";
+ $fileNameError = $this->yellow->lookup->findFileFromContentLocation($sharedLocation, true).$this->yellow->system->get("coreContentErrorFile");
+ $fileNameError = str_replace("(.*)", $statusCode, $fileNameError);
+ $languageError = $this->yellow->lookup->findContentLanguage($this->fileName, $this->yellow->system->get("language"));
+ if (is_file($fileNameError)) {
+ $rawData = $this->yellow->toolbox->readFile($fileNameError);
+ } elseif ($this->yellow->language->isText("coreError{$statusCode}Title", $languageError)) {
+ $rawData = "---\nTitle: ".$this->yellow->language->getText("coreError{$statusCode}Title", $languageError)."\n";
+ $rawData .= "Layout: error\n---\n".$this->yellow->language->getText("coreError{$statusCode}Text", $languageError);
+ } else {
+ $rawData = "---\nTitle:".$this->yellow->toolbox->getHttpStatusFormatted($statusCode, true)."\n";
+ $rawData .= "Layout:error\n---\n".$this->errorMessage;
+ }
+ return $rawData;
+ }
+
+ // Return page status code, number or HTTP format
+ public function getStatusCode($httpFormat = false) {
+ $statusCode = $this->statusCode;
+ if ($httpFormat) {
+ $statusCode = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
+ if (!is_string_empty($this->errorMessage)) $statusCode .= ": ".$this->errorMessage;
+ }
+ return $statusCode;
+ }
+
+ // Respond with status code, no page content
+ public function status($statusCode, $location = "") {
+ if ($statusCode>0 && !$this->isExisting("pageClean")) {
+ $this->statusCode = $statusCode;
+ $this->lastModified = 0;
+ $this->headerData = array();
+ if (!is_string_empty($location)) {
+ $this->setHeader("Location", $location);
+ $this->setHeader("Cache-Control", "no-cache, no-store");
+ }
+ $this->set("pageClean", (string)$statusCode);
+ }
+ }
+
+ // Respond with error page
+ public function error($statusCode, $errorMessage = "") {
+ if ($statusCode>=400 && is_string_empty($this->errorMessage)) {
+ $this->statusCode = $statusCode;
+ $this->errorMessage = is_string_empty($errorMessage) ? "Page error!" : $errorMessage;
+ }
+ }
+
+ // Check if page is available
+ public function isAvailable() {
+ return $this->available;
+ }
+
+ // Check if page is visible
+ public function isVisible() {
+ return $this->visible;
+ }
+
+ // Check if page is within current HTTP request
+ public function isActive() {
+ return $this->active;
+ }
+
+ // Check if page is cacheable
+ public function isCacheable() {
+ return $this->cacheable;
+ }
+
+ // Check if page with error
+ public function isError() {
+ return $this->statusCode>=400;
+ }
+
+ // Check if page setting exists
+ public function isExisting($key) {
+ return isset($this->metaData[$key]);
+ }
+
+ // Check if request argument exists
+ public function isRequest($key) {
+ return isset($_REQUEST[$key]);
+ }
+
+ // Check if response header exists
+ public function isHeader($key) {
+ return isset($this->headerData[$key]);
+ }
+
+ // Check if shared page exists
+ public function isPage($key) {
+ return isset($this->sharedPages[$key]);
+ }
+
+ // TODO: remove later, for backwards compatibility
+ public function getContent($rawFormat = false) { return $rawFormat ? $this->getContentRaw() : $this->getContentHtml(); }
+ public function getExtra($name) { return $this->getExtraHtml($name); }
+}
+
+class YellowPageCollection extends ArrayObject {
+ public $yellow; // access to API
+ public $filterValue; // current page filter value
+ public $paginationNumber; // current page number in pagination
+ public $paginationCount; // highest page number in pagination
+
+ public function __construct($yellow) {
+ parent::__construct(array());
+ $this->yellow = $yellow;
+ }
+
+ // Append page to end of page collection
+ #[\ReturnTypeWillChange]
+ public function append($page) {
+ parent::append($page);
+ }
+
+ // Prepend page to start of page collection
+ #[\ReturnTypeWillChange]
+ public function prepend($page) {
+ $array = $this->getArrayCopy();
+ array_unshift($array, $page);
+ $this->exchangeArray($array);
+ }
+
+ // Remove page from page collection
+ public function remove($page): YellowPageCollection {
+ $array = array();
+ $location = $page->location;
+ foreach ($this->getArrayCopy() as $page) {
+ if ($page->location!=$location) array_push($array, $page);
+ }
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Filter page collection by page setting
+ public function filter($key, $value, $exactMatch = true): YellowPageCollection {
+ $array = array();
+ $value = str_replace(" ", "-", strtoloweru($value));
+ $valueLength = strlenu($value);
+ $this->filterValue = "";
+ foreach ($this->getArrayCopy() as $page) {
+ if ($page->isExisting($key)) {
+ foreach (preg_split("/\s*,\s*/", $page->get($key)) as $pageValue) {
+ $pageValueLength = $exactMatch ? strlenu($pageValue) : $valueLength;
+ if ($value==substru(str_replace(" ", "-", strtoloweru($pageValue)), 0, $pageValueLength)) {
+ if (is_string_empty($this->filterValue)) $this->filterValue = substru($pageValue, 0, $pageValueLength);
+ array_push($array, $page);
break;
}
}
}
}
- return $value;
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Filter page collection by location or file
+ public function match($regex = "/.*/", $filterByLocation = true): YellowPageCollection {
+ $array = array();
+ $this->filterValue = $regex;
+ foreach ($this->getArrayCopy() as $page) {
+ if (preg_match($regex, $filterByLocation ? $page->location : $page->fileName)) array_push($array, $page);
+ }
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Sort page collection by settings similarity
+ public function similar($page): YellowPageCollection {
+ $location = $page->location;
+ $keywords = strtoloweru($page->get("title").",".$page->get("tag").",".$page->get("author"));
+ $tokens = array_unique(array_filter(preg_split("/[,\s\(\)\+\-]/", $keywords), "strlen"));
+ if (!is_array_empty($tokens)) {
+ $array = array();
+ foreach ($this->getArrayCopy() as $page) {
+ $sortScore = 0;
+ foreach ($tokens as $token) {
+ if (stristr($page->get("title"), $token)) $sortScore += 50;
+ if (stristr($page->get("tag"), $token)) $sortScore += 5;
+ if (stristr($page->get("author"), $token)) $sortScore += 2;
+ }
+ if ($page->location!=$location) {
+ $page->set("sortScore", $sortScore);
+ array_push($array, $page);
+ }
+ }
+ $this->exchangeArray($array);
+ $this->sort("modified", false)->sort("sortScore", false);
+ }
+ return $this;
+ }
+
+ // Sort page collection by page setting
+ public function sort($key, $ascendingOrder = true): YellowPageCollection {
+ $array = $this->getArrayCopy();
+ $sortIndex = 0;
+ foreach ($array as $page) {
+ $page->set("sortIndex", ++$sortIndex);
+ }
+ $callback = function ($a, $b) use ($key, $ascendingOrder) {
+ $result = $ascendingOrder ?
+ strnatcasecmp($a->get($key), $b->get($key)) :
+ strnatcasecmp($b->get($key), $a->get($key));
+ return $result==0 ? $a->get("sortIndex") - $b->get("sortIndex") : $result;
+ };
+ usort($array, $callback);
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Group page collection by page setting, return array with multiple collections
+ public function group($key, $ascendingOrder = true, $format = ""): array {
+ $array = array();
+ $groupByInitial = $format=="initial";
+ $groupByDate = !is_string_empty($format) && $format!="count" && $format!="initial";
+ foreach ($this->getIterator() as $page) {
+ if ($page->isExisting($key)) {
+ foreach (preg_split("/\s*,\s*/", $page->get($key)) as $group) {
+ if ($groupByInitial) {
+ $group = strtoupperu(substru($group, 0, 1));
+ } elseif ($groupByDate) {
+ $group = $this->yellow->language->getDateFormatted(strtotime($group), $format);
+ }
+ if (!is_string_empty($group)) {
+ if (!isset($array[$group])) {
+ $groupSearch = strtoloweru($group);
+ foreach (array_keys($array) as $groupFound) {
+ if (strtoloweru($groupFound)==$groupSearch) {
+ $group = $groupFound;
+ break;
+ }
+ }
+ if (!isset($array[$group])) $array[$group] = new YellowPageCollection($this->yellow);
+ }
+ $array[$group]->append($page);
+ }
+ }
+ }
+ }
+ $callbackString = function ($a, $b) use ($ascendingOrder) {
+ return $ascendingOrder ? strnatcasecmp($a, $b) : strnatcasecmp($b, $a);
+ };
+ $callbackCollection = function ($a, $b) use ($ascendingOrder) {
+ return $ascendingOrder ? count($a)-count($b) : count($b)-count($a);
+ };
+ if ($format!="count") {
+ uksort($array, $callbackString);
+ } else {
+ uasort($array, $callbackCollection);
+ }
+ return $array;
+ }
+
+ // Calculate union, merge page collection
+ public function merge($input): YellowPageCollection {
+ $this->exchangeArray(array_merge($this->getArrayCopy(), (array)$input));
+ return $this;
+ }
+
+ // Calculate intersection, remove pages that are not present in another page collection
+ public function intersect($input): YellowPageCollection {
+ $callback = function ($a, $b) {
+ return strcmp($a->location, $b->location);
+ };
+ $this->exchangeArray(array_uintersect($this->getArrayCopy(), (array)$input, $callback));
+ return $this;
+ }
+
+ // Calculate difference, remove pages that are present in another page collection
+ public function diff($input): YellowPageCollection {
+ $callback = function ($a, $b) {
+ return strcmp($a->location, $b->location);
+ };
+ $this->exchangeArray(array_udiff($this->getArrayCopy(), (array)$input, $callback));
+ return $this;
+ }
+
+ // Limit the number of pages in page collection
+ public function limit($pagesMax): YellowPageCollection {
+ $this->exchangeArray(array_slice($this->getArrayCopy(), 0, $pagesMax));
+ return $this;
+ }
+
+ // Reverse page collection
+ public function reverse(): YellowPageCollection {
+ $this->exchangeArray(array_reverse($this->getArrayCopy()));
+ return $this;
+ }
+
+ // Randomize page collection
+ public function shuffle(): YellowPageCollection {
+ $array = $this->getArrayCopy();
+ shuffle($array);
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Paginate page collection
+ public function paginate($limit): YellowPageCollection {
+ if (!$this->isPagination() && $limit!=0) {
+ $this->paginationNumber = 1;
+ $this->paginationCount = ceil($this->count() / $limit);
+ if ($this->yellow->page->isRequest("page")) {
+ $this->paginationNumber = intval($this->yellow->page->getRequest("page"));
+ }
+ if ($this->paginationNumber<0 || $this->paginationNumber>$this->paginationCount) $this->paginationNumber = 0;
+ if ($this->paginationNumber) {
+ $this->exchangeArray(array_slice($this->getArrayCopy(), ($this->paginationNumber - 1) * $limit, $limit));
+ } else {
+ $this->yellow->page->error(404);
+ }
+ }
+ return $this;
}
- // Set meta data in raw data
- public function setMetaData($rawData, $key, $value) {
- if (preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)\-\-\-[\r\n]+(.*)$/s", $rawData, $parts)) {
- $found = false;
- $key = lcfirst($key);
- foreach ($this->getTextLines($parts[2]) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])==$key) {
- $rawDataNew .= "$matches[1]: $value\n";
- $found = true;
- continue;
- }
- }
- $rawDataNew .= $line;
- }
- if (!$found) $rawDataNew .= ucfirst($key).": $value\n";
- $rawDataNew = $parts[1]."---\n".$rawDataNew."---\n".$parts[3];
- } else {
- $rawDataNew = $rawData;
- }
- return $rawDataNew;
+ // Return current page number in pagination
+ public function getPaginationNumber() {
+ return $this->paginationNumber;
}
- // Detect server URL
- public function detectServerUrl() {
- $scheme = !empty($this->getServer("HTTPS")) && $this->getServer("HTTPS")!="off" ? "https" : "http";
- $address = $this->getServer("SERVER_NAME");
- $port = $this->getServer("SERVER_PORT");
- if ($port!=80 && $port!=443) $address .= ":$port";
- $base = "";
- if (preg_match("/^(.*)\/.*\.php$/", $this->getServer("SCRIPT_NAME"), $matches)) $base = $matches[1];
- return "$scheme://$address$base/";
+ // Return highest page number in pagination
+ public function getPaginationCount() {
+ return $this->paginationCount;
}
- // Detect server location
- public function detectServerLocation() {
- if (isset($_SERVER["REQUEST_URI"])) {
- $location = $_SERVER["REQUEST_URI"];
- $location = rawurldecode(($pos = strposu($location, "?")) ? substru($location, 0, $pos) : $location);
- $location = $this->normaliseTokens($location, true);
- $separator = $this->getLocationArgumentsSeparator();
- if (preg_match("/^(.*?\/)([^\/]+$separator.*)$/", $location, $matches)) {
- $_SERVER["LOCATION"] = $location = $matches[1];
- $_SERVER["LOCATION_ARGUMENTS"] = $matches[2];
- foreach (explode("/", $matches[2]) as $token) {
- if (preg_match("/^(.*?)$separator(.*)$/", $token, $matches)) {
- if (!empty($matches[1]) && !strempty($matches[2])) {
- $matches[1] = strreplaceu(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[1]);
- $matches[2] = strreplaceu(array("\x1c", "\x1d", "\x1e"), array("/", ":", "="), $matches[2]);
- $_REQUEST[$matches[1]] = $matches[2];
- }
- }
- }
- } else {
- $_SERVER["LOCATION"] = $location;
- $_SERVER["LOCATION_ARGUMENTS"] = "";
- }
+ // Return location for a page in pagination
+ public function getPaginationLocation($absoluteLocation = true, $pageNumber = 1) {
+ $location = $locationArguments = "";
+ if ($pageNumber>=1 && $pageNumber<=$this->paginationCount) {
+ $location = $this->yellow->page->getLocation($absoluteLocation);
+ $locationArguments = $this->yellow->toolbox->getLocationArgumentsNew("page", $pageNumber>1 ? "$pageNumber" : "");
}
- return $this->getServer("LOCATION");
+ return $location.$locationArguments;
}
- // Detect server timezone
- public function detectServerTimezone() {
- $timezone = @date_default_timezone_get();
- if (PHP_OS=="Darwin" && $timezone=="UTC") {
- if (preg_match("#zoneinfo/(.*)#", @readlink("/etc/localtime"), $matches)) $timezone = $matches[1];
- }
- return $timezone;
+ // Return location for previous page in pagination
+ public function getPaginationPrevious($absoluteLocation = true) {
+ $pageNumber = $this->paginationNumber-1;
+ return $this->getPaginationLocation($absoluteLocation, $pageNumber);
}
- // Detect server name and version
- public function detectServerInformation() {
- if (preg_match("/^(\S+)\/(\S+)/", $this->getServer("SERVER_SOFTWARE"), $matches)) {
- $name = $matches[1];
- $version = $matches[2];
- } elseif (preg_match("/^(\pL+)/u", $this->getServer("SERVER_SOFTWARE"), $matches)) {
- $name = $matches[1];
- $version = "x.x.x";
- } else {
- $name = "CLI";
- $version = PHP_VERSION;
- }
- return array($name, $version);
+ // Return location for next page in pagination
+ public function getPaginationNext($absoluteLocation = true) {
+ $pageNumber = $this->paginationNumber+1;
+ return $this->getPaginationLocation($absoluteLocation, $pageNumber);
}
- // Detect browser language
- public function detectBrowserLanguage($languages, $languageDefault) {
- $languageFound = $languageDefault;
- foreach (preg_split("/\s*,\s*/", $this->getServer("HTTP_ACCEPT_LANGUAGE")) as $string) {
- list($language, $dummy) = $this->getTextList($string, ";", 2);
- if (!empty($language) && in_array($language, $languages)) {
- $languageFound = $language;
+ // Return current page number in collection
+ public function getPageNumber($page) {
+ $pageNumber = 0;
+ foreach ($this->getIterator() as $key=>$value) {
+ if ($page->getLocation()==$value->getLocation()) {
+ $pageNumber = $key+1;
break;
}
}
- return $languageFound;
+ return $pageNumber;
}
- // Detect image dimensions and type for gif/jpg/png/svg
- public function detectImageInformation($fileName, $fileType = "") {
- $width = $height = 0;
- $type = "";
- $fileHandle = @fopen($fileName, "rb");
- if ($fileHandle) {
- if (empty($fileType)) $fileType = $this->getFileType($fileName);
- if ($fileType=="gif") {
- $dataSignature = fread($fileHandle, 6);
- $dataHeader = fread($fileHandle, 7);
- if (!feof($fileHandle) && ($dataSignature=="GIF87a" || $dataSignature=="GIF89a")) {
- $width = (ord($dataHeader[1])<<8) + ord($dataHeader[0]);
- $height = (ord($dataHeader[3])<<8) + ord($dataHeader[2]);
- $type = $fileType;
- }
- } elseif ($fileType=="jpg") {
- $dataBufferSizeMax = filesize($fileName);
- $dataBufferSize = min($dataBufferSizeMax, 4096);
- if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
- $dataSignature = substrb($dataBuffer, 0, 4);
- if (!feof($fileHandle) && ($dataSignature=="\xff\xd8\xff\xe0" || $dataSignature=="\xff\xd8\xff\xe1")) {
- for ($pos=2; $pos+8<$dataBufferSize; $pos+=$length) {
- if ($dataBuffer[$pos]!="\xff") break;
- if ($dataBuffer[$pos+1]=="\xc0" || $dataBuffer[$pos+1]=="\xc2") {
- $width = (ord($dataBuffer[$pos+7])<<8) + ord($dataBuffer[$pos+8]);
- $height = (ord($dataBuffer[$pos+5])<<8) + ord($dataBuffer[$pos+6]);
- $type = $fileType;
- break;
- }
- $length = (ord($dataBuffer[$pos+2])<<8) + ord($dataBuffer[$pos+3]) + 2;
- while ($pos+$length+8>=$dataBufferSize) {
- if ($dataBufferSize==$dataBufferSizeMax) break;
- $dataBufferDiff = min($dataBufferSizeMax, $dataBufferSize*2) - $dataBufferSize;
- $dataBufferSize += $dataBufferDiff;
- $dataBufferChunk = fread($fileHandle, $dataBufferDiff);
- if (feof($fileHandle) || $dataBufferChunk===false) {
- $dataBufferSize = 0;
- break;
- }
- $dataBuffer .= $dataBufferChunk;
- }
- }
- }
- } elseif ($fileType=="png") {
- $dataSignature = fread($fileHandle, 8);
- $dataHeader = fread($fileHandle, 16);
- if (!feof($fileHandle) && $dataSignature=="\x89PNG\r\n\x1a\n") {
- $width = (ord($dataHeader[10])<<8) + ord($dataHeader[11]);
- $height = (ord($dataHeader[14])<<8) + ord($dataHeader[15]);
- $type = $fileType;
- }
- } elseif ($fileType=="svg") {
- $dataBufferSizeMax = filesize($fileName);
- $dataBufferSize = min($dataBufferSizeMax, 4096);
- if ($dataBufferSize) $dataBuffer = fread($fileHandle, $dataBufferSize);
- if (!feof($fileHandle) && preg_match("/<svg(\s.*?)>/s", $dataBuffer, $matches)) {
- if (preg_match("/\swidth=\"(\d+)\"/s", $matches[1], $tokens)) $width = $tokens[1];
- if (preg_match("/\sheight=\"(\d+)\"/s", $matches[1], $tokens)) $height = $tokens[1];
- $type = $fileType;
- }
- }
- fclose($fileHandle);
- }
- return array($width, $height, $type);
+ // Return page in collection, null if none
+ public function getPage($pageNumber = 1) {
+ return ($pageNumber>=1 && $pageNumber<=$this->count()) ? $this->offsetGet($pageNumber-1) : null;
}
- // Start timer
- public function timerStart(&$time) {
- $time = microtime(true);
+ // Return previous page in collection, null if none
+ public function getPagePrevious($page) {
+ $pageNumber = $this->getPageNumber($page)-1;
+ return $this->getPage($pageNumber);
}
- // Stop timer and calculate elapsed time in milliseconds
- public function timerStop(&$time) {
- $time = intval((microtime(true)-$time) * 1000);
+ // Return next page in collection, null if none
+ public function getPageNext($page) {
+ $pageNumber = $this->getPageNumber($page)+1;
+ return $this->getPage($pageNumber);
}
- // Check if there are location arguments in current HTTP request
- public function isLocationArguments($location = "") {
- if (empty($location)) $location = $this->getServer("LOCATION").$this->getServer("LOCATION_ARGUMENTS");
- $separator = $this->getLocationArgumentsSeparator();
- return preg_match("/[^\/]+$separator.*$/", $location);
+ // Return current page filter
+ public function getFilter() {
+ return $this->filterValue;
}
- // Check if there are pagination arguments in current HTTP request
- public function isLocationArgumentsPagination($location) {
- $separator = $this->getLocationArgumentsSeparator();
- return preg_match("/^(.*\/)?page$separator.*$/", $location);
+ // Return page collection modification date, Unix time or HTTP format
+ public function getModified($httpFormat = false) {
+ $modified = 0;
+ foreach ($this->getIterator() as $page) {
+ $modified = max($modified, $page->getModified());
+ }
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
}
-
- // Check if unmodified since last HTTP request
- public function isNotModified($lastModifiedFormatted) {
- return $this->getServer("HTTP_IF_MODIFIED_SINCE")==$lastModifiedFormatted;
+
+ // Check if there is a pagination
+ public function isPagination() {
+ return $this->paginationCount>1;
}
- //TODO: remove later, for backwards compatibility
- public function getLocationArgs() { return $this->getLocationArguments(); }
- public function getLocationArgsNew($key, $value) { return $this->getLocationArgumentsNew($key, $value); }
- public function getLocationArgsCleanUrl() { return $this->getLocationArgumentsCleanUrl(); }
- public function getLocationArgsSeparator() { return $this->getLocationArgumentsSeparator(); }
- public function getTextArgs($text, $optional = "-", $sizeMin = 9) { return $this->getTextArguments($text, $optional, $sizeMin); }
- public function normaliseArgs($text, $appendSlash = true, $filterStrict = true) { return $this->normaliseArguments($text, $appendSlash, $filterStrict); }
- public function isLocationArgs($location = "") { return $this->isLocationArguments($location); }
- public function isLocationArgsPagination($location) { return $this->isLocationArgumentsPagination($location); }
+ // Check if page collection is empty
+ public function isEmpty() {
+ return empty($this->getArrayCopy());
+ }
}
-class YellowExtensions {
- public $yellow; //access to API
- public $modified; //extension modification date
- public $extensions; //registered extensions
-
- public function __construct($yellow) {
- $this->yellow = $yellow;
- $this->modified = 0;
- $this->extensions = array();
+class YellowArray extends ArrayObject {
+ public function __construct($array = []) {
+ parent::__construct($array);
}
- // Load extensions
- public function load($path) {
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.php$/", true, false) as $entry) {
- if (defined("DEBUG") && DEBUG>=3) echo "YellowExtensions::load file:$entry<br/>\n";
- $this->modified = max($this->modified, filemtime($entry));
- require_once($entry);
- $name = $this->yellow->lookup->normaliseName(basename($entry), true, true);
- $this->register(lcfirst($name), "Yellow".ucfirst($name));
- }
- $callback = function ($a, $b) {
- return $a["priority"] - $b["priority"];
- };
- uasort($this->extensions, $callback);
- foreach ($this->extensions as $key=>$value) {
- if (method_exists($this->extensions[$key]["obj"], "onLoad")) $this->extensions[$key]["obj"]->onLoad($this->yellow);
- }
- $this->yellow->system->set("mediaLocation", "/media/"); //TODO: remove later, for backwards compatibility
- $this->yellow->system->set("downloadLocation", "/media/downloads/");
- $this->yellow->system->set("imageLocation", "/media/images/");
- $this->yellow->system->set("extensionLocation", "/media/extensions/");
- $this->yellow->system->set("resourceLocation", "/media/resources/");
- $this->yellow->system->set("mediaDir", "media/");
- $this->yellow->system->set("downloadDir", "media/downloads/");
- $this->yellow->system->set("imageDir", "media/images/");
- $this->yellow->system->set("systemDir", "system/");
- $this->yellow->system->set("extensionDir", "system/extensions/");
- $this->yellow->system->set("layoutDir", "system/layouts/");
- $this->yellow->system->set("resourceDir", "system/resources/");
- $this->yellow->system->set("settingDir", "system/settings/");
- $this->yellow->system->set("trashDir", "system/trash/");
- $this->yellow->system->set("contentDir", "content/");
- $this->yellow->system->set("contentPagination", "page");
- $this->yellow->system->set("coreStaticDir", "public/");
- $this->yellow->system->set("coreCacheDir", "cache/");
- $this->yellow->system->set("coreTrashDir", "system/trash/");
- $this->yellow->system->set("coreMediaDir", "media/");
- $this->yellow->system->set("coreDownloadDir", "media/downloads/");
- $this->yellow->system->set("coreImageDir", "media/images/");
- $this->yellow->system->set("coreSystemDir", "system/");
- $this->yellow->system->set("coreExtensionDir", "system/extensions/");
- $this->yellow->system->set("coreLayoutDir", "system/layouts/");
- $this->yellow->system->set("coreResourceDir", "system/resources/");
- $this->yellow->system->set("coreSettingDir", "system/settings/");
- $this->yellow->system->set("coreContentDir", "content/");
- $this->yellow->system->set("coreContentRootDir", "default/");
- $this->yellow->system->set("coreContentHomeDir", "home/");
- $this->yellow->system->set("coreContentSharedDir", "shared/");
+ // Set array element
+ public function set($key, $value) {
+ $this->offsetSet($key, $value);
}
- // Register extension
- public function register($name, $class) {
- if (!$this->isExisting($name) && class_exists($class)) {
- $this->extensions[$name] = array();
- $this->extensions[$name]["obj"] = new $class;
- $this->extensions[$name]["class"] = $class;
- $this->extensions[$name]["version"] = defined("$class::VERSION") ? $class::VERSION : 0;
- $this->extensions[$name]["type"] = defined("$class::TYPE") ? $class::TYPE : "feature";
- $this->extensions[$name]["priority"] = defined("$class::PRIORITY") ? $class::PRIORITY : count($this->extensions) + 10;
- }
+ // Return array element
+ public function get($key) {
+ return $this->offsetExists($key) ? $this->offsetGet($key) : "";
}
- // Return extension
- public function get($name) {
- return $this->extensions[$name]["obj"];
+ // Check if array element exists
+ public function isExisting($key) {
+ return $this->offsetExists($key);
}
- // Return extensions version
- public function getData($type = "") {
- $data = array();
- foreach ($this->extensions as $key=>$value) {
- if (empty($type) || $value["type"]==$type) {
- $data[$key] = $value["version"];
- }
- }
- uksort($data, "strnatcasecmp");
- return $data;
+ // Return array element
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key) {
+ if (is_string($key)) $key = lcfirst($key);
+ return parent::offsetGet($key);
}
- // Return extensions modification date, Unix time or HTTP format
- public function getModified($httpFormat = false) {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
+ // Set array element
+ #[\ReturnTypeWillChange]
+ public function offsetSet($key, $value) {
+ if (is_string($key)) $key = lcfirst($key);
+ parent::offsetSet($key, $value);
}
-
- // Return extensions
- public function getExtensions($type = "") {
- $extensions = array();
- foreach ($this->extensions as $key=>$value) {
- if (empty($type) || $value["type"]==$type) {
- array_push($extensions, $key);
- }
- }
- return $extensions;
+
+ // Remove array element
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($key) {
+ if (is_string($key)) $key = lcfirst($key);
+ parent::offsetUnset($key);
}
- // Check if extension exists
- public function isExisting($name) {
- return isset($this->extensions[$name]);
+ // Check if array element exists
+ #[\ReturnTypeWillChange]
+ public function offsetExists($key) {
+ if (is_string($key)) $key = lcfirst($key);
+ return parent::offsetExists($key);
}
-}
-// Check if string is empty
-function strempty($string) {
- return is_null($string) || $string==="";
+ // Check if array is empty
+ public function isEmpty() {
+ return empty($this->getArrayCopy());
+ }
}
// Make string lowercase, UTF-8 compatible
@@ -3264,47 +3844,53 @@ function strtoupperu() {
return call_user_func_array("mb_strtoupper", func_get_args());
}
-// Replace string, UTF-8 compatible
-function strreplaceu() {
- return call_user_func_array("str_replace", func_get_args());
-}
-
-// Return string length in characters, UTF-8 compatible
+// Return string length, UTF-8 characters
function strlenu() {
return call_user_func_array("mb_strlen", func_get_args());
}
-// Return string length in bytes, ASCII compatible
+// Return string length, bytes
function strlenb() {
return call_user_func_array("strlen", func_get_args());
}
-// Return string positon in characters, UTF-8 compatible
+// Return string position of first match, UTF-8 characters
function strposu() {
return call_user_func_array("mb_strpos", func_get_args());
}
-// Return string position in bytes, ASCII compatible
+// Return string position of first match, bytes
function strposb() {
return call_user_func_array("strpos", func_get_args());
}
-// Return reverse string position in characters, UTF-8 compatible
+// Return string position of last match, UTF-8 characters
function strrposu() {
return call_user_func_array("mb_strrpos", func_get_args());
}
-// Return reverse string position in bytes, ASCII compatible
+// Return string position of last match, bytes
function strrposb() {
return call_user_func_array("strrpos", func_get_args());
}
-// Return part of a string, UTF-8 compatible
+// Return part of a string, UTF-8 characters
function substru() {
return call_user_func_array("mb_substr", func_get_args());
}
-// Return part of a string, ASCII compatible
+// Return part of a string, bytes
function substrb() {
return call_user_func_array("substr", func_get_args());
}
+
+// Check if string is empty
+function is_string_empty($string) {
+ return is_null($string) || $string==="";
+}
+function strempty($string) { return is_null($string) || $string===""; } //TODO: remove later, for backwards compatibility
+
+// Check if array is empty
+function is_array_empty($array) {
+ return is_null($array) || (is_array($array) ? empty($array) : empty($array->getArrayCopy()));
+}
diff --git a/system/extensions/edit-stack.svg b/system/extensions/edit-stack.svg
@@ -0,0 +1,61 @@
+<svg xmlns="http://www.w3.org/2000/svg"><defs><style>.stack{display:none;}.stack:target{display:inline;}</style></defs>
+
+<g class="stack" id="preview"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8 5.509c1.362 0 2.491 1.129 2.491 2.491s-1.129 2.491-2.491 2.491c-1.362 0-2.491-1.129-2.491-2.491s1.129-2.491 2.491-2.491zM8 12.164c2.296 0 4.164-1.868 4.164-4.164s-1.868-4.164-4.164-4.164c-2.296 0-4.164 1.868-4.164 4.164s1.868 4.164 4.164 4.164zM8 1.774c4.164 0 6.562 2.568 8.002 6.226-1.44 3.658-3.838 6.226-8.002 6.226s-6.562-2.568-8.002-6.226c1.44-3.658 3.838-6.226 8.002-6.226z"></path></svg></g>
+
+<g class="stack" id="format"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M5 1h8v2h-2v12.017h-2v-12.017h-2v12.017h-2v-6.017c-2.209 0-4-1.791-4-4s1.791-4 4-4z"></path></svg></g>
+
+<g class="stack" id="heading"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M0.006 0.998h11.996v2.035h-4.982v11.998h-2.033v-11.998h-4.982z"></path><path fill="currentColor" d="M13.59 14.977v-6.602h2.341v-1.383h-6.353v1.383h2.341v6.602q0 0 0.249 0 0.255 0 0.587 0t0.581 0q0.255 0 0.255 0z"></path></svg></g>
+
+<g class="stack" id="h1"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M0.006 0.998h11.996v2.035h-4.982v11.998h-2.033v-11.998h-4.982z"></path><path fill="currentColor" d="M13.557 14.977h1.66v-7.985h-1.666q0 0-0.31 0.216t-0.719 0.504q-0.409 0.282-0.725 0.498-0.31 0.216-0.31 0.216v1.511q0 0 0.304-0.21 0.31-0.216 0.714-0.493 0.404-0.282 0.708-0.493 0.31-0.216 0.31-0.216h0.033q0 0 0 0.47 0 0.465 0 1.201 0 0.73 0 1.555t0 1.561 0 1.201 0 0.465z"></path></svg></g>
+
+<g class="stack" id="h2"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M0.006 0.998h11.996v2.035h-4.982v11.998h-2.033v-11.998h-4.982z"></path><path fill="currentColor" d="M10.076 9.398v0.022h1.549v-0.028q0-0.553 0.382-0.913 0.382-0.365 0.963-0.365 0.553 0 0.908 0.315 0.354 0.31 0.354 0.764 0 0.409-0.227 0.786-0.221 0.371-0.825 0.974l-3.010 2.9v1.123h5.805v-1.328h-3.603v-0.033l1.948-1.876q0.664-0.653 1.101-1.262 0.443-0.609 0.443-1.35 0-1.007-0.791-1.66-0.786-0.653-2.048-0.653-1.306 0-2.131 0.725-0.819 0.725-0.819 1.859z"></path></svg></g>
+
+<g class="stack" id="h3"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M0.006 0.998h11.996v2.035h-4.982v11.998h-2.033v-11.998h-4.982z"></path><path fill="currentColor" d="M11.825 11.49h0.985q0.675 0 1.063 0.315 0.387 0.31 0.387 0.858 0 0.509-0.398 0.83-0.398 0.315-1.024 0.315-0.659 0-1.051-0.304-0.393-0.31-0.432-0.813h-1.599q0.055 1.101 0.88 1.782 0.83 0.681 2.18 0.681 1.383 0 2.263-0.653 0.88-0.659 0.88-1.732 0-0.83-0.537-1.339-0.531-0.509-1.422-0.587v-0.033q0.736-0.133 1.19-0.631 0.459-0.498 0.459-1.234 0-0.957-0.775-1.544t-2.020-0.587q-1.295 0-2.097 0.67-0.802 0.664-0.847 1.771h1.538q0.033-0.52 0.393-0.836 0.365-0.315 0.941-0.315 0.62 0 0.952 0.293t0.332 0.775q0 0.493-0.349 0.802t-0.924 0.31h-0.968q0 0 0 0.304 0 0.299 0 0.603 0 0.299 0 0.299z"></path></svg></g>
+
+<g class="stack" id="bold"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M11.061 7.573c0.586-0.696 0.939-1.594 0.939-2.573 0-2.206-1.794-4-4-4h-5v14h6c2.206 0 4-1.794 4-4 0-1.452-0.778-2.726-1.939-3.427zM6 3h1.586c0.874 0 1.586 0.897 1.586 2s-0.711 2-1.586 2h-1.586v-4zM8.484 13h-2.484v-4h2.484c0.913 0 1.656 0.897 1.656 2s-0.743 2-1.656 2z"></path></svg></g>
+
+<g class="stack" id="italic"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8.413 0.996h1.905l-2.73 14.008h-1.905z"></path></svg></g>
+
+<g class="stack" id="strikethrough"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8.316 0.719c-2.093-0.024-4 0.687-4.87 2.207-0.752 1.348-0.556 3.247 0.681 4.247 0.42 0.359 0.889 0.62 1.384 0.826h-5.512v1h8.986c0.882 0.266 1.801 0.668 2.162 1.562 0.379 0.955-0.102 2.077-0.989 2.558-1.145 0.686-2.607 0.69-3.849 0.282-0.655-0.227-1.207-0.649-1.62-1.189l-1.293 1.136c0.397 0.536 0.923 0.979 1.528 1.272 1.559 0.775 3.413 0.778 5.071 0.35 1.46-0.389 2.79-1.549 3.021-3.095 0.187-0.976 0.029-2.077-0.57-2.876h3.553v-1h-4.813c-1.47-0.79-3.211-0.948-4.767-1.539-0.768-0.265-1.578-0.851-1.587-1.744-0.101-1.135 0.878-2.042 1.917-2.277 0.745-0.221 2.107-0.196 2.776-0.071s1.36 0.419 1.823 0.937l1.209-1.139c-0.341-0.357-0.745-0.654-1.189-0.869-0.812-0.417-1.983-0.58-2.889-0.579-0.054-0.001-0.108-0.001-0.163-0.001z"></path></svg></g>
+
+<g class="stack" id="quote"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M3.508 6.5c1.933 0 3.5 1.567 3.5 3.5s-1.567 3.5-3.5 3.5-3.5-1.567-3.5-3.5l-0.016-0.5c0-3.866 3.134-7 7-7v2c-1.336 0-2.591 0.52-3.536 1.464-0.182 0.182-0.348 0.375-0.497 0.578 0.179-0.028 0.362-0.043 0.548-0.043zM12.508 6.5c1.933 0 3.5 1.567 3.5 3.5s-1.567 3.5-3.5 3.5-3.5-1.567-3.5-3.5l-0.016-0.5c0-3.866 3.134-7 7-7v2c-1.336 0-2.591 0.52-3.536 1.464-0.182 0.182-0.348 0.375-0.497 0.578 0.179-0.028 0.362-0.043 0.549-0.043z"></path></svg></g>
+
+<g class="stack" id="code"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M9.907 11.284l1.408 1.408 4.692-4.692-4.692-4.692-1.408 1.408 3.284 3.284z"></path><path fill="currentColor" d="M6.095 4.716l-1.408-1.408-4.692 4.692 4.692 4.692 1.408-1.408-3.284-3.284z"></path></svg></g>
+
+<g class="stack" id="link"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M6.879 9.934c-0.208 0-0.416-0.079-0.575-0.238-1.486-1.486-1.486-3.905 0-5.392l3-3c0.72-0.72 1.678-1.117 2.696-1.117s1.976 0.397 2.696 1.117c1.486 1.487 1.486 3.905 0 5.392l-1.371 1.371c-0.317 0.317-0.832 0.317-1.149 0s-0.317-0.832 0-1.149l1.371-1.371c0.853-0.853 0.853-2.241 0-3.094-0.413-0.413-0.963-0.641-1.547-0.641s-1.134 0.228-1.547 0.641l-3 3c-0.853 0.853-0.853 2.241 0 3.094 0.317 0.317 0.317 0.832 0 1.149-0.159 0.159-0.367 0.238-0.575 0.238z"></path><path fill="currentColor" d="M4 15.813c-1.018 0-1.976-0.397-2.696-1.117-1.486-1.486-1.486-3.905 0-5.392l1.371-1.371c0.317-0.317 0.832-0.317 1.149 0s0.317 0.832 0 1.149l-1.371 1.371c-0.853 0.853-0.853 2.241 0 3.094 0.413 0.413 0.962 0.641 1.547 0.641s1.134-0.228 1.547-0.641l3-3c0.853-0.853 0.853-2.241 0-3.094-0.317-0.317-0.317-0.832 0-1.149s0.832-0.317 1.149 0c1.486 1.486 1.486 3.905 0 5.392l-3 3c-0.72 0.72-1.678 1.117-2.696 1.117z"></path></svg></g>
+
+<g class="stack" id="file"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M1 1c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1v-12c0-0.55-0.45-1-1-1zM2.007 2.487l11.975 0.022c0.577 0.017 0.527 0.485 0.527 0.485l-0.008 3.462-4 3.544h-1l-4-5-3.978 7.012-0.019-9.034c0.001-0.001-0.039-0.508 0.504-0.491z"></path><path fill="currentColor" d="M12.375 5.125c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path></svg></g>
+
+<g class="stack" id="list"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M6 1h10v2h-10zM6 7h10v2h-10zM6 13h10v2h-10zM-0.017 2c0-1.105-0.105-1.017 1-1.017s1-0.105 1 1c0 1.105 0.138 1.017-0.966 1.017s-1.034 0.105-1.034-1zM-0.005 8c0-1.105-0.099-1.005 1.005-1.005s1.017-0.116 1.017 0.988c0 1.105 0.088 1.022-1.017 1.022s-1.005 0.099-1.005-1.005zM0.008 14c0-1.105-0.113-1 0.992-1s1-0.113 1 0.992c0 1.105 0.122 1-0.983 1s-1.008 0.113-1.008-0.992z"></path></svg></g>
+
+<g class="stack" id="ol"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M7 13h9v2h-9zM7 7h9v2h-9zM7 1h9v2h-9z"></path><path fill="currentColor" d="M1.456 5.52h1.147v-5.519h-1.151q0 0-0.214 0.149t-0.497 0.348q-0.283 0.195-0.501 0.344-0.214 0.149-0.214 0.149v1.044q0 0 0.21-0.145 0.214-0.149 0.493-0.34 0.279-0.195 0.49-0.34 0.214-0.149 0.214-0.149h0.023q0 0 0 0.325 0 0.321 0 0.83 0 0.505 0 1.075t0 1.079 0 0.83 0 0.321z"></path><path fill="currentColor" d="M-0.016 11.15v0.015h1.071v-0.019q0-0.382 0.264-0.631 0.264-0.252 0.666-0.252 0.382 0 0.627 0.218 0.245 0.214 0.245 0.528 0 0.283-0.157 0.543-0.153 0.256-0.57 0.673l-2.081 2.004v0.776h4.012v-0.918h-2.49v-0.023l1.346-1.297q0.459-0.451 0.761-0.872 0.306-0.421 0.306-0.933 0-0.696-0.547-1.147-0.543-0.451-1.415-0.451-0.903 0-1.473 0.501-0.566 0.501-0.566 1.285z"></path></svg></g>
+
+<g class="stack" id="tl"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8.945 1.034l7.055-0.034v2l-7.033-0.012zM7 7h9v2h-9zM7 13h9v2h-9zM2.017 3c-1.345 8.667-0.672 4.333 0 0z"></path><path fill="currentColor" d="M6.5 0l-3.517 3.517-1.534-1.534-1.449 1.517 3.017 3 4.992-4.958z"></path></svg></g>
+
+<g class="stack" id="hr"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M1 7h14v2h-14z"></path></svg></g>
+
+<g class="stack" id="table"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M0 1v14h16v-14zM7.5 3v5h-6.5v-5zM8.5 8v-5h6.5v5zM1 9h6.5v5h-6.5zM8.5 14v-5h6.5v5z"></path></svg></g>
+
+<g class="stack" id="emoji"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M10.006 6c0-0.498 0.487-1.005 0.988-1.005 0.493 0 1.005 0.516 1.005 1.005s-0.521 0.998-1.005 0.988c-0.501-0.010-0.988-0.507-0.988-0.988zM8 16c4.418 0 8-3.582 8-8s-3.582-8-8-8c-4.418 0-8 3.582-8 8s3.582 8 8 8zM8 1.5c3.59 0 6.5 2.91 6.5 6.5s-2.91 6.5-6.5 6.5c-3.59 0-6.5-2.91-6.5-6.5s2.91-6.5 6.5-6.5zM12.5 8.966c-0.445 2.035-1.893 3.534-4.5 3.534s-3.987-1.433-4.5-3.536c4.022-0.028 4.969-0.013 9 0.002zM4 6c0-0.583 0.499-1 1-1 0.493 0 1 0.511 1 1s-0.516 1.009-1 1c-0.501-0.010-1-0.417-1-1z"></path></svg></g>
+
+<g class="stack" id="icon"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899z"></path></svg></g>
+
+<g class="stack" id="status"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M3.985 0.985l-1.984 0.016v13.999l1.984-0.016v-5.669c0.597-0.403 1.323-0.677 2.64-0.677 1.046 0 1.972 0.238 2.625 0.445v0c0.622 0.271 1.495 0.677 2.625 0.677s1.613-0.336 2.125-0.739v-6.999c-0.512 0.404-0.995 0.739-2.125 0.739s-2.047-0.439-2.625-0.677c-0.641-0.195-1.579-0.445-2.625-0.445-1.318 0-2.043 0.273-2.64 0.677z"></path></svg></g>
+
+<g class="stack" id="undo"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M11.904 16c1.777-3.219 2.076-8.13-4.904-7.966v3.966l-6-6 6-6v3.881c8.359-0.218 9.29 7.378 4.904 12.119z"></path></svg></g>
+
+<g class="stack" id="redo"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M9 3.881v-3.881l6 6-6 6v-3.966c-6.98-0.164-6.681 4.747-4.904 7.966-4.386-4.741-3.455-12.337 4.904-12.119z"></path></svg></g>
+
+<g class="stack" id="spinner"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8.044 0.771c-0.249 0-0.451 0.202-0.451 0.451v2.705c0 0.249 0.202 0.451 0.451 0.451s0.451-0.202 0.451-0.451v-2.705c0-0.249-0.202-0.451-0.451-0.451zM4.651 1.677c-0.073 0.002-0.146 0.021-0.213 0.060-0.216 0.125-0.289 0.4-0.165 0.616l1.352 2.343c0.125 0.216 0.4 0.29 0.616 0.165 0.145-0.083 0.226-0.235 0.226-0.391 0-0.077-0.019-0.154-0.060-0.225l-1.352-2.343c-0.086-0.148-0.243-0.23-0.403-0.225zM11.437 1.677c-0.16-0.004-0.317 0.077-0.403 0.225l-1.352 2.343c-0.124 0.216-0.051 0.491 0.165 0.616s0.491 0.051 0.616-0.165l1.352-2.342c0.041-0.071 0.060-0.149 0.060-0.225 0-0.156-0.081-0.307-0.225-0.391-0.067-0.039-0.141-0.058-0.213-0.060zM13.887 4.096c-0.082 0.002-0.164 0.024-0.24 0.068l-2.342 1.352c-0.243 0.14-0.326 0.45-0.186 0.693s0.45 0.326 0.693 0.186l2.343-1.352c0.163-0.094 0.254-0.265 0.254-0.44h-0c0-0.086-0.022-0.173-0.068-0.253-0.096-0.167-0.273-0.258-0.453-0.254zM2.2 4.152c-0.16-0.004-0.317 0.077-0.403 0.225-0.125 0.216-0.051 0.491 0.165 0.616l2.343 1.353c0.216 0.125 0.491 0.051 0.616-0.165 0.041-0.071 0.060-0.149 0.060-0.225 0-0.156-0.081-0.307-0.225-0.391l-2.343-1.352c-0.067-0.039-0.141-0.058-0.213-0.060zM1.282 7.082c-0.498 0-0.902 0.404-0.902 0.902s0.404 0.902 0.902 0.902h2.705c0.498 0 0.902-0.404 0.902-0.902s-0.404-0.902-0.902-0.902zM12.101 7.421c-0.311 0-0.564 0.252-0.564 0.564s0.252 0.564 0.564 0.564h2.705c0.311 0 0.564-0.252 0.564-0.564s-0.252-0.564-0.564-0.564zM4.548 9.168c-0.15-0.003-0.302 0.033-0.441 0.113l-2.343 1.352c-0.404 0.233-0.543 0.75-0.309 1.155s0.75 0.543 1.155 0.309l2.343-1.352c0.271-0.157 0.423-0.441 0.423-0.733 0-0.143-0.037-0.289-0.113-0.422-0.153-0.265-0.428-0.416-0.714-0.423zM11.574 9.393c-0.22-0.006-0.436 0.106-0.553 0.31-0.171 0.296-0.070 0.676 0.227 0.847l2.343 1.352c0.296 0.171 0.676 0.070 0.847-0.227 0.056-0.098 0.083-0.204 0.083-0.309l-0 0c0-0.214-0.111-0.423-0.31-0.537l-2.343-1.352c-0.093-0.053-0.193-0.080-0.293-0.083zM5.998 10.709c-0.266 0.006-0.523 0.147-0.666 0.394l-1.352 2.343c-0.218 0.377-0.089 0.86 0.289 1.078s0.86 0.089 1.078-0.289l1.352-2.343c0.072-0.124 0.106-0.26 0.106-0.394l0-0c0-0.273-0.142-0.538-0.395-0.684-0.13-0.075-0.272-0.109-0.411-0.106zM10.087 10.822c-0.12-0.003-0.241 0.026-0.353 0.091-0.323 0.187-0.434 0.6-0.248 0.924l1.352 2.343c0.187 0.323 0.6 0.434 0.924 0.248 0.217-0.125 0.338-0.353 0.338-0.586l-0 0c0-0.115-0.029-0.231-0.091-0.338l-1.352-2.343c-0.123-0.212-0.343-0.333-0.571-0.338zM8.044 11.309c-0.405 0-0.733 0.328-0.733 0.733v2.705c0 0.405 0.328 0.733 0.733 0.733s0.733-0.328 0.733-0.733v-2.705c0-0.405-0.328-0.733-0.733-0.733z"></path></svg></g>
+
+<g class="stack" id="select"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M12.78 6.22c0.136 0.136 0.219 0.323 0.219 0.53s-0.084 0.394-0.219 0.53l-4.25 4.25c-0.136 0.136-0.323 0.219-0.53 0.219s-0.394-0.084-0.53-0.219l-4.25-4.25c-0.125-0.134-0.201-0.313-0.201-0.511 0-0.414 0.336-0.75 0.75-0.75 0.198 0 0.378 0.077 0.512 0.202l-0-0 3.72 3.72 3.72-3.72c0.136-0.136 0.323-0.219 0.53-0.219s0.394 0.084 0.53 0.219l-0-0z"></path></svg></g>
+
+<g class="stack" id="search"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M15.504 13.616l-3.79-3.223c-0.392-0.353-0.811-0.514-1.149-0.499 0.895-1.048 1.435-2.407 1.435-3.893 0-3.314-2.686-6-6-6s-6 2.686-6 6 2.686 6 6 6c1.486 0 2.845-0.54 3.893-1.435-0.016 0.338 0.146 0.757 0.499 1.149l3.223 3.79c0.552 0.613 1.453 0.665 2.003 0.115s0.498-1.452-0.115-2.003zM6 10c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"></path></svg></g>
+
+<g class="stack" id="close"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M6.309 7.945l-4.364-5.231 1.854-1.691 4.161 4.999 4.288-5.026 1.71 1.467-4.486 5.32 4.584 5.63-1.603 1.567-4.483-5.287-4.176 5.312-1.744-1.591z"></path></svg></g>
+
+<g class="stack" id="help"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M6.852 10.89v-1.053q0-0.421 0.038-0.708 0.057-0.306 0.172-0.536 0.134-0.249 0.325-0.459 0.211-0.23 0.498-0.536l1.474-1.493q0.478-0.478 0.478-1.244 0-0.746-0.498-1.206-0.478-0.478-1.225-0.478-0.804 0-1.32 0.555-0.517 0.536-0.593 1.32l-2.449-0.191q0.115-0.919 0.478-1.627 0.364-0.727 0.938-1.225 0.593-0.498 1.359-0.746 0.765-0.268 1.684-0.268 0.861 0 1.588 0.249 0.746 0.249 1.282 0.727 0.555 0.459 0.861 1.167 0.306 0.689 0.306 1.588 0 0.631-0.172 1.11t-0.459 0.88-0.67 0.765q-0.364 0.364-0.785 0.765-0.268 0.249-0.459 0.44t-0.325 0.402q-0.115 0.191-0.172 0.459-0.057 0.249-0.057 0.612v0.727zM6.469 13.55q0-0.612 0.44-1.053 0.459-0.44 1.091-0.44 0.612 0 1.072 0.421t0.459 1.033-0.459 1.053q-0.44 0.44-1.072 0.44-0.306 0-0.593-0.115-0.268-0.115-0.478-0.306t-0.344-0.459q-0.115-0.268-0.115-0.574z"></path></svg></g>
+
+<g class="stack" id="logo"><svg viewBox="0 0 16 16"><path fill="currentColor" d="M8-0.005l-7.623 4.003c2.818 1.48 2.818 1.48 0 0-0.029 1.334-0.013 2.668 0 4.003-0.029 1.334-0.013 2.668 0 4.003l3.811 2.001c0-0.001 0-0.002 0-0.003 0 0.001-0 0.002 0 0.003l3.811 2.001 7.623-4.003c0.013-1.334 0.029-2.668 0-4.003 0.013-1.334 0.029-2.668 0-4.003-8.681 4.558 0-0 0-0l-3.811-2.001z"></path></svg></g>
+
+</svg>
+\ No newline at end of file
diff --git a/system/extensions/edit.css b/system/extensions/edit.css
@@ -1,6 +1,4 @@
-/* Edit extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/edit */
-/* Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se */
-/* This file may be used and distributed under the terms of the public license. */
+/* Edit extension, https://github.com/annaesvensson/yellow-edit */
.yellow-bar {
position: relative;
@@ -250,6 +248,12 @@
border-color: #666;
color: #fff;
}
+.yellow-toolbar .yellow-toolbar-disabled,
+.yellow-toolbar .yellow-toolbar-disabled:hover {
+ background-color: inherit;
+ border-color: #c1c1c1 #c1c1c1 #aaa;
+ color: #aaa;
+}
.yellow-toolbar-tooltip {
position: relative;
}
@@ -342,7 +346,7 @@
color: #333333;
background-image: linear-gradient(to bottom, #f8f8f8, #e1e1e1);
border: 1px solid #bbb;
- border-color: #c1c1c1 #c1c1c1 #aaaaaa;
+ border-color: #c1c1c1 #c1c1c1 #aaa;
border-radius: 4px;
outline-offset: -2px;
font-size: 0.9em;
@@ -419,44 +423,39 @@
font-size: 0.9em;
line-height: 1.8;
}
-#yellow-popup-emojiawesome {
+#yellow-popup-emoji {
padding: 10px;
width: 14em;
}
-#yellow-popup-emojiawesome a {
+#yellow-popup-emoji a {
padding: 0.2em;
}
-#yellow-popup-emojiawesome .yellow-dropdown li {
+#yellow-popup-emoji .yellow-dropdown li {
display: inline-block;
}
-#yellow-popup-fontawesome {
+#yellow-popup-icon {
padding: 10px;
width: 13em;
}
-#yellow-popup-fontawesome a {
+#yellow-popup-icon a {
padding: 0.18em 0.3em;
min-width: 1em;
text-align: center;
}
-#yellow-popup-fontawesome .yellow-dropdown li {
+#yellow-popup-icon .yellow-dropdown li {
display: inline-block;
}
/* Icons */
-@font-face {
- font-family: "Edit";
- font-weight: normal;
- font-style: normal;
- src: url("edit.woff") format("woff");
-}
.yellow-icon {
display: inline-block;
- font-family: Edit;
- font-style: normal;
- font-weight: normal;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ width: 1em;
+ height: 1em;
+ background-color: currentcolor;
+ background-size: 1em 1em;
+ background-repeat: no-repeat;
+ background-position: center center;
}
.yellow-spin {
-webkit-animation: yellow-spin 1s infinite steps(16);
@@ -482,100 +481,122 @@
transform: rotate(359deg);
}
}
-
-.yellow-icon-preview:before {
- content: "\f100";
-}
-.yellow-icon-format:before {
- content: "\f101";
-}
-.yellow-icon-paragraph:before {
- content: "\f101";
-}
-.yellow-icon-heading:before {
- content: "\f102";
-}
-.yellow-icon-h1:before {
- content: "\f103";
-}
-.yellow-icon-h2:before {
- content: "\f104";
-}
-.yellow-icon-h3:before {
- content: "\f105";
-}
-.yellow-icon-bold:before {
- content: "\f106";
+.yellow-icon-preview {
+ -webkit-mask: url("edit-stack.svg#preview");
+ mask: url("edit-stack.svg#preview");
}
-.yellow-icon-italic:before {
- content: "\f0f7";
+.yellow-icon-format,
+.yellow-icon-paragraph {
+ -webkit-mask: url("edit-stack.svg#format");
+ mask: url("edit-stack.svg#format");
}
-.yellow-icon-strikethrough:before {
- content: "\f108";
+.yellow-icon-heading {
+ -webkit-mask: url("edit-stack.svg#heading");
+ mask: url("edit-stack.svg#heading");
}
-.yellow-icon-quote:before {
- content: "\f109";
+.yellow-icon-h1 {
+ -webkit-mask: url("edit-stack.svg#h1");
+ mask: url("edit-stack.svg#h1");
}
-.yellow-icon-code:before {
- content: "\f10a";
+.yellow-icon-h2 {
+ -webkit-mask: url("edit-stack.svg#h2");
+ mask: url("edit-stack.svg#h2");
}
-.yellow-icon-pre:before {
- content: "\f10a";
+.yellow-icon-h3 {
+ -webkit-mask: url("edit-stack.svg#h3");
+ mask: url("edit-stack.svg#h3");
}
-.yellow-icon-link:before {
- content: "\f10b";
+.yellow-icon-bold {
+ -webkit-mask: url("edit-stack.svg#bold");
+ mask: url("edit-stack.svg#bold");
}
-.yellow-icon-file:before {
- content: "\f10c";
+.yellow-icon-italic {
+ -webkit-mask: url("edit-stack.svg#italic");
+ mask: url("edit-stack.svg#italic");
}
-.yellow-icon-list:before {
- content: "\f10d";
+.yellow-icon-strikethrough {
+ -webkit-mask: url("edit-stack.svg#strikethrough");
+ mask: url("edit-stack.svg#strikethrough");
}
-.yellow-icon-ul:before {
- content: "\f10d";
+.yellow-icon-quote {
+ -webkit-mask: url("edit-stack.svg#quote");
+ mask: url("edit-stack.svg#quote");
}
-.yellow-icon-ol:before {
- content: "\f10e";
+.yellow-icon-code,
+.yellow-icon-pre {
+ -webkit-mask: url("edit-stack.svg#code");
+ mask: url("edit-stack.svg#code");
}
-.yellow-icon-tl:before {
- content: "\f10f";
+.yellow-icon-link {
+ -webkit-mask: url("edit-stack.svg#link");
+ mask: url("edit-stack.svg#link");
}
-.yellow-icon-hr:before {
- content: "\f110";
+.yellow-icon-file {
+ -webkit-mask: url("edit-stack.svg#file");
+ mask: url("edit-stack.svg#file");
}
-.yellow-icon-table:before {
- content: "\f111";
+.yellow-icon-list,
+.yellow-icon-ul {
+ -webkit-mask: url("edit-stack.svg#list");
+ mask: url("edit-stack.svg#list");
}
-.yellow-icon-emojiawesome:before {
- content: "\f112";
+.yellow-icon-ol {
+ -webkit-mask: url("edit-stack.svg#ol");
+ mask: url("edit-stack.svg#ol");
}
-.yellow-icon-fontawesome:before {
- content: "\f113";
+.yellow-icon-tl {
+ -webkit-mask: url("edit-stack.svg#tl");
+ mask: url("edit-stack.svg#tl");
}
-.yellow-icon-status:before {
- content: "\f114";
+.yellow-icon-hr {
+ -webkit-mask: url("edit-stack.svg#hr");
+ mask: url("edit-stack.svg#hr");
}
-.yellow-icon-undo:before {
- content: "\f115";
+.yellow-icon-table {
+ -webkit-mask: url("edit-stack.svg#table");
+ mask: url("edit-stack.svg#table");
}
-.yellow-icon-redo:before {
- content: "\f116";
+.yellow-icon-emoji {
+ -webkit-mask: url("edit-stack.svg#emoji");
+ mask: url("edit-stack.svg#emoji");
}
-.yellow-icon-spinner:before {
- content: "\f200";
+.yellow-icon-icon {
+ -webkit-mask: url("edit-stack.svg#icon");
+ mask: url("edit-stack.svg#icon");
}
-.yellow-icon-search:before {
- content: "\f201";
+.yellow-icon-status {
+ -webkit-mask: url("edit-stack.svg#status");
+ mask: url("edit-stack.svg#status");
}
-.yellow-icon-close:before {
- content: "\f202";
+.yellow-icon-undo {
+ -webkit-mask: url("edit-stack.svg#undo");
+ mask: url("edit-stack.svg#undo");
}
-.yellow-icon-help:before {
- content: "\f203";
+.yellow-icon-redo {
+ -webkit-mask: url("edit-stack.svg#redo");
+ mask: url("edit-stack.svg#redo");
}
-.yellow-icon-markdown:before {
- content: "\f203";
+.yellow-icon-spinner {
+ -webkit-mask: url("edit-stack.svg#spinner");
+ mask: url("edit-stack.svg#spinner");
}
-.yellow-icon-logo:before {
- content: "\f8ff";
+.yellow-icon-select {
+ -webkit-mask: url("edit-stack.svg#select");
+ mask: url("edit-stack.svg#select");
+}
+.yellow-icon-search {
+ -webkit-mask: url("edit-stack.svg#search");
+ mask: url("edit-stack.svg#search");
+}
+.yellow-icon-close {
+ -webkit-mask: url("edit-stack.svg#close");
+ mask: url("edit-stack.svg#close");
+}
+.yellow-icon-help {
+ -webkit-mask: url("edit-stack.svg#help");
+ mask: url("edit-stack.svg#help");
+}
+.yellow-icon-logo {
+ -webkit-mask: url("edit-stack.svg#logo");
+ mask: url("edit-stack.svg#logo");
}
diff --git a/system/extensions/edit.js b/system/extensions/edit.js
@@ -1,6 +1,4 @@
-// Edit extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/edit
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Edit extension, https://github.com/annaesvensson/yellow-edit
var yellow = {
onLoad: function(e) { yellow.edit.load(e); },
@@ -16,11 +14,11 @@ var yellow = {
};
yellow.edit = {
- paneId: 0, //visible pane ID
- paneAction: 0, //current pane action
- paneStatus: 0, //current pane status
- popupId: 0, //visible popup ID
- intervalId: 0, //timer interval ID
+ paneId: 0, // visible pane ID
+ paneAction: 0, // current pane action
+ paneStatus: 0, // current pane status
+ popupId: 0, // visible popup ID
+ intervalId: 0, // timer interval ID
// Handle initialisation
load: function(e) {
@@ -76,7 +74,7 @@ yellow.edit = {
// Handle page cache
pageShow: function(e) {
- if (e.persisted && yellow.system.userEmail && !this.getCookie("csrftoken")) {
+ if (e.persisted && yellow.user.email && !this.getCookie("csrftoken")) {
window.location.reload();
}
},
@@ -94,7 +92,7 @@ yellow.edit = {
}
var elementDiv = document.createElement("div");
elementDiv.setAttribute("id", barId+"-content");
- if (yellow.system.userName) {
+ if (yellow.user.name) {
elementDiv.innerHTML =
"<div class=\"yellow-bar-left\">"+
this.getRawDataPaneAction("edit")+
@@ -102,7 +100,7 @@ yellow.edit = {
"<div class=\"yellow-bar-right\">"+
this.getRawDataPaneAction("create")+
this.getRawDataPaneAction("delete")+
- this.getRawDataPaneAction("menu", yellow.system.userName, true)+
+ this.getRawDataPaneAction("menu", yellow.user.name, true)+
"</div>"+
"<div class=\"yellow-bar-banner\"></div>";
} else {
@@ -132,7 +130,7 @@ yellow.edit = {
// Create pane
createPane: function(paneId, paneAction, paneStatus) {
- if (yellow.system.debug) console.log("yellow.edit.createPane id:"+paneId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.createPane id:"+paneId);
var elementPane = document.createElement("div");
elementPane.className = "yellow-pane";
elementPane.setAttribute("id", paneId);
@@ -156,7 +154,7 @@ yellow.edit = {
case "yellow-pane-login":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1>"+this.getText("LoginTitle")+"</h1></div>"+
"<div class=\"yellow-fields\">"+
"<input type=\"hidden\" name=\"action\" value=\"login\" />"+
@@ -170,7 +168,7 @@ yellow.edit = {
case "yellow-pane-signup":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1>"+this.getText("SignupTitle")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-signup-status\" class=\""+paneStatus+"\">"+this.getText("SignupStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-fields\">"+
@@ -186,7 +184,7 @@ yellow.edit = {
case "yellow-pane-forgot":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1>"+this.getText("ForgotTitle")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-forgot-status\" class=\""+paneStatus+"\">"+this.getText("ForgotStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-fields\">"+
@@ -199,7 +197,7 @@ yellow.edit = {
case "yellow-pane-recover":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1>"+this.getText("RecoverTitle")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-recover-status\" class=\""+paneStatus+"\">"+this.getText("RecoverStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-fields\">"+
@@ -211,7 +209,7 @@ yellow.edit = {
case "yellow-pane-quit":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1>"+this.getText("QuitTitle")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-quit-status\" class=\""+paneStatus+"\">"+this.getText("QuitStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-fields\">"+
@@ -225,7 +223,7 @@ yellow.edit = {
case "yellow-pane-account":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1 id=\"yellow-pane-account-title\">"+this.getText("AccountTitle")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-account-status\" class=\""+paneStatus+"\">"+this.getText("AccountStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-settings\">"+
@@ -245,22 +243,22 @@ yellow.edit = {
"</div>"+
"</form>";
break;
- case "yellow-pane-system":
+ case "yellow-pane-configure":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
- "<div class=\"yellow-title\"><h1 id=\"yellow-pane-system-title\">"+this.getText("SystemTitle")+"</h1></div>"+
- "<div class=\"yellow-status\"><p id=\"yellow-pane-system-status\" class=\""+paneStatus+"\">"+this.getText("SystemStatus", "", paneStatus)+"</p></div>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
+ "<div class=\"yellow-title\"><h1 id=\"yellow-pane-configure-title\">"+this.getText("ConfigureTitle")+"</h1></div>"+
+ "<div class=\"yellow-status\"><p id=\"yellow-pane-configure-status\" class=\""+paneStatus+"\">"+this.getText("ConfigureStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-settings\">"+
- "<div id=\"yellow-pane-system-settings-actions\" class=\"yellow-settings-left\"><p>"+this.getRawDataSettingsActions(paneAction)+"</p></div>"+
- "<div id=\"yellow-pane-system-settings-separator\" class=\"yellow-settings-left yellow-settings-separator\"> </div>"+
- "<div id=\"yellow-pane-system-settings-fields\" class=\"yellow-settings-right yellow-fields\">"+
- "<input type=\"hidden\" name=\"action\" value=\"system\" />"+
+ "<div id=\"yellow-pane-configure-settings-actions\" class=\"yellow-settings-left\"><p>"+this.getRawDataSettingsActions(paneAction)+"</p></div>"+
+ "<div id=\"yellow-pane-configure-settings-separator\" class=\"yellow-settings-left yellow-settings-separator\"> </div>"+
+ "<div id=\"yellow-pane-configure-settings-fields\" class=\"yellow-settings-right yellow-fields\">"+
+ "<input type=\"hidden\" name=\"action\" value=\"configure\" />"+
"<input type=\"hidden\" name=\"csrftoken\" value=\""+yellow.toolbox.encodeHtml(this.getCookie("csrftoken"))+"\" />"+
- "<p><label for=\"yellow-pane-system-sitename\">"+this.getText("SystemSitename")+"</label><br /><input class=\"yellow-form-control\" name=\"sitename\" id=\"yellow-pane-system-sitename\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("sitename"))+"\" /></p>"+
- "<p><label for=\"yellow-pane-system-author\">"+this.getText("SystemAuthor")+"</label><br /><input class=\"yellow-form-control\" name=\"author\" id=\"yellow-pane-system-author\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("author"))+"\" /></p>"+
- "<p><label for=\"yellow-pane-system-email\">"+this.getText("SystemEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-system-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
- "<p>"+this.getText("SystemInformation")+"</p>"+
+ "<p><label for=\"yellow-pane-configure-sitename\">"+this.getText("ConfigureSitename")+"</label><br /><input class=\"yellow-form-control\" name=\"sitename\" id=\"yellow-pane-configure-sitename\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("sitename"))+"\" /></p>"+
+ "<p><label for=\"yellow-pane-configure-author\">"+this.getText("ConfigureAuthor")+"</label><br /><input class=\"yellow-form-control\" name=\"author\" id=\"yellow-pane-configure-author\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("author"))+"\" /></p>"+
+ "<p><label for=\"yellow-pane-configure-email\">"+this.getText("ConfigureEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-configure-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+
+ "<p>"+this.getText("ConfigureInformation")+"</p>"+
"<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("ChangeButton")+"\" /></p>"+
"</div>"+
"<div class=\"yellow-settings yellow-settings-banner\"></div>"+
@@ -270,12 +268,12 @@ yellow.edit = {
case "yellow-pane-update":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
- "<div class=\"yellow-title\"><h1 id=\"yellow-pane-update-title\">"+yellow.toolbox.encodeHtml(yellow.system.coreVersion)+"</h1></div>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
+ "<div class=\"yellow-title\"><h1 id=\"yellow-pane-update-title\">"+yellow.toolbox.encodeHtml(yellow.system.coreProductRelease)+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-update-status\" class=\""+paneStatus+"\">"+this.getText("UpdateStatus", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-output\" id=\"yellow-pane-update-output\">"+yellow.page.rawDataOutput+"</div>"+
"<div class=\"yellow-buttons\" id=\"yellow-pane-update-buttons\">"+
- "<p><a href=\"#\" class=\"yellow-btn\" data-action=\"close\">"+this.getText("OkButton")+"</a></p>"+
+ "<p><a href=\"#\" id=\"yellow-pane-update-submit\" class=\"yellow-btn\" data-action=\"close\">"+this.getText("OkButton")+"</a></p>"+
"</div>"+
"</form>";
break;
@@ -330,7 +328,7 @@ yellow.edit = {
case "yellow-pane-menu":
elementDiv.innerHTML =
"<ul class=\"yellow-dropdown\">"+
- "<li><span>"+yellow.toolbox.encodeHtml(yellow.system.userEmail)+"</span></li>"+
+ "<li><span>"+yellow.toolbox.encodeHtml(yellow.user.email)+"</span></li>"+
"<li><a href=\"#\" data-action=\"settings\">"+this.getText("MenuSettings")+"</a></li>" +
"<li><a href=\"#\" data-action=\"help\">"+this.getText("MenuHelp")+"</a></li>" +
"<li><a href=\"#\" data-action=\"submit\" data-arguments=\"action:logout\">"+this.getText("MenuLogout")+"</a></li>"+
@@ -339,7 +337,7 @@ yellow.edit = {
case "yellow-pane-information":
elementDiv.innerHTML =
"<form method=\"post\">"+
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-title\"><h1 id=\"yellow-pane-information-title\">"+this.getText(paneAction+"Title")+"</h1></div>"+
"<div class=\"yellow-status\"><p id=\"yellow-pane-information-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</p></div>"+
"<div class=\"yellow-buttons\" id=\"yellow-pane-information-buttons\">"+
@@ -348,7 +346,7 @@ yellow.edit = {
"</form>";
break;
default: elementDiv.innerHTML =
- "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+
+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\" aria-label=\""+this.getText("CancelButton")+"\"></i></a>"+
"<div class=\"yellow-error\">Pane '"+paneId+"' was not found. Oh no...</div>";
}
elementPane.appendChild(elementDiv);
@@ -376,18 +374,20 @@ yellow.edit = {
}
if (paneStatus=="none") {
document.getElementById("yellow-pane-account-status").innerHTML = this.getText("AccountStatusNone");
- document.getElementById("yellow-pane-account-name").value = yellow.system.userName;
- document.getElementById("yellow-pane-account-email").value = yellow.system.userEmail;
+ document.getElementById("yellow-pane-account-name").value = yellow.user.name;
+ document.getElementById("yellow-pane-account-email").value = yellow.user.email;
document.getElementById("yellow-pane-account-password").value = "";
- document.getElementById("yellow-pane-account-"+yellow.system.userLanguage).checked = true;
+ if (document.getElementById("yellow-pane-account-"+yellow.user.language)) {
+ document.getElementById("yellow-pane-account-"+yellow.user.language).checked = true;
+ }
}
break;
- case "yellow-pane-system":
+ case "yellow-pane-configure":
if (paneStatus=="none") {
- document.getElementById("yellow-pane-system-status").innerHTML = this.getText("SystemStatusNone");
- document.getElementById("yellow-pane-system-sitename").value = yellow.system.sitename;
- document.getElementById("yellow-pane-system-author").value = yellow.system.author;
- document.getElementById("yellow-pane-system-email").value = yellow.system.email;
+ document.getElementById("yellow-pane-configure-status").innerHTML = this.getText("ConfigureStatusNone");
+ document.getElementById("yellow-pane-configure-sitename").value = yellow.system.sitename;
+ document.getElementById("yellow-pane-configure-author").value = yellow.system.author;
+ document.getElementById("yellow-pane-configure-email").value = yellow.system.email;
}
break;
case "yellow-pane-update":
@@ -397,7 +397,9 @@ yellow.edit = {
setTimeout("yellow.action('submit', '', 'action:update/option:check/');", 500);
}
if (paneStatus=="updates") {
- document.getElementById("yellow-pane-update-status").innerHTML = "<a href=\"#\" data-action=\"submit\" data-arguments=\"action:update\">"+this.getText("UpdateStatusUpdates")+"</a>";
+ document.getElementById(paneId+"-submit").innerHTML = this.getText("UpdateButton");
+ document.getElementById(paneId+"-submit").setAttribute("data-action", "submit");
+ document.getElementById(paneId+"-submit").setAttribute("data-arguments", "action:update");
}
break;
case "yellow-pane-create":
@@ -417,8 +419,12 @@ yellow.edit = {
this.updateToolbar(0, "yellow-toolbar-checked");
}
if (!this.isUserAccess(paneAction, yellow.page.location) || (yellow.page.rawDataReadonly && paneId!="yellow-pane-create")) {
- yellow.toolbox.setVisible(document.getElementById(paneId+"-submit"), false);
document.getElementById(paneId+"-text").readOnly = true;
+ var elements = document.getElementsByClassName("yellow-toolbar-btn-icon");
+ for (var i=0, l=elements.length; i<l; i++) {
+ yellow.toolbox.addClass(elements[i], "yellow-toolbar-disabled");
+ }
+ yellow.toolbox.setVisible(document.getElementById(paneId+"-submit"), false);
}
}
if (!document.getElementById(paneId+"-text").readOnly) {
@@ -445,7 +451,7 @@ yellow.edit = {
var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - Math.min(yellow.toolbox.getOuterHeight(elementBar) + 10, (yellow.toolbox.getWindowWidth()-yellow.toolbox.getOuterWidth(elementBar))/2);
switch (paneId) {
case "yellow-pane-account":
- case "yellow-pane-system":
+ case "yellow-pane-configure":
yellow.toolbox.setOuterLeft(document.getElementById(paneId), paneLeft);
yellow.toolbox.setOuterTop(document.getElementById(paneId), paneTop);
yellow.toolbox.setOuterWidth(document.getElementById(paneId), paneWidth);
@@ -508,7 +514,7 @@ yellow.edit = {
if (!document.getElementById(paneId)) this.createPane(paneId, paneAction, paneStatus);
var element = document.getElementById(paneId);
if (!yellow.toolbox.isVisible(element)) {
- if (yellow.system.debug) console.log("yellow.edit.showPane id:"+paneId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.showPane id:"+paneId);
yellow.toolbox.setVisible(element, true);
if (paneModal) {
yellow.toolbox.addClass(document.body, "yellow-body-modal-open");
@@ -530,7 +536,7 @@ yellow.edit = {
hidePane: function(paneId, fadeout) {
var element = document.getElementById(paneId);
if (yellow.toolbox.isVisible(element)) {
- if (yellow.system.debug) console.log("yellow.edit.hidePane id:"+paneId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.hidePane id:"+paneId);
yellow.toolbox.removeClass(document.body, "yellow-body-modal-open");
yellow.toolbox.removeValue("meta[name=viewport]", "content", ", maximum-scale=1, user-scalable=0");
yellow.toolbox.setVisible(element, false, fadeout);
@@ -548,7 +554,7 @@ yellow.edit = {
status = status ? status : "none";
arguments = arguments ? arguments : "none";
if (action!="none") {
- if (yellow.system.debug) console.log("yellow.edit.processAction action:"+action+" status:"+status);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.processAction action:"+action+" status:"+status);
var paneId = (status!="next" && status!="done") ? "yellow-pane-"+action : "yellow-pane-information";
switch(action) {
case "login": this.showPane(paneId, action, status); break;
@@ -563,24 +569,25 @@ yellow.edit = {
case "quit": this.showPane(paneId, action, status); break;
case "remove": this.showPane(paneId, action, status); break;
case "account": this.showPane(paneId, action, status); break;
- case "system": this.showPane(paneId, action, status); break;
+ case "configure": this.showPane(paneId, action, status); break;
case "update": this.showPane(paneId, action, status); break;
case "create": this.showPane(paneId, action, status, true); break;
case "edit": this.showPane(paneId, action, status, true); break;
case "delete": this.showPane(paneId, action, status, true); break;
case "menu": this.showPane(paneId, action, status); break;
- case "close": this.hidePane(this.paneId); break;
case "toolbar": this.processToolbar(status, arguments); break;
case "settings": this.processSettings(arguments); break;
case "submit": this.processSubmit(arguments); break;
+ case "restore": this.processSubmit("action:"+action); break;
case "help": this.processHelp(); break;
+ case "close": this.processClose(); break;
}
}
},
// Process toolbar
processToolbar: function(status, arguments) {
- if (yellow.system.debug) console.log("yellow.edit.processToolbar status:"+status);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.processToolbar status:"+status);
var elementText = document.getElementById(this.paneId+"-text");
var elementPreview = document.getElementById(this.paneId+"-preview");
if (!yellow.toolbox.isVisible(elementPreview) && !elementText.readOnly) {
@@ -607,14 +614,16 @@ yellow.edit = {
case "undo": yellow.editor.undo(); break;
case "redo": yellow.editor.redo(); break;
}
+ if (this.isExpandable(status)) {
+ this.showPopup("yellow-popup-"+status, status);
+ } else {
+ this.hidePopup(this.popupId);
+ }
}
- if (status=="preview" && !elementText.readOnly) this.showPreview(elementText, elementPreview);
- if (status=="save" && !elementText.readOnly && this.paneAction!="delete") this.processSubmit("action:"+this.paneAction);
- if (status=="help") window.open(this.getText("YellowHelpUrl"), "_blank");
- if (this.isExpandable(status)) {
- this.showPopup("yellow-popup-"+status, status);
- } else {
- this.hidePopup(this.popupId);
+ if (!elementText.readOnly) {
+ if (status=="preview") this.showPreview(elementText, elementPreview);
+ if (status=="save" && this.paneAction!="delete") this.processSubmit("action:"+this.paneAction);
+ if (status=="help") window.open(this.getText("YellowHelpUrl"), "_blank");
}
},
@@ -643,7 +652,7 @@ yellow.edit = {
for (var i=0; i<tokens.length; i++) {
var pair = tokens[i].split(" ");
if (shortcut==pair[0] || shortcut.replace("meta+", "ctrl+")==pair[0]) {
- if (yellow.system.debug) console.log("yellow.edit.processShortcut shortcut:"+shortcut);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.processShortcut shortcut:"+shortcut);
e.stopPropagation();
e.preventDefault();
this.processToolbar(pair[1]);
@@ -681,9 +690,21 @@ yellow.edit = {
window.open(this.getText("YellowHelpUrl"), "_self");
},
+ // Process close
+ processClose: function() {
+ this.hidePane(this.paneId);
+ if (yellow.page.action=="login") {
+ var url = yellow.system.coreServerScheme+"://"+
+ yellow.system.coreServerAddress+
+ yellow.system.coreServerBase+
+ yellow.page.location;
+ window.open(url, "_self");
+ }
+ },
+
// Create popup
createPopup: function(popupId) {
- if (yellow.system.debug) console.log("yellow.edit.createPopup id:"+popupId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.createPopup id:"+popupId);
var elementPopup = document.createElement("div");
elementPopup.className = "yellow-popup";
elementPopup.setAttribute("id", popupId);
@@ -719,25 +740,25 @@ yellow.edit = {
"<li><a href=\"#\" id=\"yellow-popup-list-tl\" data-action=\"toolbar\" data-status=\"tl\">"+this.getText("ToolbarTl")+"</a></li>"+
"</ul>";
break;
- case "yellow-popup-emojiawesome":
+ case "yellow-popup-emoji":
var rawDataEmojis = "";
- if (yellow.system.emojiawesomeToolbarButtons && yellow.system.emojiawesomeToolbarButtons!="none") {
- var tokens = yellow.system.emojiawesomeToolbarButtons.split(" ");
+ if (yellow.system.emojiToolbarButtons && yellow.system.emojiToolbarButtons!="none") {
+ var tokens = yellow.system.emojiToolbarButtons.split(" ");
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/[\:]/g,"");
var className = token.replace("+1", "plus1").replace("-1", "minus1").replace(/_/g, "-");
- rawDataEmojis += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"ea ea-"+yellow.toolbox.encodeHtml(className)+"\"></i></a></li>";
+ rawDataEmojis += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"emoji emoji-"+yellow.toolbox.encodeHtml(className)+"\"></i></a></li>";
}
}
elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataEmojis+"</ul>";
break;
- case "yellow-popup-fontawesome":
+ case "yellow-popup-icon":
var rawDataIcons = "";
- if (yellow.system.fontawesomeToolbarButtons && yellow.system.fontawesomeToolbarButtons!="none") {
- var tokens = yellow.system.fontawesomeToolbarButtons.split(" ");
+ if (yellow.system.iconToolbarButtons && yellow.system.iconToolbarButtons!="none") {
+ var tokens = yellow.system.iconToolbarButtons.split(" ");
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/[\:]/g,"");
- rawDataIcons += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"fa "+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>";
+ rawDataIcons += "<li><a href=\"#\" id=\"yellow-popup-list-"+yellow.toolbox.encodeHtml(token)+"\" data-action=\"toolbar\" data-status=\"text\" data-arguments=\":"+yellow.toolbox.encodeHtml(token)+":\"><i class=\"icon "+yellow.toolbox.encodeHtml(token)+"\"></i></a></li>";
}
}
elementDiv.innerHTML = "<ul class=\"yellow-dropdown yellow-dropdown-menu\">"+rawDataIcons+"</ul>";
@@ -754,7 +775,7 @@ yellow.edit = {
this.hidePopup(this.popupId);
if (!document.getElementById(popupId)) this.createPopup(popupId);
var element = document.getElementById(popupId);
- if (yellow.system.debug) console.log("yellow.edit.showPopup id:"+popupId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.showPopup id:"+popupId);
yellow.toolbox.setVisible(element, true);
this.popupId = popupId;
this.updateToolbar(status, "yellow-toolbar-selected");
@@ -772,7 +793,7 @@ yellow.edit = {
hidePopup: function(popupId, fadeout) {
var element = document.getElementById(popupId);
if (yellow.toolbox.isVisible(element)) {
- if (yellow.system.debug) console.log("yellow.edit.hidePopup id:"+popupId);
+ if (yellow.system.coreDebugMode) console.log("yellow.edit.hidePopup id:"+popupId);
yellow.toolbox.setVisible(element, false, fadeout);
this.popupId = 0;
this.updateToolbar(0, "yellow-toolbar-selected");
@@ -805,7 +826,7 @@ yellow.edit = {
if (showPreview) {
this.updateToolbar("preview", "yellow-toolbar-checked");
elementPreview.innerHTML = responseText;
- dispatchEvent(new Event("load"));
+ dispatchEvent(new Event("DOMContentLoaded"));
} else {
this.updateToolbar(0, "yellow-toolbar-checked");
elementText.focus();
@@ -829,7 +850,7 @@ yellow.edit = {
var extension = (file.name.lastIndexOf(".")!=-1 ? file.name.substring(file.name.lastIndexOf("."), file.name.length) : "").toLowerCase();
var extensions = yellow.system.editUploadExtensions.split(/\s*,\s*/);
if (file.size<=yellow.system.coreFileSizeMax && extensions.indexOf(extension)!=-1) {
- var text = this.getText("UploadProgress")+"\u200b";
+ var text = "["+this.getText("UploadProgress")+"]\u200b";
yellow.editor.setMarkdown(elementText, text, "insert");
var thisObject = this;
var formData = new FormData();
@@ -855,7 +876,7 @@ yellow.edit = {
uploadFileDone: function(elementText, responseText) {
var result = JSON.parse(responseText);
if (result) {
- var textOld = this.getText("UploadProgress")+"\u200b";
+ var textOld = "["+this.getText("UploadProgress")+"]\u200b";
var textNew;
if (result.location.substring(0, yellow.system.coreImageLocation.length)==yellow.system.coreImageLocation) {
textNew = "[image "+result.location.substring(yellow.system.coreImageLocation.length)+"]";
@@ -870,7 +891,7 @@ yellow.edit = {
uploadFileError: function(elementText, responseText) {
var result = JSON.parse(responseText);
if (result) {
- var textOld = this.getText("UploadProgress")+"\u200b";
+ var textOld = "["+this.getText("UploadProgress")+"]\u200b";
var textNew = "["+result.error+"]";
yellow.editor.replace(elementText, textOld, textNew);
}
@@ -880,8 +901,11 @@ yellow.edit = {
bindActions: function(element) {
var elements = element.getElementsByTagName("a");
for (var i=0, l=elements.length; i<l; i++) {
- if (elements[i].getAttribute("href") && elements[i].getAttribute("href").substring(0, 13)=="#data-action-") {
- elements[i].setAttribute("data-action", elements[i].getAttribute("href").substring(13));
+ if (elements[i].getAttribute("href") && elements[i].getAttribute("href").indexOf("#data-action-")!=-1) {
+ var position = elements[i].getAttribute("href").indexOf("#data-action-");
+ var action = elements[i].getAttribute("href").substring(position+13);
+ var href = elements[i].getAttribute("href").substring(0, position);
+ if (href=="" || href==yellow.page.base+yellow.page.location) elements[i].setAttribute("data-action", action);
}
if (elements[i].getAttribute("data-action")) elements[i].onclick = yellow.onClickAction;
if (elements[i].getAttribute("data-action")=="toolbar") elements[i].onmousedown = function(e) { e.preventDefault(); };
@@ -894,7 +918,7 @@ yellow.edit = {
var paneAction = paneId.substring(panePrefix.length);
if (paneAction=="edit") {
if (document.getElementById("yellow-pane-edit-text").value.length==0) paneAction = "delete";
- if (yellow.page.statusCode==434) paneAction = "create";
+ if (yellow.page.statusCode==434 || yellow.page.statusCode==435) paneAction = "create";
}
return paneAction;
},
@@ -931,7 +955,7 @@ yellow.edit = {
rawDataLanguages += "<label for=\""+paneId+"-"+language+"\"><input type=\"radio\" name=\"language\" id=\""+paneId+"-"+language+"\" value=\""+language+"\""+checked+"> "+yellow.toolbox.encodeHtml(yellow.system.coreLanguages[language])+"</label><br />";
}
}
- return rawDataLanguages
+ return rawDataLanguages;
},
// Return raw data for buttons
@@ -954,14 +978,14 @@ yellow.edit = {
return rawDataButtons;
},
- // Return request string
+ // Return request data
getRequest: function(key, prefix) {
if (!prefix) prefix = "request";
key = prefix + yellow.toolbox.toUpperFirst(key);
return (key in yellow.page) ? yellow.page[key] : "";
},
- // Return shortcut string
+ // Return shortcut setting
getShortcut: function(key) {
var shortcut = "";
var tokens = yellow.system.editKeyboardShortcuts.split(/\s*,\s*/);
@@ -972,7 +996,7 @@ yellow.edit = {
break;
}
}
- var labels = yellow.text.editKeyboardLabels.split(/\s*,\s*/);
+ var labels = yellow.language.editKeyboardLabels.split(/\s*,\s*/);
if (navigator.platform.indexOf("Mac")==-1) {
shortcut = shortcut.toUpperCase().replace("CTRL+", labels[0]).replace("ALT+", labels[1]).replace("SHIFT+", labels[2]);
} else {
@@ -982,12 +1006,12 @@ yellow.edit = {
return shortcut;
},
- // Return text string
+ // Return text setting
getText: function(key, prefix, postfix) {
if (!prefix) prefix = "edit";
if (!postfix) postfix = "";
key = prefix + yellow.toolbox.toUpperFirst(key) + yellow.toolbox.toUpperFirst(postfix);
- return (key in yellow.text) ? yellow.text[key] : "["+key+"]";
+ return (key in yellow.language) ? yellow.language[key] : "["+key+"]";
},
// Return browser cookie
@@ -997,13 +1021,13 @@ yellow.edit = {
// Check if user with access
isUserAccess: function(action, location) {
- var tokens = yellow.system.userAccess.split(/\s*,\s*/);
- return tokens.indexOf(action)!=-1 && (!location || location.substring(0, yellow.system.userHome.length)==yellow.system.userHome);
+ var tokens = yellow.user.access.split(/\s*,\s*/);
+ return tokens.indexOf(action)!=-1 && (!location || location.substring(0, yellow.user.home.length)==yellow.user.home);
},
// Check if element is expandable
isExpandable: function(name) {
- return (name=="format" || name=="heading" || name=="list" || name=="emojiawesome" || name=="fontawesome");
+ return (name=="format" || name=="heading" || name=="list" || name=="emoji" || name=="icon");
},
// Check if extension exists
@@ -1075,7 +1099,7 @@ yellow.editor = {
element.value = textSelectionBefore + textSelectionNew + textSelectionAfter;
element.setSelectionRange(selectionStartNew, selectionEndNew);
}
- if (yellow.system.debug) console.log("yellow.editor.setMarkdown type:"+information.type);
+ if (yellow.system.coreDebugMode) console.log("yellow.editor.setMarkdown type:"+information.type);
},
// Return Markdown formatting information
@@ -1209,7 +1233,7 @@ yellow.editor = {
element.value = textSelectionBefore + textSelectionNew + textSelectionAfter;
element.setSelectionRange(selectionStartNew, selectionEndNew);
element.scrollTop = 0;
- if (yellow.system.debug) console.log("yellow.editor.setMetaData key:"+key);
+ if (yellow.system.coreDebugMode) console.log("yellow.editor.setMetaData key:"+key);
}
},
diff --git a/system/extensions/edit.php b/system/extensions/edit.php
@@ -1,69 +1,114 @@
<?php
-// Edit extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/edit
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Edit extension, https://github.com/annaesvensson/yellow-edit
class YellowEdit {
- const VERSION = "0.8.27";
- const TYPE = "feature";
- public $yellow; //access to API
- public $response; //web response
- public $users; //user accounts
- public $merge; //text merge
+ const VERSION = "0.8.77";
+ public $yellow; // access to API
+ public $response; // web response
+ public $merge; // text merge
+ public $editable; // page can be edited? (boolean)
// Handle initialisation
public function onLoad($yellow) {
$this->yellow = $yellow;
$this->response = new YellowEditResponse($yellow);
- $this->users = new YellowEditUsers($yellow);
$this->merge = new YellowEditMerge($yellow);
+ $this->yellow->system->setDefault("editSiteEmail", "noreply");
$this->yellow->system->setDefault("editLocation", "/edit/");
$this->yellow->system->setDefault("editUploadNewLocation", "/media/@group/@filename");
- $this->yellow->system->setDefault("editUploadExtensions", ".gif, .jpg, .pdf, .png, .svg, .zip");
+ $this->yellow->system->setDefault("editUploadExtensions", ".gif, .jpg, .mp3, .ogg, .pdf, .png, .svg, .zip");
$this->yellow->system->setDefault("editKeyboardShortcuts", "ctrl+b bold, ctrl+i italic, ctrl+k strikethrough, ctrl+e code, ctrl+s save, ctrl+alt+p preview");
$this->yellow->system->setDefault("editToolbarButtons", "auto");
$this->yellow->system->setDefault("editEndOfLine", "auto");
$this->yellow->system->setDefault("editNewFile", "page-new-(.*).md");
- $this->yellow->system->setDefault("editUserFile", "user.ini");
$this->yellow->system->setDefault("editUserPasswordMinLength", "8");
$this->yellow->system->setDefault("editUserHashAlgorithm", "bcrypt");
$this->yellow->system->setDefault("editUserHashCost", "10");
+ $this->yellow->system->setDefault("editUserAccess", "create, edit, delete, restore, upload");
$this->yellow->system->setDefault("editUserHome", "/");
- $this->yellow->system->setDefault("editUserAccess", "create, edit, delete, upload");
$this->yellow->system->setDefault("editLoginRestriction", "0");
$this->yellow->system->setDefault("editLoginSessionTimeout", "2592000");
$this->yellow->system->setDefault("editBruteForceProtection", "25");
- $this->users->load($this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile"));
+ }
+
+ // Handle update
+ public function onUpdate($action) {
+ if ($action=="clean" || $action=="daily") {
+ $cleanup = false;
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $fileData = $this->yellow->toolbox->readFile($fileNameUser);
+ $fileDataNew = "";
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (lcfirst($matches[1])=="email" && !is_string_empty($matches[2])) {
+ $status = $this->yellow->user->getUser("status", $matches[2]);
+ $reserved = strtotime($this->yellow->user->getUser("modified", $matches[2])) + 60*60*24;
+ $cleanup = $status!="active" && $status!="inactive" && $reserved<=time();
+ }
+ }
+ if (!$cleanup) $fileDataNew .= $line;
+ }
+ $fileDataNew = rtrim($fileDataNew)."\n";
+ if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileNameUser, $fileDataNew)) {
+ $this->yellow->toolbox->log("error", "Can't write file '$fileNameUser'!");
+ }
+ }
}
// Handle request
public function onRequest($scheme, $address, $base, $location, $fileName) {
$statusCode = 0;
- if ($this->checkRequest($location)) {
+ if ($this->isEditLocation($location)) {
+ $this->editable = true;
$scheme = $this->yellow->system->get("coreServerScheme");
$address = $this->yellow->system->get("coreServerAddress");
$base = rtrim($this->yellow->system->get("coreServerBase").$this->yellow->system->get("editLocation"), "/");
- list($scheme, $address, $base, $location, $fileName) = $this->yellow->getRequestInformation($scheme, $address, $base);
- $this->yellow->page->setRequestInformation($scheme, $address, $base, $location, $fileName);
+ list($scheme, $address, $base, $location, $fileName) = $this->yellow->lookup->getRequestInformation($scheme, $address, $base);
+ $this->yellow->page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
$statusCode = $this->processRequest($scheme, $address, $base, $location, $fileName);
}
return $statusCode;
}
+ // Handle command
+ public function onCommand($command, $text) {
+ switch ($command) {
+ case "user": $statusCode = $this->processCommandUser($command, $text); break;
+ default: $statusCode = 0;
+ }
+ return $statusCode;
+ }
+
+ // Handle command help
+ public function onCommandHelp() {
+ return "user [option email password]";
+ }
+
+ // Handle page meta data
+ public function onParseMetaData($page) {
+ $page->set("editPageUrl", $this->yellow->lookup->normaliseUrl(
+ $this->yellow->system->get("coreServerScheme"),
+ $this->yellow->system->get("coreServerAddress"),
+ $this->yellow->system->get("coreServerBase"),
+ rtrim($this->yellow->system->get("editLocation"), "/").$page->location));
+ }
+
// Handle page content of shortcut
public function onParseContentShortcut($page, $name, $text, $type) {
$output = null;
if ($name=="edit" && $type=="inline") {
- $editText = "$name $text";
- if (substru($text, 0, 2)=="- ") $editText = trim(substru($text, 2));
- $output = "<a href=\"".$page->get("pageEdit")."\">".htmlspecialchars($editText)."</a>";
+ list($target, $description) = $this->yellow->toolbox->getTextList($text, " ", 2);
+ if (is_string_empty($target) || $target=="-") $target = "main";
+ if (is_string_empty($description)) $description = ucfirst($name);
+ $pageTarget = $target=="main" ? $page->getPage("main") : $page->getPage("main")->getPage($target);
+ $output = "<a href=\"".$pageTarget->get("editPageUrl")."\">".htmlspecialchars($description)."</a>";
}
return $output;
}
// Handle page layout
public function onParsePageLayout($page, $name) {
- if ($this->response->isActive()) {
+ if ($this->editable) {
$this->response->processPageData($page);
}
}
@@ -71,7 +116,7 @@ class YellowEdit {
// Handle page extra data
public function onParsePageExtra($page, $name) {
$output = null;
- if ($name=="header" && $this->response->isActive()) {
+ if ($this->editable && $name=="header") {
$extensionLocation = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreExtensionLocation");
$output = "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"{$extensionLocation}edit.css\" />\n";
$output .= "<script type=\"text/javascript\" src=\"{$extensionLocation}edit.js\"></script>\n";
@@ -79,50 +124,14 @@ class YellowEdit {
$output .= "// <![CDATA[\n";
$output .= "yellow.page = ".json_encode($this->response->getPageData($page)).";\n";
$output .= "yellow.system = ".json_encode($this->response->getSystemData()).";\n";
- $output .= "yellow.text = ".json_encode($this->response->getTextData()).";\n";
+ $output .= "yellow.user = ".json_encode($this->response->getUserData()).";\n";
+ $output .= "yellow.language = ".json_encode($this->response->getLanguageData()).";\n";
$output .= "// ]]>\n";
$output .= "</script>\n";
}
return $output;
}
- // Handle command
- public function onCommand($command, $text) {
- switch ($command) {
- case "user": $statusCode = $this->processCommandUser($command, $text); break;
- default: $statusCode = 0;
- }
- return $statusCode;
- }
-
- // Handle command help
- public function onCommandHelp() {
- return "user [option email password name]\n";
- }
-
- // Handle update
- public function onUpdate($action) {
- if ($action=="update") {
- $cleanup = false;
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $fileData = $this->yellow->toolbox->readFile($fileNameUser);
- $fileDataNew = "";
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="email" && !strempty($matches[2])) {
- $status = $this->users->getUser($matches[2], "status");
- $cleanup = !empty($status) && $status!="active" && $status!="inactive";
- }
- }
- if (!$cleanup) $fileDataNew .= $line;
- }
- $fileDataNew = rtrim($fileDataNew)."\n";
- if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileNameUser, $fileDataNew)) {
- $this->yellow->log("error", "Can't write file '$fileNameUser'!");
- }
- }
- }
-
// Process command to update user account
public function processCommandUser($command, $text) {
list($option) = $this->yellow->toolbox->getTextArguments($text);
@@ -138,21 +147,23 @@ class YellowEdit {
// Show user accounts
public function userShow($command, $text) {
- foreach ($this->users->getData() as $line) {
- echo "$line\n";
+ $data = array();
+ foreach ($this->yellow->user->settings as $key=>$value) {
+ $data[$key] = "$value[email] - User account by $value[name].";
}
- if (!$this->users->getNumber()) echo "Yellow $command: No user accounts\n";
+ uksort($data, "strnatcasecmp");
+ foreach ($data as $line) echo "$line\n";
+ if (is_array_empty($data)) echo "Yellow $command: No user accounts\n";
return 200;
}
// Add user account
public function userAdd($command, $text) {
$status = "ok";
- list($option, $email, $password, $name) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($email) || empty($password)) $status = $this->response->status = "incomplete";
- if (empty($name)) $name = $this->yellow->system->get("sitename");
- if ($status=="ok") $status = $this->getUserAccount($email, $password, "add");
- if ($status=="ok" && $this->users->isTaken($email)) $status = "taken";
+ list($option, $email, $password) = $this->yellow->toolbox->getTextArguments($text);
+ if (is_string_empty($email) || is_string_empty($password)) $status = $this->response->status = "incomplete";
+ if ($status=="ok") $status = $this->getUserAccount("add", $email, $password);
+ if ($status=="ok" && $this->isUserAccountTaken($email)) $status = "taken";
switch ($status) {
case "incomplete": echo "ERROR updating settings: Please enter email and password!\n"; break;
case "invalid": echo "ERROR updating settings: Please enter a valid email!\n"; break;
@@ -161,25 +172,28 @@ class YellowEdit {
case "short": echo "ERROR updating settings: Please enter a longer password!\n"; break;
}
if ($status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $name = $this->yellow->system->get("sitename");
+ $userLanguage = $this->yellow->system->get("language");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
"name" => $name,
- "language" => $this->yellow->system->get("language"),
- "home" => $this->yellow->system->get("editUserHome"),
+ "description" => $this->yellow->language->getText("editUserDescription", $userLanguage),
+ "language" => $userLanguage,
"access" => $this->yellow->system->get("editUserAccess"),
- "hash" => $this->users->createHash($password),
- "stamp" => $this->users->createStamp(),
+ "home" => $this->yellow->system->get("editUserHome"),
+ "hash" => $this->response->createHash($password),
+ "stamp" => $this->response->createStamp(),
"pending" => "none",
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()),
"status" => "active");
- $status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
- $this->yellow->log($status=="ok" ? "info" : "error", "Add user '".strtok($name, " ")."'");
+ $this->yellow->toolbox->log($status=="ok" ? "info" : "error", "Add user '".strtok($name, " ")."'");
}
if ($status=="ok") {
$algorithm = $this->yellow->system->get("editUserHashAlgorithm");
- $status = substru($this->users->getUser($email, "hash"), 0, 10)!="error-hash" ? "ok" : "error";
+ $status = substru($this->yellow->user->getUser("hash", $email), 0, 10)!="error-hash" ? "ok" : "error";
if ($status=="error") echo "ERROR updating settings: Hash algorithm '$algorithm' not supported!\n";
}
$statusCode = $status=="ok" ? 200 : 500;
@@ -190,10 +204,10 @@ class YellowEdit {
// Change user account
public function userChange($command, $text) {
$status = "ok";
- list($option, $email, $password, $name) = $this->yellow->toolbox->getTextArguments($text);
- if (empty($email)) $status = $this->response->status = "invalid";
- if ($status=="ok") $status = $this->getUserAccount($email, $password, "change");
- if ($status=="ok" && !$this->users->isExisting($email)) $status = "unknown";
+ list($option, $email, $password) = $this->yellow->toolbox->getTextArguments($text);
+ if (is_string_empty($email)) $status = $this->response->status = "invalid";
+ if ($status=="ok") $status = $this->getUserAccount("change", $email, $password);
+ if ($status=="ok" && !$this->yellow->user->isExisting($email)) $status = "unknown";
switch ($status) {
case "invalid": echo "ERROR updating settings: Please enter a valid email!\n"; break;
case "unknown": echo "ERROR updating settings: Can't find email '$email'!\n"; break;
@@ -201,13 +215,12 @@ class YellowEdit {
case "short": echo "ERROR updating settings: Please enter a longer password!\n"; break;
}
if ($status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
- "name" => empty($name) ? $this->users->getUser($email, "name") : $name,
- "hash" => empty($password) ? $this->users->getUser($email, "hash") : $this->users->createHash($password),
+ "hash" => is_string_empty($password) ? $this->yellow->user->getUser("hash", $email) : $this->response->createHash($password),
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()));
- $status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
}
$statusCode = $status=="ok" ? 200 : 500;
@@ -219,20 +232,19 @@ class YellowEdit {
public function userRemove($command, $text) {
$status = "ok";
list($option, $email) = $this->yellow->toolbox->getTextArguments($text);
- $name = $this->users->getUser($email, "name");
- if (empty($email)) $status = $this->response->status = "invalid";
- if (empty($name)) $name = $this->yellow->system->get("sitename");
- if ($status=="ok") $status = $this->getUserAccount($email, "", "remove");
- if ($status=="ok" && !$this->users->isExisting($email)) $status = "unknown";
+ if (is_string_empty($email)) $status = $this->response->status = "invalid";
+ if ($status=="ok") $status = $this->getUserAccount("remove", $email, "");
+ if ($status=="ok" && !$this->yellow->user->isExisting($email)) $status = "unknown";
switch ($status) {
case "invalid": echo "ERROR updating settings: Please enter a valid email!\n"; break;
case "unknown": echo "ERROR updating settings: Can't find email '$email'!\n"; break;
}
if ($status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $status = $this->users->remove($fileNameUser, $email) ? "ok" : "error";
+ $name = $this->yellow->user->getUser("name", $email);
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $status = $this->yellow->user->remove($fileNameUser, $email) ? "ok" : "error";
if ($status=="error") echo "ERROR updating settings: Can't write file '$fileNameUser'!\n";
- $this->yellow->log($status=="ok" ? "info" : "error", "Remove user '".strtok($name, " ")."'");
+ $this->yellow->toolbox->log($status=="ok" ? "info" : "error", "Remove user '".strtok($name, " ")."'");
}
$statusCode = $status=="ok" ? 200 : 500;
echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "")."removed\n";
@@ -249,11 +261,12 @@ class YellowEdit {
case "logout": $statusCode = $this->processRequestLogout($scheme, $address, $base, $location, $fileName); break;
case "quit": $statusCode = $this->processRequestQuit($scheme, $address, $base, $location, $fileName); break;
case "account": $statusCode = $this->processRequestAccount($scheme, $address, $base, $location, $fileName); break;
- case "system": $statusCode = $this->processRequestSystem($scheme, $address, $base, $location, $fileName); break;
+ case "configure": $statusCode = $this->processRequestConfigure($scheme, $address, $base, $location, $fileName); break;
case "update": $statusCode = $this->processRequestUpdate($scheme, $address, $base, $location, $fileName); break;
case "create": $statusCode = $this->processRequestCreate($scheme, $address, $base, $location, $fileName); break;
case "edit": $statusCode = $this->processRequestEdit($scheme, $address, $base, $location, $fileName); break;
case "delete": $statusCode = $this->processRequestDelete($scheme, $address, $base, $location, $fileName); break;
+ case "restore": $statusCode = $this->processRequestRestore($scheme, $address, $base, $location, $fileName); break;
case "preview": $statusCode = $this->processRequestPreview($scheme, $address, $base, $location, $fileName); break;
case "upload": $statusCode = $this->processRequestUpload($scheme, $address, $base, $location, $fileName); break;
}
@@ -284,11 +297,16 @@ class YellowEdit {
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
} else {
if ($this->yellow->lookup->isRedirectLocation($location)) {
- $location = $this->yellow->lookup->isFileLocation($location) ? "$location/" : "/".$this->yellow->getRequestLanguage()."/";
+ $location = $this->yellow->lookup->getRedirectLocation($location);
$location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
$statusCode = $this->yellow->sendStatus(301, $location);
} else {
- $this->yellow->page->error($this->response->isUserAccess("create", $location) ? 434 : 404);
+ $statusCode = 404;
+ if ($this->response->isUserAccess("create", $location)) $statusCode = 434;
+ if ($this->response->isUserAccess("restore", $location) && $this->response->isDeletedLocation($location)) {
+ $statusCode = 435;
+ }
+ $this->yellow->page->error($statusCode);
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
}
@@ -297,10 +315,10 @@ class YellowEdit {
// Process request for user login
public function processRequestLogin($scheme, $address, $base, $location, $fileName) {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
- if ($this->users->save($fileNameUser, $this->response->userEmail, $settings)) {
- $home = $this->users->getUser($this->response->userEmail, "home");
+ if ($this->yellow->user->save($fileNameUser, $this->response->userEmail, $settings)) {
+ $home = $this->yellow->user->getUser("home", $this->response->userEmail);
if (substru($location, 0, strlenu($home))==$home) {
$location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
$statusCode = $this->yellow->sendStatus(303, $location);
@@ -336,29 +354,31 @@ class YellowEdit {
$email = trim($this->yellow->page->getRequest("email"));
$password = trim($this->yellow->page->getRequest("password"));
$consent = trim($this->yellow->page->getRequest("consent"));
- if (empty($name) || empty($email) || empty($password) || empty($consent)) $this->response->status = "incomplete";
- if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, $password, $this->response->action);
+ if (is_string_empty($name) || is_string_empty($email) || is_string_empty($password) || is_string_empty($consent)) $this->response->status = "incomplete";
+ if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
if ($this->response->status=="ok" && $this->response->isLoginRestriction()) $this->response->status = "next";
- if ($this->response->status=="ok" && $this->users->isTaken($email)) $this->response->status = "next";
+ if ($this->response->status=="ok" && $this->isUserAccountTaken($email)) $this->response->status = "next";
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $userLanguage = $this->yellow->lookup->findContentLanguage($fileName, $this->yellow->system->get("language"));
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
"name" => $name,
- "language" => $this->yellow->lookup->findLanguageFromFile($fileName, $this->yellow->system->get("language")),
- "home" => $this->yellow->system->get("editUserHome"),
+ "description" => $this->yellow->language->getText("editUserDescription", $userLanguage),
+ "language" => $userLanguage,
"access" => $this->yellow->system->get("editUserAccess"),
- "hash" => $this->users->createHash($password),
- "stamp" => $this->users->createStamp(),
+ "home" => $this->yellow->system->get("editUserHome"),
+ "hash" => $this->response->createHash($password),
+ "stamp" => $this->response->createStamp(),
"pending" => "none",
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()),
"status" => "unconfirmed");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
$algorithm = $this->yellow->system->get("editUserHashAlgorithm");
- $this->response->status = substru($this->users->getUser($email, "hash"), 0, 10)!="error-hash" ? "ok" : "error";
+ $this->response->status = substru($this->yellow->user->getUser("hash", $email), 0, 10)!="error-hash" ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Hash algorithm '$algorithm' not supported!");
}
if ($this->response->status=="ok") {
@@ -376,9 +396,9 @@ class YellowEdit {
$email = $this->yellow->page->getRequest("email");
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "unapproved");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -396,11 +416,12 @@ class YellowEdit {
$email = $this->yellow->page->getRequest("email");
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $name = $this->yellow->user->getUser("name", $email);
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "active");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
- $this->yellow->log($this->response->status=="ok" ? "info" : "error", "Add user '".strtok($this->users->getUser($email, "name"), " ")."'");
+ $this->yellow->toolbox->log($this->response->status=="ok" ? "info" : "error", "Add user '".strtok($name, " ")."'");
}
if ($this->response->status=="ok") {
$this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "welcome") ? "done" : "error";
@@ -416,7 +437,7 @@ class YellowEdit {
$this->response->status = "ok";
$email = trim($this->yellow->page->getRequest("email"));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid";
- if ($this->response->status=="ok" && !$this->users->isExisting($email)) $this->response->status = "next";
+ if ($this->response->status=="ok" && !$this->yellow->user->isExisting($email)) $this->response->status = "next";
if ($this->response->status=="ok") {
$this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "recover") ? "next" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!");
@@ -433,12 +454,12 @@ class YellowEdit {
$password = trim($this->yellow->page->getRequest("password"));
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- if (empty($password)) $this->response->status = "password";
- if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, $password, $this->response->action);
+ if (is_string_empty($password)) $this->response->status = "password";
+ if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $settings = array("hash" => $this->users->createHash($password), "failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $settings = array("hash" => $this->response->createHash($password), "failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -457,9 +478,9 @@ class YellowEdit {
$email = $this->yellow->page->getRequest("email");
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "active");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "done" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "done" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
@@ -473,13 +494,13 @@ class YellowEdit {
$email = $emailSource = $this->yellow->page->getRequest("email");
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- $emailSource = $this->users->getUser($email, "pending");
- if ($this->users->getUser($emailSource, "status")!="active") $this->response->status = "done";
+ $emailSource = $this->yellow->user->getUser("pending", $email);
+ if ($this->yellow->user->getUser("status", $emailSource)!="active") $this->response->status = "done";
}
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "unchanged");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -497,23 +518,23 @@ class YellowEdit {
$email = $emailSource = trim($this->yellow->page->getRequest("email"));
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- list($email, $hash) = $this->yellow->toolbox->getTextList($this->users->getUser($email, "pending"), ":", 2);
- if (!$this->users->isExisting($email) || empty($hash)) $this->response->status = "done";
+ list($email, $hash) = $this->yellow->toolbox->getTextList($this->yellow->user->getUser("pending", $email), ":", 2);
+ if (!$this->yellow->user->isExisting($email) || is_string_empty($hash)) $this->response->status = "done";
}
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
"hash" => $hash,
"pending" => "none",
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()),
"status" => "active");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok" && $email!=$emailSource) {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $this->response->status = $this->users->remove($fileNameUser, $emailSource) ? "ok" : "error";
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $this->response->status = $this->yellow->user->remove($fileNameUser, $emailSource) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -530,9 +551,9 @@ class YellowEdit {
$this->response->status = "ok";
$name = trim($this->yellow->page->getRequest("name"));
$email = $this->response->userEmail;
- if (empty($name)) $this->response->status = "none";
- if ($this->response->status=="ok" && $name!=$this->users->getUser($email, "name")) $this->response->status = "mismatch";
- if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, "", $this->response->action);
+ if (is_string_empty($name)) $this->response->status = "none";
+ if ($this->response->status=="ok" && $name!=$this->yellow->user->getUser("name", $email)) $this->response->status = "mismatch";
+ if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, "");
if ($this->response->status=="ok") {
$this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "remove") ? "next" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!");
@@ -548,19 +569,20 @@ class YellowEdit {
$email = $this->yellow->page->getRequest("email");
$this->response->status = $this->getUserStatus($email, $this->yellow->page->getRequest("action"));
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $name = $this->yellow->user->getUser("name", $email);
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("failed" => "0", "modified" => date("Y-m-d H:i:s", time()), "status" => "removed");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
- $this->yellow->log($this->response->status=="ok" ? "info" : "error", "Remove user '".strtok($this->users->getUser($email, "name"), " ")."'");
+ $this->yellow->toolbox->log($this->response->status=="ok" ? "info" : "error", "Remove user '".strtok($name, " ")."'");
}
if ($this->response->status=="ok") {
$this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "goodbye") ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!");
}
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $this->response->status = $this->users->remove($fileNameUser, $email) ? "ok" : "error";
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $this->response->status = $this->yellow->user->remove($fileNameUser, $email) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -580,35 +602,36 @@ class YellowEdit {
$password = trim($this->yellow->page->getRequest("password"));
$name = trim(preg_replace("/[^\pL\d\-\. ]/u", "-", $this->yellow->page->getRequest("name")));
$language = trim($this->yellow->page->getRequest("language"));
- if ($email!=$emailSource || !empty($password)) {
- if (empty($email)) $this->response->status = "invalid";
- if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, $password, $this->response->action);
- if ($this->response->status=="ok" && $email!=$emailSource && $this->users->isTaken($email)) $this->response->status = "taken";
+ if ($email!=$emailSource || !is_string_empty($password)) {
+ if (is_string_empty($email)) $this->response->status = "invalid";
+ if ($this->response->status=="ok") $this->response->status = $this->getUserAccount($this->response->action, $email, $password);
+ if ($this->response->status=="ok" && $email!=$emailSource && $this->isUserAccountTaken($email)) $this->response->status = "taken";
if ($this->response->status=="ok" && $email!=$emailSource) {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
"name" => $name,
+ "description" => $this->yellow->user->getUser("description", $emailSource),
"language" => $language,
- "home" => $this->users->getUser($emailSource, "home"),
- "access" => $this->users->getUser($emailSource, "access"),
- "hash" => $this->users->createHash("none"),
- "stamp" => $this->users->createStamp(),
+ "access" => $this->yellow->user->getUser("access", $emailSource),
+ "home" => $this->yellow->user->getUser("home", $emailSource),
+ "hash" => $this->response->createHash("none"),
+ "stamp" => $this->response->createStamp(),
"pending" => $emailSource,
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()),
"status" => "unverified");
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array(
"name" => $name,
"language" => $language,
- "pending" => $email.":".(empty($password) ? $this->users->getUser($emailSource, "hash") : $this->users->createHash($password)),
+ "pending" => $email.":".(is_string_empty($password) ? $this->yellow->user->getUser("hash", $emailSource) : $this->response->createHash($password)),
"failed" => "0",
"modified" => date("Y-m-d H:i:s", time()));
- $this->response->status = $this->users->save($fileNameUser, $emailSource, $settings) ? "ok" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $emailSource, $settings) ? "ok" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($this->response->status=="ok") {
@@ -618,9 +641,9 @@ class YellowEdit {
}
} else {
if ($this->response->status=="ok") {
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
$settings = array("name" => $name, "language" => $language, "failed" => "0", "modified" => date("Y-m-d H:i:s", time()));
- $this->response->status = $this->users->save($fileNameUser, $email, $settings) ? "done" : "error";
+ $this->response->status = $this->yellow->user->save($fileNameUser, $email, $settings) ? "done" : "error";
if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
}
@@ -633,24 +656,24 @@ class YellowEdit {
return $statusCode;
}
- // Process request to change system settings
- public function processRequestSystem($scheme, $address, $base, $location, $fileName) {
+ // Process request to change settings
+ public function processRequestConfigure($scheme, $address, $base, $location, $fileName) {
$statusCode = 0;
- if ($this->response->isUserAccess("system")) {
- $this->response->action = "system";
+ if ($this->response->isUserAccess("configure")) {
+ $this->response->action = "configure";
$this->response->status = "ok";
$sitename = trim($this->yellow->page->getRequest("sitename"));
$author = trim($this->yellow->page->getRequest("author"));
$email = trim($this->yellow->page->getRequest("email"));
if ($email!=$this->yellow->system->get("email")) {
- if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid";
+ if (is_string_empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid";
}
if ($this->response->status=="ok") {
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
+ $fileNameSystem = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
$settings = array("sitename" => $sitename, "author" => $author, "email" => $email);
- $file = $this->response->getFileSystem($scheme, $address, $base, $location, $fileName, $settings);
- $this->response->status = (!$file->isError() && $this->yellow->system->save($fileName, $settings)) ? "done" : "error";
- if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileName'!");
+ $file = $this->response->getFileSystem($scheme, $address, $base, $location, $fileNameSystem, $settings);
+ $this->response->status = (!$file->isError() && $this->yellow->system->save($fileNameSystem, $settings)) ? "done" : "error";
+ if ($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameSystem'!");
}
if ($this->response->status=="done") {
$location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
@@ -668,18 +691,16 @@ class YellowEdit {
if ($this->response->isUserAccess("update")) {
$this->response->action = "update";
$this->response->status = "ok";
- $extension = trim($this->yellow->page->getRequest("extension"));
- $option = trim($this->yellow->page->getRequest("option"));
- if ($option=="check") {
- list($statusCode, $updates, $rawData) = $this->response->getUpdateInformation();
- $this->response->status = $updates ? "updates" : "ok";
+ if ($this->yellow->page->getRequest("option")=="check") {
+ list($statusCode, $rawData) = $this->response->getUpdateInformation();
+ $this->response->status = is_string_empty($rawData) ? "ok" : "updates";
$this->response->rawDataOutput = $rawData;
if ($statusCode!=200) {
$this->response->status = "error";
$this->response->rawDataOutput = "";
}
} else {
- $this->response->status = $this->yellow->command("update $extension $option")==0 ? "done" : "error";
+ $this->response->status = $this->yellow->command("update all")==0 ? "done" : "error";
}
if ($this->response->status=="done") {
$location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
@@ -694,7 +715,7 @@ class YellowEdit {
// Process request to create page
public function processRequestCreate($scheme, $address, $base, $location, $fileName) {
$statusCode = 0;
- if ($this->response->isUserAccess("create", $location) && !empty($this->yellow->page->getRequest("rawdataedit"))) {
+ if ($this->response->isUserAccess("create", $location) && !is_string_empty($this->yellow->page->getRequest("rawdataedit"))) {
$this->response->rawDataSource = $this->yellow->page->getRequest("rawdatasource");
$this->response->rawDataEdit = $this->yellow->page->getRequest("rawdatasource");
$this->response->rawDataEndOfLine = $this->yellow->page->getRequest("rawdataendofline");
@@ -710,7 +731,7 @@ class YellowEdit {
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
} else {
- $this->yellow->page->error(500, $page->get("pageError"));
+ $this->yellow->page->error(500, $page->errorMessage);
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
}
@@ -720,7 +741,7 @@ class YellowEdit {
// Process request to edit page
public function processRequestEdit($scheme, $address, $base, $location, $fileName) {
$statusCode = 0;
- if ($this->response->isUserAccess("edit", $location) && !empty($this->yellow->page->getRequest("rawdataedit"))) {
+ if ($this->response->isUserAccess("edit", $location) && !is_string_empty($this->yellow->page->getRequest("rawdataedit"))) {
$this->response->rawDataSource = $this->yellow->page->getRequest("rawdatasource");
$this->response->rawDataEdit = $this->yellow->page->getRequest("rawdataedit");
$this->response->rawDataEndOfLine = $this->yellow->page->getRequest("rawdataendofline");
@@ -729,26 +750,21 @@ class YellowEdit {
$this->response->rawDataSource, $this->response->rawDataEdit, $rawDataFile, $this->response->rawDataEndOfLine);
if (!$page->isError()) {
if ($this->yellow->lookup->isFileLocation($location)) {
- if ($this->yellow->toolbox->renameFile($fileName, $page->fileName, true) &&
- $this->yellow->toolbox->createFile($page->fileName, $page->rawData)) {
- $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $page->location);
- $statusCode = $this->yellow->sendStatus(303, $location);
- } else {
- $this->yellow->page->error(500, "Can't write file '$page->fileName'!");
- $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
- }
+ $ok = $this->yellow->toolbox->renameFile($fileName, $page->fileName, true) &&
+ $this->yellow->toolbox->createFile($page->fileName, $page->rawData);
} else {
- if ($this->yellow->toolbox->renameDirectory(dirname($fileName), dirname($page->fileName), true) &&
- $this->yellow->toolbox->createFile($page->fileName, $page->rawData)) {
- $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $page->location);
- $statusCode = $this->yellow->sendStatus(303, $location);
- } else {
- $this->yellow->page->error(500, "Can't write file '$page->fileName'!");
- $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
- }
+ $ok = $this->yellow->toolbox->renameDirectory(dirname($fileName), dirname($page->fileName), true) &&
+ $this->yellow->toolbox->createFile($page->fileName, $page->rawData);
+ }
+ if ($ok) {
+ $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $page->location);
+ $statusCode = $this->yellow->sendStatus(303, $location);
+ } else {
+ $this->yellow->page->error(500, "Can't write file '$page->fileName'!");
+ $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
} else {
- $this->yellow->page->error(500, $page->get("pageError"));
+ $this->yellow->page->error(500, $page->errorMessage);
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
}
@@ -767,39 +783,61 @@ class YellowEdit {
$rawDataFile, $this->response->rawDataEndOfLine);
if (!$page->isError()) {
if ($this->yellow->lookup->isFileLocation($location)) {
- if ($this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"))) {
- $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
- $statusCode = $this->yellow->sendStatus(303, $location);
- } else {
- $this->yellow->page->error(500, "Can't delete file '$fileName'!");
- $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
- }
+ $ok = $this->response->deleteFileLocation($location, $fileName);
} else {
- if ($this->yellow->toolbox->deleteDirectory(dirname($fileName), $this->yellow->system->get("coreTrashDirectory"))) {
- $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
- $statusCode = $this->yellow->sendStatus(303, $location);
- } else {
- $this->yellow->page->error(500, "Can't delete file '$fileName'!");
- $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
- }
+ $ok = $this->response->deleteDirectoryLocation($location, $fileName);
+ }
+ if ($ok) {
+ $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
+ $statusCode = $this->yellow->sendStatus(303, $location);
+ } else {
+ $this->yellow->page->error(500, "Can't delete file '$fileName'!");
+ $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
} else {
- $this->yellow->page->error(500, $page->get("pageError"));
+ $this->yellow->page->error(500, $page->errorMessage);
$statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
}
}
return $statusCode;
}
+ // Process request to restore deleted page
+ public function processRequestRestore($scheme, $address, $base, $location, $fileName) {
+ $statusCode = 0;
+ if ($this->response->isUserAccess("restore", $location) && !is_file($fileName)) {
+ $page = $this->response->getPageRestore($scheme, $address, $base, $location, $fileName);
+ if (!$page->isError()) {
+ if ($this->yellow->lookup->isFileLocation($location)) {
+ $ok = $this->response->restoreFileLocation($location);
+ } else {
+ $ok = $this->response->restoreDirectoryLocation($location);
+ }
+ if ($ok) {
+ $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
+ $statusCode = $this->yellow->sendStatus(303, $location);
+ } else {
+ $this->yellow->page->error(500, "Can't restore file '$fileName'!");
+ $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
+ }
+ } else {
+ $this->yellow->page->error(500, $page->errorMessage);
+ $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false);
+ }
+ }
+ return $statusCode;
+ }
+
// Process request to show preview
public function processRequestPreview($scheme, $address, $base, $location, $fileName) {
$page = $this->response->getPagePreview($scheme, $address, $base, $location, $fileName,
$this->yellow->page->getRequest("rawdataedit"), $this->yellow->page->getRequest("rawdataendofline"));
- $statusCode = $this->yellow->sendData(200, $page->outputData, "", false);
- if (defined("DEBUG") && DEBUG>=1) {
- $parser = $page->get("parser");
- echo "YellowEdit::processRequestPreview parser:$parser<br/>\n";
- }
+ $page->headerData = array(
+ "Cache-Control"=>"no-cache, no-store",
+ "Content-Type"=>$this->yellow->toolbox->getMimeContentType("a.html"),
+ "Last-Modified"=>$this->yellow->toolbox->getHttpDateFormatted(time()));
+ $statusCode = $this->yellow->sendData($page->statusCode, $page->headerData, $page->outputData);
+ if ($this->yellow->system->get("coreDebugMode")>=1) echo "YellowEdit::processRequestPreview file:$fileName<br/>\n";
return $statusCode;
}
@@ -822,15 +860,11 @@ class YellowEdit {
} else {
$data["error"] = "Can't write file '$fileNameShort'!";
}
- $statusCode = $this->yellow->sendData(isset($data["error"]) ? 500 : 200, json_encode($data), "a.json", false);
- return $statusCode;
- }
-
- // Check request
- public function checkRequest($location) {
- $locationLength = strlenu($this->yellow->system->get("editLocation"));
- $this->response->active = substru($location, 0, $locationLength)==$this->yellow->system->get("editLocation");
- return $this->response->isActive();
+ $headerData = array(
+ "Cache-Control"=>"no-cache, no-store",
+ "Content-Type"=>$this->yellow->toolbox->getMimeContentType("a.json"),
+ "Last-Modified"=>$this->yellow->toolbox->getHttpDateFormatted(time()));
+ return $this->yellow->sendData(isset($data["error"]) ? 500 : 200, $headerData, json_encode($data));
}
// Check user authentication
@@ -838,11 +872,11 @@ class YellowEdit {
$action = $this->yellow->page->getRequest("action");
$authToken = $this->yellow->toolbox->getCookie("authtoken");
$csrfToken = $this->yellow->toolbox->getCookie("csrftoken");
- if (empty($action) || $this->isRequestSameSite("POST", $scheme, $address)) {
+ if (is_string_empty($action) || $this->isRequestSameSite("POST", $scheme, $address)) {
if ($action=="login") {
$email = $this->yellow->page->getRequest("email");
$password = $this->yellow->page->getRequest("password");
- if ($this->users->checkAuthLogin($email, $password)) {
+ if ($this->response->checkAuthLogin($email, $password)) {
$this->response->createCookies($scheme, $address, $base, $email);
$this->response->userEmail = $email;
$this->response->language = $this->getUserLanguage($email);
@@ -851,18 +885,19 @@ class YellowEdit {
$this->response->userFailedEmail = $email;
$this->response->userFailedExpire = PHP_INT_MAX;
}
- } elseif (!empty($authToken) && !empty($csrfToken)) {
+ } elseif (!is_string_empty($authToken) && !is_string_empty($csrfToken)) {
$csrfTokenReceived = isset($_POST["csrftoken"]) ? $_POST["csrftoken"] : "";
- $csrfTokenIrrelevant = empty($action);
- if ($this->users->checkAuthToken($authToken, $csrfToken, $csrfTokenReceived, $csrfTokenIrrelevant)) {
- $this->response->userEmail = $email = $this->users->getAuthEmail($authToken);
+ $csrfTokenIrrelevant = is_string_empty($action);
+ if ($this->response->checkAuthToken($authToken, $csrfToken, $csrfTokenReceived, $csrfTokenIrrelevant)) {
+ $this->response->userEmail = $email = $this->response->getAuthEmail($authToken);
$this->response->language = $this->getUserLanguage($email);
} else {
$this->response->userFailedError = "auth";
- $this->response->userFailedEmail = $this->users->getAuthEmail($authToken);
- $this->response->userFailedExpire = $this->users->getAuthExpire($authToken);
+ $this->response->userFailedEmail = $this->response->getAuthEmail($authToken);
+ $this->response->userFailedExpire = $this->response->getAuthExpire($authToken);
}
}
+ $this->yellow->user->set($this->response->userEmail);
}
return $this->response->isUser();
}
@@ -871,15 +906,15 @@ class YellowEdit {
public function checkUserUnauth($scheme, $address, $base, $location, $fileName) {
$ok = false;
$action = $this->yellow->page->getRequest("action");
- if (empty($action) || $action=="signup" || $action=="forgot") {
+ if (is_string_empty($action) || $action=="signup" || $action=="forgot") {
$ok = true;
} elseif ($this->yellow->page->isRequest("actiontoken")) {
$actionToken = $this->yellow->page->getRequest("actiontoken");
$email = $this->yellow->page->getRequest("email");
$action = $this->yellow->page->getRequest("action");
$expire = $this->yellow->page->getRequest("expire");
- $langauge = $this->yellow->page->getRequest("language");
- if ($this->users->checkActionToken($actionToken, $email, $action, $expire)) {
+ $language = $this->yellow->page->getRequest("language");
+ if ($this->response->checkActionToken($actionToken, $email, $action, $expire)) {
$ok = true;
$this->response->language = $this->getActionLanguage($language);
} else {
@@ -893,18 +928,18 @@ class YellowEdit {
// Check user failed
public function checkUserFailed($scheme, $address, $base, $location, $fileName) {
- if (!empty($this->response->userFailedError)) {
- if ($this->response->userFailedExpire>time() && $this->users->isExisting($this->response->userFailedEmail)) {
+ if (!is_string_empty($this->response->userFailedError)) {
+ if ($this->response->userFailedExpire>time() && $this->yellow->user->isExisting($this->response->userFailedEmail)) {
$email = $this->response->userFailedEmail;
- $failed = $this->users->getUser($email, "failed")+1;
- $fileNameUser = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("editUserFile");
- $status = $this->users->save($fileNameUser, $email, array("failed" => $failed)) ? "ok" : "error";
+ $failed = $this->yellow->user->getUser("failed", $email)+1;
+ $fileNameUser = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreUserFile");
+ $status = $this->yellow->user->save($fileNameUser, $email, array("failed" => $failed)) ? "ok" : "error";
if ($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
if ($failed==$this->yellow->system->get("editBruteForceProtection")) {
- $statusBeforeProtection = $this->users->getUser($email, "status");
+ $statusBeforeProtection = $this->yellow->user->getUser("status", $email);
$statusAfterProtection = ($statusBeforeProtection=="active" || $statusBeforeProtection=="inactive") ? "inactive" : "failed";
if ($status=="ok") {
- $status = $this->users->save($fileNameUser, $email, array("status" => $statusAfterProtection)) ? "ok" : "error";
+ $status = $this->yellow->user->save($fileNameUser, $email, array("status" => $statusAfterProtection)) ? "ok" : "error";
if ($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!");
}
if ($status=="ok" && $statusBeforeProtection=="active") {
@@ -935,40 +970,51 @@ class YellowEdit {
case "change": $statusExpected = "active"; break;
case "remove": $statusExpected = "active"; break;
}
- return $this->users->getUser($email, "status")==$statusExpected ? "ok" : "done";
+ return $this->yellow->user->getUser("status", $email)==$statusExpected ? "ok" : "done";
}
// Return user account changes
- public function getUserAccount($email, $password, $action) {
+ public function getUserAccount($action, $email, $password) {
$status = null;
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onEditUserAccount")) {
- $status = $value["obj"]->onEditUserAccount($email, $password, $action, $this->users);
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onEditUserAccount")) {
+ $status = $value["object"]->onEditUserAccount($action, $email, $password);
if (!is_null($status)) break;
}
}
if (is_null($status)) {
$status = "ok";
- if (!empty($password) && strlenu($password)<$this->yellow->system->get("editUserPasswordMinLength")) $status = "short";
- if (!empty($password) && $password==$email) $status = "weak";
- if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) $status = "invalid";
+ if (!is_string_empty($password) && strlenu($password)<$this->yellow->system->get("editUserPasswordMinLength")) $status = "short";
+ if (!is_string_empty($password) && $password==$email) $status = "weak";
+ if (!is_string_empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) $status = "invalid";
}
return $status;
}
// Return user language
public function getUserLanguage($email) {
- $language = $this->users->getUser($email, "language");
- if (!$this->yellow->text->isLanguage($language)) $language = $this->yellow->system->get("language");
+ $language = $this->yellow->user->getUser("language", $email);
+ if (!$this->yellow->language->isExisting($language)) $language = $this->yellow->system->get("language");
return $language;
}
// Return action language
public function getActionLanguage($language) {
- if (!$this->yellow->text->isLanguage($language)) $language = $this->yellow->system->get("language");
+ if (!$this->yellow->language->isExisting($language)) $language = $this->yellow->system->get("language");
return $language;
}
+ // Check if user account is taken
+ public function isUserAccountTaken($email) {
+ $taken = false;
+ if ($this->yellow->user->isExisting($email)) {
+ $status = $this->yellow->user->getUser("status", $email);
+ $reserved = strtotime($this->yellow->user->getUser("modified", $email)) + 60*60*24;
+ if ($status=="active" || $status=="inactive" || $reserved>time()) $taken = true;
+ }
+ return $taken;
+ }
+
// Check if request came from same site
public function isRequestSameSite($method, $scheme, $address) {
$origin = "";
@@ -976,69 +1022,75 @@ class YellowEdit {
if ($this->yellow->toolbox->getServer("HTTP_ORIGIN")) $origin = $this->yellow->toolbox->getServer("HTTP_ORIGIN");
return $this->yellow->toolbox->getServer("REQUEST_METHOD")==$method && $origin=="$scheme://$address";
}
+
+ // Check if edit location
+ public function isEditLocation($location) {
+ $locationLength = strlenu($this->yellow->system->get("editLocation"));
+ return substru($location, 0, $locationLength)==$this->yellow->system->get("editLocation");
+ }
}
class YellowEditResponse {
- public $yellow; //access to API
- public $extension; //access to extension
- public $active; //location is active? (boolean)
- public $userEmail; //user email
- public $userFailedError; //error of failed authentication
- public $userFailedEmail; //email of failed authentication
- public $userFailedExpire; //expiration time of failed authentication
- public $rawDataSource; //raw data of page for comparison
- public $rawDataEdit; //raw data of page for editing
- public $rawDataOutput; //raw data of dynamic output
- public $rawDataReadonly; //raw data is read only? (boolean)
- public $rawDataEndOfLine; //end of line format for raw data
- public $language; //response language
- public $action; //response action
- public $status; //response status
+ public $yellow; // access to API
+ public $extension; // access to extension
+ public $userEmail; // user email
+ public $userFailedError; // error of failed authentication
+ public $userFailedEmail; // email of failed authentication
+ public $userFailedExpire; // expiration time of failed authentication
+ public $rawDataSource; // raw data of page for comparison
+ public $rawDataEdit; // raw data of page for editing
+ public $rawDataOutput; // raw data of dynamic output
+ public $rawDataReadonly; // raw data is read only? (boolean)
+ public $rawDataEndOfLine; // end of line format for raw data
+ public $language; // response language
+ public $action; // response action
+ public $status; // response status
public function __construct($yellow) {
$this->yellow = $yellow;
- $this->extension = $yellow->extensions->get("edit");
+ $this->extension = $yellow->extension->get("edit");
+ $this->userEmail = "";
}
// Process page data
public function processPageData($page) {
if ($this->isUser()) {
- if (empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
- if (empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
- if (empty($this->rawDataEndOfLine)) $this->rawDataEndOfLine = $this->getEndOfLine($page->rawData);
+ if (is_string_empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
+ if (is_string_empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
+ if (is_string_empty($this->rawDataEndOfLine)) $this->rawDataEndOfLine = $this->getEndOfLine($page->rawData);
if ($page->statusCode==404 || $this->yellow->toolbox->isLocationArguments()) {
$this->rawDataEdit = $this->getRawDataGenerated($page);
$this->rawDataReadonly = true;
}
- if ($page->statusCode==434) {
+ if ($page->statusCode==434 || $page->statusCode==435) {
$this->rawDataEdit = $this->getRawDataNew($page, true);
$this->rawDataReadonly = false;
}
}
- if (empty($this->language)) $this->language = $page->get("language");
- if (empty($this->action)) $this->action = $this->isUser() ? "none" : "login";
- if (empty($this->status)) $this->status = "none";
+ if (is_string_empty($this->language)) $this->language = $page->get("language");
+ if (is_string_empty($this->action)) $this->action = $this->isUser() ? "none" : "login";
+ if (is_string_empty($this->status)) $this->status = "none";
if ($this->status=="error") $this->action = "error";
}
// Return new page
public function getPageNew($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
- $rawData = $this->yellow->toolbox->normaliseLines($rawData, $endOfLine);
+ $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
$page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $page->parseData($rawData, false, 0);
- $this->editContentFile($page, "create");
+ $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $page->parseMeta($rawData);
+ $this->editContentFile($page, "create", $this->userEmail);
if ($this->yellow->content->find($page->location)) {
- $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"));
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"));
$page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("published"));
- while ($this->yellow->content->find($page->location) || empty($page->fileName)) {
+ while ($this->yellow->content->find($page->location) || is_string_empty($page->fileName)) {
$page->rawData = $this->yellow->toolbox->setMetaData($page->rawData, "title", $this->getTitleNext($page->rawData));
- $page->rawData = $this->yellow->toolbox->normaliseLines($page->rawData, $endOfLine);
- $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"));
+ $page->rawData = $this->yellow->lookup->normaliseLines($page->rawData, $endOfLine);
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"));
$page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("published"));
if (++$pageCounter>999) break;
}
- if ($this->yellow->content->find($page->location) || empty($page->fileName)) {
+ if ($this->yellow->content->find($page->location) || is_string_empty($page->fileName)) {
$page->error(500, "Page '".$page->get("title")."' is not possible!");
}
} else {
@@ -1052,25 +1104,25 @@ class YellowEditResponse {
// Return modified page
public function getPageEdit($scheme, $address, $base, $location, $fileName, $rawDataSource, $rawDataEdit, $rawDataFile, $endOfLine) {
- $rawDataSource = $this->yellow->toolbox->normaliseLines($rawDataSource, $endOfLine);
- $rawDataEdit = $this->yellow->toolbox->normaliseLines($rawDataEdit, $endOfLine);
- $rawDataFile = $this->yellow->toolbox->normaliseLines($rawDataFile, $endOfLine);
+ $rawDataSource = $this->yellow->lookup->normaliseLines($rawDataSource, $endOfLine);
+ $rawDataEdit = $this->yellow->lookup->normaliseLines($rawDataEdit, $endOfLine);
+ $rawDataFile = $this->yellow->lookup->normaliseLines($rawDataFile, $endOfLine);
$rawData = $this->extension->merge->merge($rawDataSource, $rawDataEdit, $rawDataFile);
$page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $page->parseData($rawData, false, 0);
+ $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $page->parseMeta($rawData);
$pageSource = new YellowPage($this->yellow);
- $pageSource->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $pageSource->parseData(($rawDataSource), false, 0);
- $this->editContentFile($page, "edit");
+ $pageSource->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $pageSource->parseMeta($rawDataSource);
+ $this->editContentFile($page, "edit", $this->userEmail);
if ($this->isMetaModified($pageSource, $page) && $page->location!=$this->yellow->content->getHomeLocation($page->location)) {
- $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("pageNewLocation"), true);
+ $page->location = $this->getPageNewLocation($page->rawData, $page->location, $page->get("editNewLocation"), true);
$page->fileName = $this->getPageNewFile($page->location, $page->fileName, $page->get("published"));
- if ($page->location!=$pageSource->location && ($this->yellow->content->find($page->location) || empty($page->fileName))) {
+ if ($page->location!=$pageSource->location && ($this->yellow->content->find($page->location) || is_string_empty($page->fileName))) {
$page->error(500, "Page '".$page->get("title")."' is not possible!");
}
}
- if (empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
+ if (is_string_empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
if (!$this->isUserAccess("edit", $page->location) ||
!$this->isUserAccess("edit", $pageSource->location)) {
$page->error(500, "Page '".$page->get("title")."' is restricted!");
@@ -1080,48 +1132,61 @@ class YellowEditResponse {
// Return deleted page
public function getPageDelete($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
- $rawData = $this->yellow->toolbox->normaliseLines($rawData, $endOfLine);
+ $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
$page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $page->parseData($rawData, false, 0);
- $this->editContentFile($page, "delete");
+ $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $page->parseMeta($rawData);
+ $this->editContentFile($page, "delete", $this->userEmail);
if (!$this->isUserAccess("delete", $page->location)) {
$page->error(500, "Page '".$page->get("title")."' is restricted!");
}
return $page;
}
+ // Return restored page
+ public function getPageRestore($scheme, $address, $base, $location, $fileName) {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $page->parseMeta("");
+ $this->editContentFile($page, "restore", $this->userEmail);
+ if (!$this->isUserAccess("restore", $page->location)) {
+ $page->error(500, "Page '".$page->get("title")."' is restricted!");
+ }
+ return $page;
+ }
+
// Return preview page
public function getPagePreview($scheme, $address, $base, $location, $fileName, $rawData, $endOfLine) {
- $rawData = $this->yellow->toolbox->normaliseLines($rawData, $endOfLine);
+ $rawData = $this->yellow->lookup->normaliseLines($rawData, $endOfLine);
$page = new YellowPage($this->yellow);
- $page->setRequestInformation($scheme, $address, $base, $location, $fileName);
- $page->parseData($rawData, false, 200);
- $this->yellow->text->setLanguage($page->get("language"));
+ $page->setRequestInformation($scheme, $address, $base, $location, $fileName, false);
+ $page->parseMeta($rawData, 200);
+ $this->yellow->language->set($page->get("language"));
$class = "page-preview layout-".$page->get("layout");
$output = "<div class=\"".htmlspecialchars($class)."\"><div class=\"content\"><div class=\"main\">";
if ($this->yellow->system->get("editToolbarButtons")!="none") $output .= "<h1>".$page->getHtml("titleContent")."</h1>\n";
- $output .= $page->getContent();
+ $output .= $page->getContentHtml();
$output .= "</div></div></div>";
- $page->setOutput($output);
+ $page->statusCode = 200;
+ $page->outputData = $output;
return $page;
}
// Return uploaded file
public function getFileUpload($scheme, $address, $base, $pageLocation, $fileNameTemp, $fileNameShort) {
$file = new YellowPage($this->yellow);
- $file->setRequestInformation($scheme, $address, $base, "/".$fileNameTemp, $fileNameTemp);
- $file->parseData(null, false, 0);
+ $file->setRequestInformation($scheme, $address, $base, "/".$fileNameTemp, $fileNameTemp, false);
+ $file->parseMeta(null);
$file->set("fileNameShort", $fileNameShort);
$file->set("type", $this->yellow->toolbox->getFileType($fileNameShort));
if ($file->get("type")=="html" || $file->get("type")=="svg") {
$fileData = $this->yellow->toolbox->readFile($fileNameTemp);
- $fileData = $this->yellow->toolbox->normaliseData($fileData, $file->get("type"));
- if (empty($fileData) || !$this->yellow->toolbox->createFile($fileNameTemp, $fileData)) {
+ $fileData = $this->yellow->lookup->normaliseData($fileData, $file->get("type"));
+ if (is_string_empty($fileData) || !$this->yellow->toolbox->createFile($fileNameTemp, $fileData)) {
$file->error(500, "Can't write file '$fileNameTemp'!");
}
}
- $this->editMediaFile($file, "upload");
+ $this->editMediaFile($file, "upload", $this->userEmail);
$file->location = $this->getFileNewLocation($fileNameShort, $pageLocation, $file->get("fileNewLocation"));
$file->fileName = substru($file->location, 1);
while (is_file($file->fileName)) {
@@ -1135,18 +1200,22 @@ class YellowEditResponse {
}
// Return system file
- public function getFileSystem($scheme, $address, $base, $pageLocation, $fileName, $settings) {
+ public function getFileSystem($scheme, $address, $base, $pageLocation, $fileNameSystem, $settings) {
$file = new YellowPage($this->yellow);
- $file->setRequestInformation($scheme, $address, $base, "/".$fileName, $fileName);
- $file->parseData(null, false, 0);
+ $file->setRequestInformation($scheme, $address, $base, "/".$fileNameSystem, $fileNameSystem, false);
+ $file->parseMeta(null);
foreach ($settings as $key=>$value) $file->set($key, $value);
- $this->editSystemFile($file, "system");
+ $this->editSystemFile($file, "configure", $this->userEmail);
return $file;
}
// Return page data including status information
public function getPageData($page) {
$data = array();
+ $data["scheme"] = $this->yellow->page->scheme;
+ $data["address"] = $this->yellow->page->address;
+ $data["base"] = $this->yellow->page->base;
+ $data["location"] = $this->yellow->page->location;
if ($this->isUser()) {
$data["title"] = $this->yellow->toolbox->getMetaData($this->rawDataEdit, "title");
$data["rawDataSource"] = $this->rawDataSource;
@@ -1155,10 +1224,6 @@ class YellowEditResponse {
$data["rawDataOutput"] = strval($this->rawDataOutput);
$data["rawDataReadonly"] = intval($this->rawDataReadonly);
$data["rawDataEndOfLine"] = $this->rawDataEndOfLine;
- $data["scheme"] = $this->yellow->page->scheme;
- $data["address"] = $this->yellow->page->address;
- $data["base"] = $this->yellow->page->base;
- $data["location"] = $this->yellow->page->location;
}
if ($this->action!="none") $data = array_merge($data, $this->getRequestData());
$data["action"] = $this->action;
@@ -1167,37 +1232,33 @@ class YellowEditResponse {
return $data;
}
- // Return system data including user information
+ // Return system data
public function getSystemData() {
- $data = $this->yellow->system->getData("", "Location");
+ $data = array();
+ $data["coreServerScheme"] = $this->yellow->system->get("coreServerScheme");
+ $data["coreServerAddress"] = $this->yellow->system->get("coreServerAddress");
+ $data["coreServerBase"] = $this->yellow->system->get("coreServerBase");
+ $data["coreDebugMode"] = $this->yellow->system->get("coreDebugMode");
+ $data = array_merge($data, $this->yellow->system->getSettings("", "Location"));
if ($this->isUser()) {
- $data["userEmail"] = $this->userEmail;
- $data["userName"] = $this->extension->users->getUser($this->userEmail, "name");
- $data["userLanguage"] = $this->extension->users->getUser($this->userEmail, "language");
- $data["userStatus"] = $this->extension->users->getUser($this->userEmail, "status");
- $data["userHome"] = $this->extension->users->getUser($this->userEmail, "home");
- $data["userAccess"] = $this->extension->users->getUser($this->userEmail, "access");
- $data["coreServerScheme"] = $this->yellow->system->get("coreServerScheme");
- $data["coreServerAddress"] = $this->yellow->system->get("coreServerAddress");
- $data["coreServerBase"] = $this->yellow->system->get("coreServerBase");
$data["coreFileSizeMax"] = $this->yellow->toolbox->getNumberBytes(ini_get("upload_max_filesize"));
- $data["coreVersion"] = "Datenstrom Yellow ".YellowCore::VERSION;
+ $data["coreProductRelease"] = "Datenstrom Yellow ".YellowCore::RELEASE;
$data["coreExtensions"] = array();
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- $data["coreExtensions"][$key] = $value["type"];
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ $data["coreExtensions"][$key] = $value["class"];
}
$data["coreLanguages"] = array();
- foreach ($this->yellow->text->getLanguages() as $language) {
- $data["coreLanguages"][$language] = $this->yellow->text->getTextHtml("languageDescription", $language);
+ foreach ($this->yellow->system->getAvailable("language") as $language) {
+ $data["coreLanguages"][$language] = $this->yellow->language->getTextHtml("languageDescription", $language);
}
$data["editSettingsActions"] = $this->getSettingsActions();
$data["editUploadExtensions"] = $this->yellow->system->get("editUploadExtensions");
$data["editKeyboardShortcuts"] = $this->yellow->system->get("editKeyboardShortcuts");
$data["editToolbarButtons"] = $this->getToolbarButtons();
$data["editStatusValues"] = $this->getStatusValues();
- $data["emojiawesomeToolbarButtons"] = $this->yellow->system->get("emojiawesomeToolbarButtons");
- $data["fontawesomeToolbarButtons"] = $this->yellow->system->get("fontawesomeToolbarButtons");
- if ($this->isUserAccess("system")) {
+ $data["emojiToolbarButtons"] = $this->yellow->system->get("emojiToolbarButtons");
+ $data["iconToolbarButtons"] = $this->yellow->system->get("iconToolbarButtons");
+ if ($this->isUserAccess("configure")) {
$data["sitename"] = $this->yellow->system->get("sitename");
$data["author"] = $this->yellow->system->get("author");
$data["email"] = $this->yellow->system->get("email");
@@ -1207,11 +1268,32 @@ class YellowEditResponse {
$data["editLoginPassword"] = $this->yellow->page->get("editLoginPassword");
$data["editLoginRestriction"] = intval($this->isLoginRestriction());
}
- if (defined("DEBUG") && DEBUG>=1) $data["debug"] = DEBUG;
return $data;
}
- // Return request strings
+ // Return user data
+ public function getUserData() {
+ $data = array();
+ if ($this->isUser()) {
+ $data["email"] = $this->userEmail;
+ $data["name"] = $this->yellow->user->getUser("name", $this->userEmail);
+ $data["description"] = $this->yellow->user->getUser("description", $this->userEmail);
+ $data["language"] = $this->yellow->user->getUser("language", $this->userEmail);
+ $data["status"] = $this->yellow->user->getUser("status", $this->userEmail);
+ $data["access"] = $this->yellow->user->getUser("access", $this->userEmail);
+ $data["home"] = $this->yellow->user->getUser("home", $this->userEmail);
+ }
+ return $data;
+ }
+
+ // Return language data
+ public function getLanguageData() {
+ $dataLanguage = $this->yellow->language->getSettings("language", "", $this->language);
+ $dataEdit = $this->yellow->language->getSettings("edit", "", $this->language);
+ return array_merge($dataLanguage, $dataEdit);
+ }
+
+ // Return request data
public function getRequestData() {
$data = array();
foreach ($_REQUEST as $key=>$value) {
@@ -1221,29 +1303,21 @@ class YellowEditResponse {
return $data;
}
- // Return text strings
- public function getTextData() {
- $textLanguage = $this->yellow->text->getData("language", $this->language);
- $textEdit = $this->yellow->text->getData("edit", $this->language);
- return array_merge($textLanguage, $textEdit);
- }
-
// Return settings actions
public function getSettingsActions() {
$settingsActions = "account";
- if ($this->isUserAccess("system")) $settingsActions .= ", system";
+ if ($this->isUserAccess("configure")) $settingsActions .= ", configure";
if ($this->isUserAccess("update")) $settingsActions .= ", update";
- return $settingsActions=="account" ? "" : $settingsActions;
+ return $settingsActions=="account" ? "none" : $settingsActions;
}
// Return toolbar buttons
public function getToolbarButtons() {
$toolbarButtons = $this->yellow->system->get("editToolbarButtons");
if ($toolbarButtons=="auto") {
- $toolbarButtons = "";
- if ($this->yellow->extensions->isExisting("markdown")) $toolbarButtons = "format, bold, italic, strikethrough, code, separator, list, link, file";
- if ($this->yellow->extensions->isExisting("emojiawesome")) $toolbarButtons .= ", emojiawesome";
- if ($this->yellow->extensions->isExisting("fontawesome")) $toolbarButtons .= ", fontawesome";
+ $toolbarButtons = "format, bold, italic, strikethrough, code, separator, list, link, file";
+ if ($this->yellow->extension->isExisting("emoji")) $toolbarButtons .= ", emoji";
+ if ($this->yellow->extension->isExisting("icon")) $toolbarButtons .= ", icon";
$toolbarButtons .= ", status, preview";
}
return $toolbarButtons;
@@ -1252,8 +1326,8 @@ class YellowEditResponse {
// Return status values
public function getStatusValues() {
$statusValues = "";
- if ($this->yellow->extensions->isExisting("private")) $statusValues .= ", private";
- if ($this->yellow->extensions->isExisting("draft")) $statusValues .= ", draft";
+ if ($this->yellow->extension->isExisting("private")) $statusValues .= ", private";
+ if ($this->yellow->extension->isExisting("draft")) $statusValues .= ", draft";
$statusValues .= ", unlisted";
return ltrim($statusValues, ", ");
}
@@ -1262,7 +1336,7 @@ class YellowEditResponse {
public function getEndOfLine($rawData = "") {
$endOfLine = $this->yellow->system->get("editEndOfLine");
if ($endOfLine=="auto") {
- $rawData = empty($rawData) ? PHP_EOL : substru($rawData, 0, 4096);
+ $rawData = is_string_empty($rawData) ? PHP_EOL : substru($rawData, 0, 4096);
$endOfLine = strposu($rawData, "\r")===false ? "lf" : "crlf";
}
return $endOfLine;
@@ -1271,35 +1345,29 @@ class YellowEditResponse {
// Return update information
public function getUpdateInformation() {
$statusCode = 200;
- $updates = 0;
$rawData = "";
- if ($this->yellow->extensions->isExisting("update")) {
- list($statusCodeCurrent, $dataCurrent) = $this->yellow->extensions->get("update")->getExtensionsVersion();
- list($statusCodeLatest, $dataLatest) = $this->yellow->extensions->get("update")->getExtensionsVersion(true);
- list($statusCodeModified, $dataModified) = $this->yellow->extensions->get("update")->getExtensionsModified();
- $statusCode = max($statusCodeCurrent, $statusCodeLatest, $statusCodeModified);
- foreach ($dataCurrent as $key=>$value) {
- if (strnatcasecmp($dataCurrent[$key], $dataLatest[$key])<0) {
- $rawData .= htmlspecialchars(ucfirst($key)." $dataLatest[$key]")."<br />\n";
- ++$updates;
- }
- }
- if ($updates==0) {
- foreach ($dataCurrent as $key=>$value) {
- if (isset($dataModified[$key]) && isset($dataLatest[$key])) {
- $output = $this->yellow->text->getTextHtml("editUpdateModified", $this->language)." - <a href=\"#\" data-action=\"submit\" data-arguments=\"".$this->yellow->toolbox->normaliseArguments("action:update/extension:$key/option:force")."\">".$this->yellow->text->getTextHtml("editUpdateForce", $this->language)."</a><br />\n";
- $rawData .= preg_replace("/@extension/i", htmlspecialchars(ucfirst($key)." $dataLatest[$key]"), $output);
+ if ($this->yellow->extension->isExisting("update")) {
+ list($statusCodeCurrent, $settingsCurrent) = $this->yellow->extension->get("update")->getExtensionSettings(false);
+ list($statusCodeLatest, $settingsLatest) = $this->yellow->extension->get("update")->getExtensionSettings(true);
+ $statusCode = max($statusCodeCurrent, $statusCodeLatest);
+ foreach ($settingsCurrent as $key=>$value) {
+ if ($settingsLatest->isExisting($key)) {
+ $versionCurrent = $settingsCurrent[$key]->get("version");
+ $versionLatest = $settingsLatest[$key]->get("version");
+ if (strnatcasecmp($versionCurrent, $versionLatest)<0) {
+ $rawData .= htmlspecialchars(ucfirst($key)." $versionLatest")."<br />";
}
}
}
+ if (!is_string_empty($rawData)) $rawData = "<p>$rawData</p>\n";
}
- return array($statusCode, $updates, $rawData);
+ return array($statusCode, $rawData);
}
// Return raw data for generated page
public function getRawDataGenerated($page) {
$title = $page->get("title");
- $text = $this->yellow->text->getText("editDataGenerated", $page->get("language"));
+ $text = $this->yellow->language->getText("editDataGenerated", $page->get("language"));
return "---\nTitle: $title\n---\n$text";
}
@@ -1309,26 +1377,26 @@ class YellowEditResponse {
foreach ($this->yellow->content->path($page->location)->reverse() as $ancestor) {
if ($ancestor->isExisting("layoutNew")) {
$name = $this->yellow->lookup->normaliseName($ancestor->get("layoutNew"));
- $location = $this->yellow->content->getHomeLocation($page->location).$this->yellow->system->get("coreContentSharedDirectory");
- $fileName = $this->yellow->lookup->findFileFromLocation($location, true).$this->yellow->system->get("editNewFile");
- $fileName = strreplaceu("(.*)", $name, $fileName);
+ $location = $this->yellow->content->getHomeLocation($page->location)."shared/";
+ $fileName = $this->yellow->lookup->findFileFromContentLocation($location, true).$this->yellow->system->get("editNewFile");
+ $fileName = str_replace("(.*)", $name, $fileName);
if (is_file($fileName)) break;
}
}
if (!is_file($fileName)) {
$name = $this->yellow->lookup->normaliseName($this->yellow->system->get("layout"));
- $location = $this->yellow->content->getHomeLocation($page->location).$this->yellow->system->get("coreContentSharedDirectory");
- $fileName = $this->yellow->lookup->findFileFromLocation($location, true).$this->yellow->system->get("editNewFile");
- $fileName = strreplaceu("(.*)", $name, $fileName);
+ $location = $this->yellow->content->getHomeLocation($page->location)."shared/";
+ $fileName = $this->yellow->lookup->findFileFromContentLocation($location, true).$this->yellow->system->get("editNewFile");
+ $fileName = str_replace("(.*)", $name, $fileName);
}
if (is_file($fileName)) {
$rawData = $this->yellow->toolbox->readFile($fileName);
$rawData = preg_replace("/@timestamp/i", time(), $rawData);
$rawData = preg_replace("/@datetime/i", date("Y-m-d H:i:s"), $rawData);
$rawData = preg_replace("/@date/i", date("Y-m-d"), $rawData);
- $rawData = preg_replace("/@usershort/i", strtok($this->extension->users->getUser($this->userEmail, "name"), " "), $rawData);
- $rawData = preg_replace("/@username/i", $this->extension->users->getUser($this->userEmail, "name"), $rawData);
- $rawData = preg_replace("/@userlanguage/i", $this->extension->users->getUser($this->userEmail, "language"), $rawData);
+ $rawData = preg_replace("/@usershort/i", strtok($this->yellow->user->getUser("name", $this->userEmail), " "), $rawData);
+ $rawData = preg_replace("/@username/i", $this->yellow->user->getUser("name", $this->userEmail), $rawData);
+ $rawData = preg_replace("/@userlanguage/i", $this->yellow->user->getUser("language", $this->userEmail), $rawData);
} else {
$rawData = "---\nTitle: Page\n---\n";
}
@@ -1340,16 +1408,16 @@ class YellowEditResponse {
}
// Return location for new/modified page
- public function getPageNewLocation($rawData, $pageLocation, $pageNewLocation, $pageMatchLocation = false) {
- $location = empty($pageNewLocation) ? "@title" : $pageNewLocation;
+ public function getPageNewLocation($rawData, $pageLocation, $editNewLocation, $pageMatchLocation = false) {
+ $location = is_string_empty($editNewLocation) ? "@title" : $editNewLocation;
$location = preg_replace("/@title/i", $this->getPageNewTitle($rawData), $location);
- $location = preg_replace("/@timestamp/i", $this->getPageNewData($rawData, "published", true, "U"), $location);
- $location = preg_replace("/@date/i", $this->getPageNewData($rawData, "published", true, "Y-m-d"), $location);
- $location = preg_replace("/@year/i", $this->getPageNewData($rawData, "published", true, "Y"), $location);
- $location = preg_replace("/@month/i", $this->getPageNewData($rawData, "published", true, "m"), $location);
- $location = preg_replace("/@day/i", $this->getPageNewData($rawData, "published", true, "d"), $location);
- $location = preg_replace("/@tag/i", $this->getPageNewData($rawData, "tag", true), $location);
- $location = preg_replace("/@author/i", $this->getPageNewData($rawData, "author", true), $location);
+ $location = preg_replace("/@timestamp/i", $this->getPageNewData($rawData, "published", "U"), $location);
+ $location = preg_replace("/@date/i", $this->getPageNewData($rawData, "published", "Y-m-d"), $location);
+ $location = preg_replace("/@year/i", $this->getPageNewData($rawData, "published", "Y"), $location);
+ $location = preg_replace("/@month/i", $this->getPageNewData($rawData, "published", "m"), $location);
+ $location = preg_replace("/@day/i", $this->getPageNewData($rawData, "published", "d"), $location);
+ $location = preg_replace("/@tag/i", $this->getPageNewData($rawData, "tag"), $location);
+ $location = preg_replace("/@author/i", $this->getPageNewData($rawData, "author"), $location);
if (!preg_match("/^\//", $location)) {
if ($this->yellow->lookup->isFileLocation($pageLocation) || !$pageMatchLocation) {
$location = $this->yellow->lookup->getDirectoryLocation($pageLocation).$location;
@@ -1357,6 +1425,14 @@ class YellowEditResponse {
$location = $this->yellow->lookup->getDirectoryLocation(rtrim($pageLocation, "/")).$location;
}
}
+ if (preg_match("/\d/", $location)) {
+ $locationNew = "";
+ $tokens = explode("/", $location);
+ for ($i=1; $i<count($tokens); ++$i) {
+ $locationNew .= "/".$this->yellow->lookup->normaliseToken($tokens[$i]);
+ }
+ $location = $locationNew;
+ }
if ($pageMatchLocation) {
$location = rtrim($location, "/").($this->yellow->lookup->isFileLocation($pageLocation) ? "" : "/");
}
@@ -1367,25 +1443,25 @@ class YellowEditResponse {
public function getPageNewTitle($rawData) {
$title = $this->yellow->toolbox->getMetaData($rawData, "title");
$titleSlug = $this->yellow->toolbox->getMetaData($rawData, "titleSlug");
- $value = empty($titleSlug) ? $title : $titleSlug;
- $value = $this->yellow->lookup->normaliseName($value, true, false, true);
+ $value = is_string_empty($titleSlug) ? $title : $titleSlug;
+ $value = $this->yellow->lookup->normaliseName($value, false, false, true);
return trim(preg_replace("/-+/", "-", $value), "-");
}
// Return data for new/modified page
- public function getPageNewData($rawData, $key, $filterFirst = false, $dateFormat = "") {
+ public function getPageNewData($rawData, $key, $dateFormat = "") {
$value = $this->yellow->toolbox->getMetaData($rawData, $key);
- if ($filterFirst && preg_match("/^(.*?)\,(.*)$/", $value, $matches)) $value = $matches[1];
- if (!empty($dateFormat)) $value = date($dateFormat, strtotime($value));
- if (strempty($value)) $value = "none";
- $value = $this->yellow->lookup->normaliseName($value, true, false, true);
+ if (preg_match("/^(.*?)\,(.*)$/", $value, $matches)) $value = $matches[1];
+ if (!is_string_empty($dateFormat)) $value = date($dateFormat, strtotime($value));
+ if (is_string_empty($value)) $value = "none";
+ $value = $this->yellow->lookup->normaliseName($value, false, false, true);
return trim(preg_replace("/-+/", "-", $value), "-");
}
// Return file name for new/modified page
public function getPageNewFile($location, $pageFileName = "", $pagePrefix = "") {
- $fileName = $this->yellow->lookup->findFileFromLocation($location);
- if (!empty($fileName)) {
+ $fileName = $this->yellow->lookup->findFileFromContentLocation($location);
+ if (!is_string_empty($fileName)) {
if (!is_dir(dirname($fileName))) {
$path = "";
$tokens = explode("/", $fileName);
@@ -1404,7 +1480,7 @@ class YellowEditResponse {
$path .= $tokens[$i]."/";
}
$fileName = $path.$tokens[$i];
- $pageFileName = empty($pageFileName) ? $fileName : $pageFileName;
+ $pageFileName = is_string_empty($pageFileName) ? $fileName : $pageFileName;
}
$prefix = $this->getPageNewPrefix($location, $pageFileName, $pagePrefix);
if ($this->yellow->lookup->isFileLocation($location)) {
@@ -1428,7 +1504,7 @@ class YellowEditResponse {
// Return prefix for new/modified page
public function getPageNewPrefix($location, $pageFileName, $pagePrefix) {
- if (empty($pagePrefix)) {
+ if (is_string_empty($pagePrefix)) {
if ($this->yellow->lookup->isFileLocation($location)) {
if (preg_match("#^(.*)\/(.+?)$#", $pageFileName, $matches)) $pagePrefix = $matches[2];
} else {
@@ -1440,8 +1516,9 @@ class YellowEditResponse {
// Return location for new file
public function getFileNewLocation($fileNameShort, $pageLocation, $fileNewLocation) {
- $location = empty($fileNewLocation) ? $this->yellow->system->get("editUploadNewLocation") : $fileNewLocation;
+ $location = is_string_empty($fileNewLocation) ? $this->yellow->system->get("editUploadNewLocation") : $fileNewLocation;
$location = preg_replace("/@timestamp/i", time(), $location);
+ $location = preg_replace("/@date/i", date("Y-m-d"), $location);
$location = preg_replace("/@type/i", $this->yellow->toolbox->getFileType($fileNameShort), $location);
$location = preg_replace("/@group/i", $this->getFileNewGroup($fileNameShort), $location);
$location = preg_replace("/@folder/i", $this->getFileNewFolder($pageLocation), $location);
@@ -1455,10 +1532,15 @@ class YellowEditResponse {
// Return group for new file
public function getFileNewGroup($fileNameShort) {
$group = "none";
- $path = $this->yellow->system->get("coreMediaDirectory");
$fileType = $this->yellow->toolbox->getFileType($fileNameShort);
- $fileName = $this->yellow->system->get(preg_match("/(gif|jpg|png|svg)$/", $fileType) ? "coreImageDirectory" : "coreDownloadDirectory").$fileNameShort;
- if (preg_match("#^$path(.+?)\/#", $fileName, $matches)) $group = strtoloweru($matches[1]);
+ $locationMedia = $this->yellow->system->get("coreMediaLocation");
+ $locationGroup = $this->yellow->system->get("coreDownloadLocation");
+ if (preg_match("/(gif|jpg|png|svg)$/", $fileType)) {
+ $locationGroup = $this->yellow->system->get("coreImageLocation");
+ }
+ if (preg_match("#^$locationMedia(.+?)\/#", $locationGroup, $matches)) {
+ $group = strtoloweru($matches[1]);
+ }
return $group;
}
@@ -1474,7 +1556,7 @@ class YellowEditResponse {
$fileText = $fileNumber = $fileExtension = "";
if (preg_match("/^(.*?)(\d*)(\..*?)?$/", $fileNameShort, $matches)) {
$fileText = $matches[1];
- $fileNumber = strempty($matches[2]) ? "-2" : $matches[2]+1;
+ $fileNumber = is_string_empty($matches[2]) ? "-2" : $matches[2]+1;
$fileExtension = $matches[3];
}
return $fileText.$fileNumber.$fileExtension;
@@ -1483,9 +1565,9 @@ class YellowEditResponse {
// Return next title
public function getTitleNext($rawData) {
$titleText = $titleNumber = "";
- if(preg_match("/^(.*?)(\d*)$/", $this->yellow->toolbox->getMetaData($rawData, "title"), $matches)) {
+ if (preg_match("/^(.*?)(\d*)$/", $this->yellow->toolbox->getMetaData($rawData, "title"), $matches)) {
$titleText = $matches[1];
- $titleNumber = strempty($matches[2]) ? " 2" : $matches[2]+1;
+ $titleNumber = is_string_empty($matches[2]) ? " 2" : $matches[2]+1;
}
return $titleText.$titleNumber;
}
@@ -1497,7 +1579,7 @@ class YellowEditResponse {
$userEmail = $this->yellow->system->get("email");
$userLanguage = $this->extension->getUserLanguage($userEmail);
} else {
- $userName = $this->extension->users->getUser($email, "name");
+ $userName = $this->yellow->user->getUser("name", $email);
$userEmail = $email;
$userLanguage = $this->extension->getUserLanguage($email);
}
@@ -1505,248 +1587,63 @@ class YellowEditResponse {
$url = "$scheme://$address$base/";
} else {
$expire = time() + 60*60*24;
- $actionToken = $this->extension->users->createActionToken($email, $action, $expire);
- $url = "$scheme://$address$base"."/action:$action/email:$email/expire:$expire/language:$userLanguage/actiontoken:$actionToken/";
+ $actionToken = $this->createActionToken($email, $action, $expire);
+ $locationArguments = "/action:$action/email:$email/expire:$expire/language:$userLanguage/actiontoken:$actionToken/";
+ $url = "$scheme://$address$base".$this->yellow->lookup->normaliseArguments($locationArguments, false, false);
}
$prefix = "edit".ucfirst($action);
- $message = $this->yellow->text->getText("{$prefix}Message", $userLanguage);
- $message = strreplaceu("\\n", "\r\n", $message);
+ $message = $this->yellow->language->getText("{$prefix}Message", $userLanguage);
+ $message = str_replace("\\n", "\r\n", $message);
$message = preg_replace("/@useraccount/i", $email, $message);
$message = preg_replace("/@usershort/i", strtok($userName, " "), $message);
$message = preg_replace("/@username/i", $userName, $message);
$message = preg_replace("/@userlanguage/i", $userLanguage, $message);
$sitename = $this->yellow->system->get("sitename");
- $footer = $this->yellow->text->getText("editMailFooter", $userLanguage);
- $footer = strreplaceu("\\n", "\r\n", $footer);
+ $siteEmail = $this->yellow->system->get("editSiteEmail");
+ $subject = $this->yellow->language->getText("{$prefix}Subject", $userLanguage);
+ $footer = $this->yellow->language->getText("editMailFooter", $userLanguage);
+ $footer = str_replace("\\n", "\r\n", $footer);
$footer = preg_replace("/@sitename/i", $sitename, $footer);
- $mailTo = mb_encode_mimeheader("$userName")." <$userEmail>";
- $mailSubject = mb_encode_mimeheader($this->yellow->text->getText("{$prefix}Subject", $userLanguage));
- $mailHeaders = mb_encode_mimeheader("From: $sitename")." <noreply>\r\n";
- $mailHeaders .= mb_encode_mimeheader("X-Request-Url: $scheme://$address$base")."\r\n";
- $mailHeaders .= "Mime-Version: 1.0\r\n";
- $mailHeaders .= "Content-Type: text/plain; charset=utf-8\r\n";
+ $mailHeaders = array(
+ "To" => "$userName <$userEmail>",
+ "From" => "$sitename <$siteEmail>",
+ "Subject" => $subject,
+ "Date" => date(DATE_RFC2822),
+ "Mime-Version" => "1.0",
+ "Content-Type" => "text/plain; charset=utf-8",
+ "X-Request-Url" => "$scheme://$address$base");
$mailMessage = "$message\r\n\r\n$url\r\n-- \r\n$footer";
- return mail($mailTo, $mailSubject, $mailMessage, $mailHeaders);
+ return $this->yellow->toolbox->mail($action, $mailHeaders, $mailMessage);
}
// Create browser cookies
public function createCookies($scheme, $address, $base, $email) {
$expire = time() + $this->yellow->system->get("editLoginSessionTimeout");
- $authToken = $this->extension->users->createAuthToken($email, $expire);
- $csrfToken = $this->extension->users->createCsrfToken();
+ $authToken = $this->createAuthToken($email, $expire);
+ $csrfToken = $this->createCsrfToken();
setcookie("authtoken", $authToken, $expire, "$base/", "", $scheme=="https", true);
setcookie("csrftoken", $csrfToken, $expire, "$base/", "", $scheme=="https", false);
}
// Destroy browser cookies
public function destroyCookies($scheme, $address, $base) {
- setcookie("authtoken", "", 1, "$base/", "", $scheme=="https", true);
- setcookie("csrftoken", "", 1, "$base/", "", $scheme=="https", false);
- }
-
- // Change content file
- public function editContentFile($page, $action) {
- if (!$page->isError()) {
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onEditContentFile")) $value["obj"]->onEditContentFile($page, $action);
- }
- }
- }
-
- // Change media file
- public function editMediaFile($file, $action) {
- if (!$file->isError()) {
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onEditMediaFile")) $value["obj"]->onEditMediaFile($file, $action);
- }
- }
- }
-
- // Change system file
- public function editSystemFile($file, $action) {
- if (!$file->isError()) {
- foreach ($this->yellow->extensions->extensions as $key=>$value) {
- if (method_exists($value["obj"], "onEditSystemFile")) $value["obj"]->onEditSystemFile($file, $action);
- }
- }
- }
-
- // Check if meta data has been modified
- public function isMetaModified($pageSource, $pageOther) {
- return substrb($pageSource->rawData, 0, $pageSource->metaDataOffsetBytes) !=
- substrb($pageOther->rawData, 0, $pageOther->metaDataOffsetBytes);
- }
-
- // Check if active
- public function isActive() {
- return $this->active;
- }
-
- // Check if user is logged in
- public function isUser() {
- return !empty($this->userEmail);
- }
-
- // Check if user with access
- public function isUserAccess($action, $location = "") {
- $userHome = $this->extension->users->getUser($this->userEmail, "home");
- $userAccess = preg_split("/\s*,\s*/", $this->extension->users->getUser($this->userEmail, "access"));
- return in_array($action, $userAccess) && (empty($location) || substru($location, 0, strlenu($userHome))==$userHome);
- }
-
- // Check if login with restriction
- public function isLoginRestriction() {
- return $this->yellow->system->get("editLoginRestriction");
- }
-}
-
-class YellowEditUsers {
- public $yellow; //access to API
- public $users; //registered users
-
- public function __construct($yellow) {
- $this->yellow = $yellow;
- $this->users = array();
- }
-
- // Load users from file
- public function load($fileName) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowEditUsers::load file:$fileName<br/>\n";
- $fileData = $this->yellow->toolbox->readFile($fileName);
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\#/", $line)) continue;
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="email" && !strempty($matches[2])) {
- $email = $matches[2];
- if (defined("DEBUG") && DEBUG>=3) echo "YellowEditUsers::load email:$email<br/>\n";
- }
- if (!empty($email) && !empty($matches[1]) && !strempty($matches[2])) {
- $this->setUser($email, $matches[1], $matches[2]);
- }
- }
- }
- }
-
- // Save user to file
- public function save($fileName, $email, $settings) {
- $scan = false;
- $fileData = $this->yellow->toolbox->readFile($fileName);
- $fileDataStart = $fileDataMiddle = $fileDataEnd = "";
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="email" && !strempty($matches[2])) {
- $scan = $matches[2]==$email;
- }
- }
- if (!$scan && empty($fileDataMiddle)) {
- $fileDataStart .= $line;
- } elseif ($scan) {
- $fileDataMiddle .= $line;
- } else {
- $fileDataEnd .= $line;
- }
- }
- $settingsNew = new YellowDataCollection();
- $settingsNew["email"] = $email;
- foreach ($settings as $key=>$value) {
- if (!empty($key) && !strempty($value)) {
- $this->setUser($email, $key, $value);
- $settingsNew[$key] = $value;
- }
- }
- $fileDataSettings = "";
- foreach ($this->yellow->toolbox->getTextLines($fileDataMiddle) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (!empty($matches[1]) && isset($settingsNew[$matches[1]])) {
- $fileDataSettings .= "$matches[1]: ".$settingsNew[$matches[1]]."\n";
- unset($settingsNew[$matches[1]]);
- continue;
- }
- }
- $fileDataSettings .= $line;
- }
- foreach ($settingsNew as $key=>$value) {
- $fileDataSettings .= ucfirst($key).": $value\n";
- }
- if (!empty($fileDataSettings)) {
- $fileDataSettings = preg_replace("/\n+/", "\n", $fileDataSettings);
- if (!empty($fileDataStart) && substr($fileDataStart, -2)!="\n\n") $fileDataSettings = "\n".$fileDataSettings;
- if (!empty($fileDataEnd)) $fileDataSettings .= "\n";
- }
- $fileDataNew = $fileDataStart.$fileDataSettings.$fileDataEnd;
- return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
- }
-
- // Remove user from file
- public function remove($fileName, $email) {
- $scan = false;
- $fileData = $this->yellow->toolbox->readFile($fileName);
- $fileDataStart = $fileDataMiddle = $fileDataEnd = "";
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="email" && !strempty($matches[2])) {
- $scan = $matches[2]==$email;
- }
- }
- if (!$scan && empty($fileDataMiddle)) {
- $fileDataStart .= $line;
- } elseif ($scan) {
- $fileDataMiddle .= $line;
- } else {
- $fileDataEnd .= $line;
- }
- }
- if (isset($this->users[$email])) unset($this->users[$email]);
- $fileDataNew = rtrim($fileDataStart.$fileDataEnd)."\n";
- return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
- }
-
- // Set user setting
- public function setUser($email, $key, $value) {
- if (!isset($this->users[$email])) $this->users[$email] = new YellowDataCollection();
- $this->users[$email][$key] = $value;
- }
-
- // Return user setting
- public function getUser($email, $key) {
- return isset($this->users[$email]) && isset($this->users[$email][$key]) ? $this->users[$email][$key] : "";
- }
-
- // Check user authentication from email and password
- public function checkAuthLogin($email, $password) {
- $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
- return $this->isExisting($email) && $this->users[$email]["status"]=="active" &&
- $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]);
- }
-
- // Check user authentication from tokens
- public function checkAuthToken($authToken, $csrfTokenExpected, $csrfTokenReceived, $csrfTokenIrrelevant) {
- $signature = "$5y$".substrb($authToken, 0, 96);
- $email = $this->getAuthEmail($authToken);
- $expire = $this->getAuthExpire($authToken);
- return $expire>time() && $this->isExisting($email) && $this->users[$email]["status"]=="active" &&
- $this->yellow->toolbox->verifyHash($this->users[$email]["hash"]."auth".$expire, "sha256", $signature) &&
- ($this->yellow->toolbox->verifyToken($csrfTokenExpected, $csrfTokenReceived) || $csrfTokenIrrelevant);
- }
-
- // Check action token
- public function checkActionToken($actionToken, $email, $action, $expire) {
- $signature = "$5y$".$actionToken;
- return $expire>time() && $this->isExisting($email) &&
- $this->yellow->toolbox->verifyHash($this->users[$email]["hash"].$action.$expire, "sha256", $signature);
+ setcookie("authtoken", "", 1, "$base/");
+ setcookie("csrftoken", "", 1, "$base/");
}
// Create authentication token
public function createAuthToken($email, $expire) {
- $signature = $this->yellow->toolbox->createHash($this->users[$email]["hash"]."auth".$expire, "sha256");
- if (empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
- return substrb($signature, 4).$this->getUser($email, "stamp").dechex($expire);
+ $hash = $this->yellow->user->getUser("hash", $email);
+ $signature = $this->yellow->toolbox->createHash($hash."auth".$expire, "sha256");
+ if (is_string_empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
+ return substrb($signature, 4).$this->yellow->user->getUser("stamp", $email).dechex($expire);
}
// Create action token
public function createActionToken($email, $action, $expire) {
- $signature = $this->yellow->toolbox->createHash($this->users[$email]["hash"].$action.$expire, "sha256");
- if (empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
+ $hash = $this->yellow->user->getUser("hash", $email);
+ $signature = $this->yellow->toolbox->createHash($hash.$action.$expire, "sha256");
+ if (is_string_empty($signature)) $signature = "padd"."error-hash-algorithm-sha256";
return substrb($signature, 4);
}
@@ -1760,7 +1657,7 @@ class YellowEditUsers {
$algorithm = $this->yellow->system->get("editUserHashAlgorithm");
$cost = $this->yellow->system->get("editUserHashCost");
$hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
- if (empty($hash)) $hash = "error-hash-algorithm-$algorithm";
+ if (is_string_empty($hash)) $hash = "error-hash-algorithm-$algorithm";
return $hash;
}
@@ -1773,11 +1670,38 @@ class YellowEditUsers {
return $stamp;
}
+ // Check user authentication from email and password
+ public function checkAuthLogin($email, $password) {
+ $algorithm = $this->yellow->system->get("editUserHashAlgorithm");
+ $hash = $this->yellow->user->getUser("hash", $email);
+ return $this->yellow->user->getUser("status", $email)=="active" &&
+ $this->yellow->toolbox->verifyHash($password, $algorithm, $hash);
+ }
+
+ // Check user authentication from tokens
+ public function checkAuthToken($authToken, $csrfTokenExpected, $csrfTokenReceived, $csrfTokenIrrelevant) {
+ $signature = "$5y$".substrb($authToken, 0, 96);
+ $email = $this->getAuthEmail($authToken);
+ $expire = $this->getAuthExpire($authToken);
+ $hash = $this->yellow->user->getUser("hash", $email);
+ return $expire>time() && $this->yellow->user->getUser("status", $email)=="active" &&
+ $this->yellow->toolbox->verifyHash($hash."auth".$expire, "sha256", $signature) &&
+ ($this->yellow->toolbox->verifyToken($csrfTokenExpected, $csrfTokenReceived) || $csrfTokenIrrelevant);
+ }
+
+ // Check action token
+ public function checkActionToken($actionToken, $email, $action, $expire) {
+ $signature = "$5y$".$actionToken;
+ $hash = $this->yellow->user->getUser("hash", $email);
+ return $expire>time() && $this->yellow->user->isExisting($email) &&
+ $this->yellow->toolbox->verifyHash($hash.$action.$expire, "sha256", $signature);
+ }
+
// Return user email from authentication, timing attack safe email lookup
public function getAuthEmail($authToken, $stamp = "") {
$email = "";
- if (empty($stamp)) $stamp = substrb($authToken, 96, 20);
- foreach ($this->users as $key=>$value) {
+ if (is_string_empty($stamp)) $stamp = substrb($authToken, 96, 20);
+ foreach ($this->yellow->user->settings as $key=>$value) {
if ($this->yellow->toolbox->verifyToken($value["stamp"], $stamp)) $email = $key;
}
return $email;
@@ -1788,43 +1712,150 @@ class YellowEditUsers {
return hexdec(substrb($authToken, 96+20));
}
- // Return number of users
- public function getNumber() {
- return count($this->users);
+ // Change content file
+ public function editContentFile($page, $action, $email) {
+ if (!$page->isError()) {
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onEditContentFile")) $value["object"]->onEditContentFile($page, $action, $email);
+ }
+ }
}
- // Return user data
- public function getData() {
- $data = array();
- foreach ($this->users as $key=>$value) {
- $name = $value["name"];
- if (preg_match("/\s/", $name)) $name = "\"$name\"";
- $data[$key] = "$value[email] $name $value[status]";
+ // Change media file
+ public function editMediaFile($file, $action, $email) {
+ if (!$file->isError()) {
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onEditMediaFile")) $value["object"]->onEditMediaFile($file, $action, $email);
+ }
}
- uksort($data, "strnatcasecmp");
- return $data;
}
- // Check if user is taken
- public function isTaken($email) {
- $taken = false;
- if ($this->isExisting($email)) {
- $status = $this->users[$email]["status"];
- $reserved = strtotime($this->users[$email]["modified"]) + 60*60*24;
- if ($status=="active" || $status=="inactive" || $reserved>time()) $taken = true;
+ // Change system file
+ public function editSystemFile($file, $action, $email) {
+ if (!$file->isError()) {
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onEditSystemFile")) $value["object"]->onEditSystemFile($file, $action, $email);
+ }
}
- return $taken;
}
- // Check if user exists
- public function isExisting($email) {
- return isset($this->users[$email]);
+ // Delete file
+ public function deleteFileLocation($location, $fileName) {
+ $rawData = $this->yellow->toolbox->readFile($fileName);
+ $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalLocation", $location);
+ $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalFileName", $fileName);
+ return $this->yellow->toolbox->createFile($fileName, $rawData) &&
+ $this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"));
+ }
+
+ // Delete directory
+ public function deleteDirectoryLocation($location, $fileName) {
+ $rawData = $this->yellow->toolbox->readFile($fileName);
+ $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalLocation", $location);
+ $rawData = $this->yellow->toolbox->setMetaData($rawData, "pageOriginalFileName", $fileName);
+ return $this->yellow->toolbox->createFile($fileName, $rawData) &&
+ $this->yellow->toolbox->deleteDirectory(dirname($fileName), $this->yellow->system->get("coreTrashDirectory"));
+ }
+
+ // Restore deleted file from trash
+ public function restoreFileLocation($location) {
+ $fileNameDeleted = $fileNameRestored = "";
+ $deleted = 0;
+ $pathTrash = $this->yellow->system->get("coreTrashDirectory");
+ $regex = "/^.*\\".$this->yellow->system->get("coreContentExtension")."$/";
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, $regex, false, false) as $entry) {
+ $rawDataOriginal = $this->yellow->toolbox->readFile($entry);
+ $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
+ $fileNameOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalFileName");
+ $deletedOriginal = $this->yellow->toolbox->getFileDeleted($entry);
+ if ($location==$locationOriginal && $deleted<=$deletedOriginal) {
+ $fileNameDeleted = $entry;
+ $fileNameRestored = $fileNameOriginal;
+ $rawDataRestored = $rawDataOriginal;
+ $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalLocation");
+ $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalFileName");
+ $deleted = $deletedOriginal;
+ }
+ }
+ return !is_string_empty($fileNameDeleted) && $this->yellow->lookup->isContentFile($fileNameRestored) &&
+ $this->yellow->toolbox->renameFile($fileNameDeleted, $fileNameRestored, true) &&
+ $this->yellow->toolbox->createFile($fileNameRestored, $rawDataRestored);
+ }
+
+ // Restore deleted directory from trash
+ public function restoreDirectoryLocation($location) {
+ $pathDeleted = $fileNameRestored = "";
+ $deleted = 0;
+ $pathTrash = $this->yellow->system->get("coreTrashDirectory");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, "/.*/", false, true) as $entry) {
+ $fileName = $entry."/".$this->yellow->system->get("coreContentDefaultFile");
+ if (!is_file($fileName)) continue;
+ $rawDataOriginal = $this->yellow->toolbox->readFile($fileName);
+ $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
+ $fileNameOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalFileName");
+ $deletedOriginal = $this->yellow->toolbox->getFileDeleted($entry);
+ if ($location==$locationOriginal && $deleted<=$deletedOriginal) {
+ $pathDeleted = $entry;
+ $fileNameRestored = $fileNameOriginal;
+ $rawDataRestored = $rawDataOriginal;
+ $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalLocation");
+ $rawDataRestored = $this->yellow->toolbox->unsetMetaData($rawDataRestored, "pageOriginalFileName");
+ $deleted = $deletedOriginal;
+ }
+ }
+ return !is_string_empty($pathDeleted) && $this->yellow->lookup->isContentFile($fileNameRestored) &&
+ $this->yellow->toolbox->renameDirectory($pathDeleted, dirname($fileNameRestored), true) &&
+ $this->yellow->toolbox->createFile($fileNameRestored, $rawDataRestored);
+ }
+
+ // Check if location has been deleted
+ public function isDeletedLocation($location) {
+ $found = false;
+ $pathTrash = $this->yellow->system->get("coreTrashDirectory");
+ $regex = "/^.*\\".$this->yellow->system->get("coreContentExtension")."$/";
+ $fileNames = $this->yellow->toolbox->getDirectoryEntries($pathTrash, $regex, false, false);
+ foreach ($this->yellow->toolbox->getDirectoryEntries($pathTrash, "/.*/", false, true) as $entry) {
+ $fileName = $entry."/".$this->yellow->system->get("coreContentDefaultFile");
+ if (is_file($fileName)) array_push($fileNames, $fileName);
+ }
+ foreach ($fileNames as $fileName) {
+ $rawDataOriginal = $this->yellow->toolbox->readFile($fileName, 4096);
+ $locationOriginal = $this->yellow->toolbox->getMetaData($rawDataOriginal, "pageOriginalLocation");
+ if ($location==$locationOriginal) {
+ $found = true;
+ break;
+ }
+ }
+ return $found;
+ }
+
+ // Check if meta data has been modified
+ public function isMetaModified($pageSource, $pageOther) {
+ return substrb($pageSource->rawData, 0, $pageSource->metaDataOffsetBytes) !=
+ substrb($pageOther->rawData, 0, $pageOther->metaDataOffsetBytes);
+ }
+
+ // Check if login with restriction
+ public function isLoginRestriction() {
+ return $this->yellow->system->get("editLoginRestriction");
+ }
+
+ // Check if user is logged in
+ public function isUser() {
+ return !is_string_empty($this->userEmail);
+ }
+
+ // Check if user with access
+ public function isUserAccess($action, $location = "") {
+ $userHome = $this->yellow->user->getUser("home", $this->userEmail);
+ $tokens = preg_split("/\s*,\s*/", $this->yellow->user->getUser("access", $this->userEmail));
+ return in_array($action, $tokens) && (is_string_empty($location) || substru($location, 0, strlenu($userHome))==$userHome);
}
}
class YellowEditMerge {
- public $yellow; //access to API
- const ADD = "+"; //merge types
+ public $yellow; // access to API
+ const ADD = "+"; // merge types
const MODIFY = "*";
const REMOVE = "-";
const SAME = " ";
@@ -1936,7 +1967,6 @@ class YellowEditMerge {
} else {
$this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], true);
}
- if (defined("DEBUG") && DEBUG>=2) echo "YellowEditMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
if ($typeMine==YellowEditMerge::ADD || $typeYours==YellowEditMerge::ADD) {
if ($typeMine==YellowEditMerge::ADD) ++$posMine;
if ($typeYours==YellowEditMerge::ADD) ++$posYours;
@@ -1949,13 +1979,11 @@ class YellowEditMerge {
array_push($diff, $diffMine[$posMine]);
$typeMine = $diffMine[$posMine][0];
$typeYours = " ";
- if (defined("DEBUG") && DEBUG>=2) echo "YellowEditMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
}
for (;$posYours<count($diffYours); ++$posYours) {
array_push($diff, $diffYours[$posYours]);
$typeYours = $diffYours[$posYours][0];
$typeMine = " ";
- if (defined("DEBUG") && DEBUG>=2) echo "YellowEditMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
}
return $diff;
}
diff --git a/system/extensions/edit.woff b/system/extensions/edit.woff
Binary files differ.
diff --git a/system/extensions/generate.php b/system/extensions/generate.php
@@ -0,0 +1,456 @@
+<?php
+// Generate extension, https://github.com/annaesvensson/yellow-generate
+
+class YellowGenerate {
+ const VERSION = "0.8.52";
+ public $yellow; // access to API
+ public $files; // number of files
+ public $errors; // number of errors
+ public $locationsArguments; // locations with location arguments detected
+ public $locationsArgumentsPagination; // locations with pagination arguments detected
+
+ // Handle initialisation
+ public function onLoad($yellow) {
+ $this->yellow = $yellow;
+ $this->yellow->system->setDefault("generateStaticUrl", "auto");
+ $this->yellow->system->setDefault("generateStaticDirectory", "public/");
+ $this->yellow->system->setDefault("generateStaticDefaultFile", "index.html");
+ $this->yellow->system->setDefault("generateStaticErrorFile", "404.html");
+ }
+
+ // Handle update
+ public function onUpdate($action) {
+ if ($action=="install") {
+ if ($this->yellow->system->isExisting("commandStaticUrl")) { //TODO: remove later, for backwards compatibility
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ $settings = array(
+ "generateStaticUrl" => $this->yellow->system->get("commandStaticUrl"),
+ "generateStaticDirectory" => $this->yellow->system->get("commandStaticDirectory"),
+ "generateStaticDefaultFile" => $this->yellow->system->get("commandStaticDefaultFile"),
+ "generateStaticErrorFile" => $this->yellow->system->get("commandStaticErrorFile"));
+ if (!$this->yellow->system->save($fileName, $settings)) {
+ $this->yellow->toolbox->log("error", "Can't write file '$fileName'!");
+ }
+ $this->yellow->toolbox->log("info", "Import settings for 'Generate ".YellowGenerate::VERSION."'");
+ }
+ }
+ }
+
+ // Handle request
+ public function onRequest($scheme, $address, $base, $location, $fileName) {
+ return $this->processRequestCache($scheme, $address, $base, $location, $fileName);
+ }
+
+ // Handle command
+ public function onCommand($command, $text) {
+ switch ($command) {
+ case "generate": $statusCode = $this->processCommandGenerate($command, $text); break;
+ case "clean": $statusCode = $this->processCommandClean($command, $text); break;
+ default: $statusCode = 0;
+ }
+ return $statusCode;
+ }
+
+ // Handle command help
+ public function onCommandHelp() {
+ return array("generate [directory location]", "clean [directory location]");
+ }
+
+ // Process command to generate static website
+ public function processCommandGenerate($command, $text) {
+ $statusCode = 0;
+ list($path, $location) = $this->yellow->toolbox->getTextArguments($text);
+ if (is_string_empty($location) || substru($location, 0, 1)=="/") {
+ if ($this->checkStaticSettings()) {
+ $statusCode = $this->generateStatic($path, $location);
+ } else {
+ $statusCode = 500;
+ $this->files = 0;
+ $this->errors = 1;
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ echo "ERROR generating files: Please configure GenerateStaticUrl in file '$fileName'!\n";
+ }
+ echo "Yellow $command: $this->files file".($this->files!=1 ? "s" : "");
+ echo ", $this->errors error".($this->errors!=1 ? "s" : "")."\n";
+ } else {
+ $statusCode = 400;
+ echo "Yellow $command: Invalid arguments\n";
+ }
+ return $statusCode;
+ }
+
+ // Generate static website
+ public function generateStatic($path, $location) {
+ $statusCode = 200;
+ $this->files = $this->errors = 0;
+ $path = rtrim(is_string_empty($path) ? $this->yellow->system->get("generateStaticDirectory") : $path, "/");
+ if (is_string_empty($location)) {
+ $statusCode = $this->cleanStatic($path, $location);
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("clean");
+ }
+ }
+ $statusCode = max($statusCode, $this->generateStaticContent($path, $location, "\rGenerating static website", 5, 95));
+ $statusCode = max($statusCode, $this->generateStaticMedia($path, $location));
+ echo "\rGenerating static website 100%... done\n";
+ return $statusCode;
+ }
+
+ // Generate static content
+ public function generateStaticContent($path, $locationFilter, $progressText, $increments, $max) {
+ $statusCode = 200;
+ $this->locationsArguments = $this->locationsArgumentsPagination = array();
+ $staticUrl = $this->yellow->system->get("generateStaticUrl");
+ list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
+ $locations = $this->getContentLocations();
+ $filesEstimated = count($locations);
+ foreach ($locations as $location) {
+ echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max/1.5)."%... ";
+ if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
+ $statusCode = max($statusCode, $this->generateStaticFile($path, $location, true));
+ }
+ foreach ($this->locationsArguments as $location) {
+ echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max/1.5)."%... ";
+ if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
+ $statusCode = max($statusCode, $this->generateStaticFile($path, $location, true));
+ }
+ $filesEstimated = $this->files + count($this->locationsArguments) + count($this->locationsArgumentsPagination);
+ foreach ($this->locationsArgumentsPagination as $location) {
+ echo "$progressText ".$this->getProgressPercent($this->files, $filesEstimated, $increments, $max)."%... ";
+ if (!preg_match("#^$base$locationFilter#", "$base$location")) continue;
+ if (substru($location, -1)!=$this->yellow->toolbox->getLocationArgumentsSeparator()) {
+ $statusCode = max($statusCode, $this->generateStaticFile($path, $location, false, true));
+ }
+ for ($pageNumber=2; $pageNumber<=999; ++$pageNumber) {
+ $statusCodeLocation = $this->generateStaticFile($path, $location.$pageNumber, false, true);
+ $statusCode = max($statusCode, $statusCodeLocation);
+ if ($statusCodeLocation==100) break;
+ }
+ }
+ echo "$progressText ".$this->getProgressPercent(100, 100, $increments, $max)."%... ";
+ return $statusCode;
+ }
+
+ // Generate static media
+ public function generateStaticMedia($path, $locationFilter) {
+ $statusCode = 200;
+ if (is_string_empty($locationFilter)) {
+ foreach ($this->getMediaLocations() as $location) {
+ $statusCode = max($statusCode, $this->generateStaticFile($path, $location));
+ }
+ foreach ($this->getExtraLocations($path) as $location) {
+ $statusCode = max($statusCode, $this->generateStaticFile($path, $location));
+ }
+ $statusCode = max($statusCode, $this->generateStaticFile($path, "/error/", false, false, true));
+ }
+ return $statusCode;
+ }
+
+ // Generate static file
+ public function generateStaticFile($path, $location, $analyse = false, $probe = false, $error = false) {
+ $this->yellow->content = new YellowContent($this->yellow);
+ $this->yellow->page = new YellowPage($this->yellow);
+ $this->yellow->page->fileName = substru($location, 1);
+ if (!is_readable($this->yellow->page->fileName)) {
+ ob_start();
+ $staticUrl = $this->yellow->system->get("generateStaticUrl");
+ list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
+ $statusCode = $this->requestStaticFile($scheme, $address, $base, $location);
+ if ($statusCode<400 || $error) {
+ $fileData = ob_get_contents();
+ $statusCode = $this->saveStaticFile($path, $location, $fileData, $statusCode);
+ }
+ ob_end_clean();
+ } else {
+ $statusCode = $this->copyStaticFile($path, $location);
+ }
+ if ($statusCode==200 && $analyse) $this->analyseLocations($scheme, $address, $base, $fileData);
+ if ($statusCode==404 && $probe) $statusCode = 100;
+ if ($statusCode==404 && $error) $statusCode = 200;
+ if ($statusCode>=200) ++$this->files;
+ if ($statusCode>=400) {
+ ++$this->errors;
+ echo "\rERROR generating location '$location', ".$this->yellow->page->getStatusCode(true)."\n";
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=1) {
+ echo "YellowGenerate::generateStaticFile status:$statusCode location:$location<br/>\n";
+ }
+ return $statusCode;
+ }
+
+ // Request static file
+ public function requestStaticFile($scheme, $address, $base, $location) {
+ list($serverName, $serverPort) = $this->yellow->toolbox->getTextList($address, ":", 2);
+ if (is_string_empty($serverPort)) $serverPort = $scheme=="https" ? 443 : 80;
+ $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1";
+ $_SERVER["SERVER_NAME"] = $serverName;
+ $_SERVER["SERVER_PORT"] = $serverPort;
+ $_SERVER["REQUEST_METHOD"] = "GET";
+ $_SERVER["REQUEST_SCHEME"] = $scheme;
+ $_SERVER["REQUEST_URI"] = $base.$location;
+ $_SERVER["SCRIPT_NAME"] = $base."/yellow.php";
+ $_SERVER["REMOTE_ADDR"] = "127.0.0.1";
+ $_REQUEST = array();
+ return $this->yellow->request();
+ }
+
+ // Save static file
+ public function saveStaticFile($path, $location, $fileData, $statusCode) {
+ $modified = strtotime($this->yellow->page->getHeader("Last-Modified"));
+ if ($modified==0) $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName);
+ if ($statusCode>=301 && $statusCode<=303) {
+ $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location"));
+ $modified = time();
+ }
+ $fileName = $this->getStaticFile($path, $location, $statusCode);
+ if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName);
+ if (!$this->yellow->toolbox->createFile($fileName, $fileData, true) ||
+ !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
+ $statusCode = 500;
+ $this->yellow->page->statusCode = $statusCode;
+ $this->yellow->page->errorMessage = "Can't write file '$fileName'!";
+ }
+ return $statusCode;
+ }
+
+ // Copy static file
+ public function copyStaticFile($path, $location) {
+ $statusCode = 200;
+ $modified = $this->yellow->toolbox->getFileModified($this->yellow->page->fileName);
+ $fileName = $this->getStaticFile($path, $location, $statusCode);
+ if (is_file($fileName)) $this->yellow->toolbox->deleteFile($fileName);
+ if (!$this->yellow->toolbox->copyFile($this->yellow->page->fileName, $fileName, true) ||
+ !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
+ $statusCode = 500;
+ $this->yellow->page->statusCode = $statusCode;
+ $this->yellow->page->errorMessage = "Can't write file '$fileName'!";
+ }
+ return $statusCode;
+ }
+
+ // Analyse locations with arguments
+ public function analyseLocations($scheme, $address, $base, $rawData) {
+ preg_match_all("/<(.*?)href=\"([^\"]+)\"(.*?)>/i", $rawData, $matches);
+ foreach ($matches[2] as $match) {
+ $location = rawurldecode($match);
+ if (preg_match("/^(.*?)#(.*)$/", $location, $tokens)) $location = $tokens[1];
+ if (preg_match("/^(\w+):\/\/([^\/]+)(.*)$/", $location, $tokens)) {
+ if ($tokens[1]!=$scheme) continue;
+ if ($tokens[2]!=$address) continue;
+ $location = $tokens[3];
+ }
+ if (substru($location, 0, strlenu($base))!=$base) continue;
+ if (substru($location, strlenu($base), 1)!="/") continue;
+ $location = substru($location, strlenu($base));
+ if (!$this->yellow->toolbox->isLocationArguments($location)) continue;
+ if (!$this->yellow->toolbox->isLocationArgumentsPagination($location)) {
+ $location = rtrim($location, "/")."/";
+ if (!isset($this->locationsArguments[$location])) {
+ $this->locationsArguments[$location] = $location;
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowGenerate::analyseLocations detected location:$location<br/>\n";
+ }
+ }
+ } else {
+ $location = rtrim($location, "0..9");
+ if (!isset($this->locationsArgumentsPagination[$location])) {
+ $this->locationsArgumentsPagination[$location] = $location;
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowGenerate::analyseLocations detected location:$location<br/>\n";
+ }
+ }
+ }
+ }
+ }
+
+ // Process command to clean static website
+ public function processCommandClean($command, $text) {
+ $statusCode = 0;
+ list($path, $location) = $this->yellow->toolbox->getTextArguments($text);
+ if (is_string_empty($location) || substru($location, 0, 1)=="/") {
+ $statusCode = $this->cleanStatic($path, $location);
+ echo "Yellow $command: Static website";
+ echo " ".($statusCode!=200 ? "not " : "")."cleaned\n";
+ } else {
+ $statusCode = 400;
+ echo "Yellow $command: Invalid arguments\n";
+ }
+ return $statusCode;
+ }
+
+ // Clean static website
+ public function cleanStatic($path, $location) {
+ $statusCode = 200;
+ $path = rtrim(is_string_empty($path) ? $this->yellow->system->get("generateStaticDirectory") : $path, "/");
+ if (is_string_empty($location)) {
+ $statusCode = max($statusCode, $this->cleanStaticDirectory($path));
+ } else {
+ if ($this->yellow->lookup->isFileLocation($location)) {
+ $fileName = $this->getStaticFile($path, $location, $statusCode);
+ $statusCode = $this->cleanStaticFile($fileName);
+ } else {
+ $statusCode = $this->cleanStaticDirectory($path.$location);
+ }
+ }
+ return $statusCode;
+ }
+
+ // Clean static directory
+ public function cleanStaticDirectory($path) {
+ $statusCode = 200;
+ if (is_dir($path) && $this->checkStaticDirectory($path)) {
+ if (!$this->yellow->toolbox->deleteDirectory($path)) {
+ $statusCode = 500;
+ echo "ERROR cleaning files: Can't delete directory '$path'!\n";
+ }
+ }
+ return $statusCode;
+ }
+
+ // Clean static file
+ public function cleanStaticFile($fileName) {
+ $statusCode = 200;
+ if (is_file($fileName)) {
+ if (!$this->yellow->toolbox->deleteFile($fileName)) {
+ $statusCode = 500;
+ echo "ERROR cleaning files: Can't delete file '$fileName'!\n";
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request for cached files
+ public function processRequestCache($scheme, $address, $base, $location, $fileName) {
+ $statusCode = 0;
+ if (is_dir($this->yellow->system->get("coreCacheDirectory"))) {
+ $location .= $this->yellow->toolbox->getLocationArguments();
+ $fileName = rtrim($this->yellow->system->get("coreCacheDirectory"), "/").$location;
+ if (!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->system->get("generateStaticDefaultFile");
+ if (is_file($fileName) && is_readable($fileName) && !$this->yellow->lookup->isCommandLine()) {
+ $statusCode = $this->yellow->sendFile(200, $fileName, true);
+ }
+ }
+ return $statusCode;
+ }
+
+ // Check static settings
+ public function checkStaticSettings() {
+ return preg_match("/^(http|https):/", $this->yellow->system->get("generateStaticUrl"));
+ }
+
+ // Check static directory
+ public function checkStaticDirectory($path) {
+ $ok = false;
+ if (!is_string_empty($path)) {
+ if ($path==rtrim($this->yellow->system->get("generateStaticDirectory"), "/")) $ok = true;
+ if ($path==rtrim($this->yellow->system->get("coreCacheDirectory"), "/")) $ok = true;
+ if ($path==rtrim($this->yellow->system->get("coreTrashDirectory"), "/")) $ok = true;
+ if (is_file("$path/".$this->yellow->system->get("generateStaticDefaultFile"))) $ok = true;
+ if (is_file("$path/yellow.php")) $ok = false;
+ }
+ return $ok;
+ }
+
+ // Return progress in percent
+ public function getProgressPercent($now, $total, $increments, $max) {
+ $max = intval($max/$increments) * $increments;
+ $percent = intval(($max/$total) * $now);
+ if ($increments>1) $percent = intval($percent/$increments) * $increments;
+ return min($max, $percent);
+ }
+
+ // Return static file
+ public function getStaticFile($path, $location, $statusCode) {
+ if ($statusCode<400) {
+ $fileName = $path.$location;
+ if (!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->system->get("generateStaticDefaultFile");
+ } elseif ($statusCode==404) {
+ $fileName = $path."/".$this->yellow->system->get("generateStaticErrorFile");
+ } else {
+ $fileName = $path."/error.html";
+ }
+ return $fileName;
+ }
+
+ // Return static redirect
+ public function getStaticRedirect($location) {
+ $output = "<!DOCTYPE html><html>\n<head>\n";
+ $output .= "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n";
+ $output .= "<meta http-equiv=\"refresh\" content=\"0;url=".htmlspecialchars($location)."\" />\n";
+ $output .= "</head>\n</html>";
+ return $output;
+ }
+
+ // Return content locations
+ public function getContentLocations($includeAll = false) {
+ $locations = array();
+ $staticUrl = $this->yellow->system->get("generateStaticUrl");
+ list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($staticUrl);
+ $this->yellow->page->setRequestInformation($scheme, $address, $base, "", "", false);
+ foreach ($this->yellow->content->index(true, true) as $page) {
+ if (preg_match("/exclude/i", $page->get("generate")) && !$includeAll) continue;
+ if ($page->get("status")=="private" || $page->get("status")=="draft") continue;
+ array_push($locations, $page->location);
+ }
+ if (!$this->yellow->content->find("/") && $this->yellow->system->get("coreMultiLanguageMode")) array_unshift($locations, "/");
+ return $locations;
+ }
+
+ // Return media locations
+ public function getMediaLocations() {
+ $locations = array();
+ $mediaPath = $this->yellow->system->get("coreMediaDirectory");
+ $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($mediaPath, "/.*/", false, false);
+ foreach ($fileNames as $fileName) {
+ array_push($locations, $this->yellow->lookup->findMediaLocationFromFile($fileName));
+ }
+ $extensionPath = $this->yellow->system->get("coreExtensionDirectory");
+ $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($extensionPath, "/.*/", false, false);
+ foreach ($fileNames as $fileName) {
+ array_push($locations, $this->yellow->lookup->findMediaLocationFromFile($fileName));
+ }
+ $themePath = $this->yellow->system->get("coreThemeDirectory");
+ $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive($themePath, "/.*/", false, false);
+ foreach ($fileNames as $fileName) {
+ array_push($locations, $this->yellow->lookup->findMediaLocationFromFile($fileName));
+ }
+ return array_diff($locations, $this->getMediaLocationsIgnore());
+ }
+
+ // Return media locations to ignore
+ public function getMediaLocationsIgnore() {
+ $locations = array("");
+ $extensionPath = $this->yellow->system->get("coreExtensionDirectory");
+ $extensionDirectoryLength = strlenu($this->yellow->system->get("coreExtensionDirectory"));
+ if ($this->yellow->extension->isExisting("bundle")) {
+ foreach ($this->yellow->toolbox->getDirectoryEntries($extensionPath, "/^bundle-(.*)/", false, false) as $entry) {
+ list($locationsBundle) = $this->yellow->extension->get("bundle")->getBundleInformation($entry);
+ $locations = array_merge($locations, $locationsBundle);
+ }
+ }
+ if ($this->yellow->extension->isExisting("edit")) {
+ foreach ($this->yellow->toolbox->getDirectoryEntries($extensionPath, "/^edit\.(.*)/", false, false) as $entry) {
+ $location = $this->yellow->system->get("coreExtensionLocation").substru($entry, $extensionDirectoryLength);
+ array_push($locations, $location);
+ }
+ }
+ return array_unique($locations);
+ }
+
+ // Return extra locations
+ public function getExtraLocations($path) {
+ $locations = array();
+ $pathIgnore = "($path/|".
+ $this->yellow->system->get("generateStaticDirectory")."|".
+ $this->yellow->system->get("coreContentDirectory")."|".
+ $this->yellow->system->get("coreMediaDirectory")."|".
+ $this->yellow->system->get("coreSystemDirectory").")";
+ $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive(".", "/.*/", false, false);
+ foreach ($fileNames as $fileName) {
+ $fileName = substru($fileName, 2);
+ if (preg_match("#^$pathIgnore#", $fileName) || $fileName=="yellow.php") continue;
+ array_push($locations, "/".$fileName);
+ }
+ return $locations;
+ }
+}
diff --git a/system/extensions/image.php b/system/extensions/image.php
@@ -1,24 +1,30 @@
<?php
-// Image extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/image
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Image extension, https://github.com/annaesvensson/yellow-image
class YellowImage {
- const VERSION = "0.8.8";
- const TYPE = "feature";
- public $yellow; //access to API
+ const VERSION = "0.8.19";
+ public $yellow; // access to API
// Handle initialisation
public function onLoad($yellow) {
$this->yellow = $yellow;
- $this->yellow->system->setDefault("imageAlt", "Image");
$this->yellow->system->setDefault("imageUploadWidthMax", "1280");
$this->yellow->system->setDefault("imageUploadHeightMax", "1280");
$this->yellow->system->setDefault("imageUploadJpgQuality", "80");
- $this->yellow->system->setDefault("imageThumbnailLocation", "/media/thumbnails/");
- $this->yellow->system->setDefault("imageThumbnailDirectory", "media/thumbnails/");
$this->yellow->system->setDefault("imageThumbnailJpgQuality", "80");
}
+
+ // Handle update
+ public function onUpdate($action) {
+ if ($action=="clean") {
+ $statusCode = 200;
+ $path = $this->yellow->lookup->findMediaDirectory("coreThumbnailLocation");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, false) as $entry) {
+ if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
+ }
+ if ($statusCode==500) $this->yellow->toolbox->log("error", "Can't delete files in directory '$path'!");
+ }
+ }
// Handle page content of shortcut
public function onParseContentShortcut($page, $name, $text, $type) {
@@ -26,91 +32,80 @@ class YellowImage {
if ($name=="image" && $type=="inline") {
list($name, $alt, $style, $width, $height) = $this->yellow->toolbox->getTextArguments($text);
if (!preg_match("/^\w+:/", $name)) {
- if (empty($alt)) $alt = $this->yellow->system->get("imageAlt");
- if (empty($width)) $width = "100%";
- if (empty($height)) $height = $width;
- list($src, $width, $height) = $this->getImageInformation($this->yellow->system->get("coreImageDirectory").$name, $width, $height);
+ if (is_string_empty($alt)) $alt = $this->yellow->language->getText("imageDefaultAlt");
+ if (is_string_empty($width)) $width = "100%";
+ if (is_string_empty($height)) $height = $width;
+ $path = $this->yellow->lookup->findMediaDirectory("coreImageLocation");
+ list($src, $width, $height) = $this->getImageInformation($path.$name, $width, $height);
} else {
- if (empty($alt)) $alt = $this->yellow->system->get("imageAlt");
+ if (is_string_empty($alt)) $alt = $this->yellow->language->getText("imageDefaultAlt");
$src = $this->yellow->lookup->normaliseUrl("", "", "", $name);
$width = $height = 0;
}
$output = "<img src=\"".htmlspecialchars($src)."\"";
if ($width && $height) $output .= " width=\"".htmlspecialchars($width)."\" height=\"".htmlspecialchars($height)."\"";
- if (!empty($alt)) $output .= " alt=\"".htmlspecialchars($alt)."\" title=\"".htmlspecialchars($alt)."\"";
- if (!empty($style)) $output .= " class=\"".htmlspecialchars($style)."\"";
+ if (!is_string_empty($alt)) $output .= " alt=\"".htmlspecialchars($alt)."\" title=\"".htmlspecialchars($alt)."\"";
+ if (!is_string_empty($style)) $output .= " class=\"".htmlspecialchars($style)."\"";
$output .= " />";
}
return $output;
}
// Handle media file changes
- public function onEditMediaFile($file, $action) {
+ public function onEditMediaFile($file, $action, $email) {
if ($action=="upload") {
$fileName = $file->fileName;
- list($widthInput, $heightInput, $type) = $this->yellow->toolbox->detectImageInformation($fileName, $file->get("type"));
+ list($widthInput, $heightInput, $orientation, $type) =
+ $this->yellow->toolbox->detectImageInformation($fileName, $file->get("type"));
$widthMax = $this->yellow->system->get("imageUploadWidthMax");
$heightMax = $this->yellow->system->get("imageUploadHeightMax");
- if (($widthInput>$widthMax || $heightInput>$heightMax) && ($type=="gif" || $type=="jpg" || $type=="png")) {
- list($widthOutput, $heightOutput) = $this->getImageDimensionsFit($widthInput, $heightInput, $widthMax, $heightMax);
- $image = $this->loadImage($fileName, $type);
- $image = $this->resizeImage($image, $widthInput, $heightInput, $widthOutput, $heightOutput);
- $image = $this->orientImage($image, $fileName, $type);
- if (!$this->saveImage($image, $fileName, $type, $this->yellow->system->get("imageUploadJpgQuality"))) {
- $file->error(500, "Can't write file '$fileName'!");
+ if ($type=="gif" || $type=="jpg" || $type=="png") {
+ if ($widthInput>$widthMax || $heightInput>$heightMax) {
+ list($widthOutput, $heightOutput) = $this->getImageDimensionsFit($widthInput, $heightInput, $widthMax, $heightMax);
+ $image = $this->loadImage($fileName, $type);
+ $image = $this->resizeImage($image, $widthInput, $heightInput, $widthOutput, $heightOutput);
+ $image = $this->orientImage($image, $orientation);
+ if (!$this->saveImage($image, $fileName, $type, $this->yellow->system->get("imageUploadJpgQuality"))) {
+ $file->error(500, "Can't write file '$fileName'!");
+ }
+ } elseif ($orientation>1) {
+ $image = $this->loadImage($fileName, $type);
+ $image = $this->orientImage($image, $orientation);
+ if (!$this->saveImage($image, $fileName, $type, $this->yellow->system->get("imageUploadJpgQuality"))) {
+ $file->error(500, "Can't write file '$fileName'!");
+ }
}
}
}
}
-
- // Handle command
- public function onCommand($command, $text) {
- switch ($command) {
- case "clean": $statusCode = $this->processCommandClean($command, $text); break;
- default: $statusCode = 0;
- }
- return $statusCode;
- }
- // Process command to clean thumbnails
- public function processCommandClean($command, $text) {
- $statusCode = 0;
- if ($command=="clean" && $text=="all") {
- $path = $this->yellow->system->get("imageThumbnailDirectory");
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, false) as $entry) {
- if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
- }
- if ($statusCode==500) echo "ERROR cleaning thumbnails: Can't delete files in directory '$path'!\n";
- }
- return $statusCode;
- }
-
- // Return image info, create thumbnail on demand
+ // Return image information, create thumbnail on demand
public function getImageInformation($fileName, $widthOutput, $heightOutput) {
- $fileNameShort = substru($fileName, strlenu($this->yellow->system->get("coreImageDirectory")));
- list($widthInput, $heightInput, $type) = $this->yellow->toolbox->detectImageInformation($fileName);
+ $fileNameShort = substru($fileName, strlenu($this->yellow->lookup->findMediaDirectory("coreImageLocation")));
+ list($widthInput, $heightInput, $orientation, $type) = $this->yellow->toolbox->detectImageInformation($fileName);
$widthOutput = $this->convertValueAndUnit($widthOutput, $widthInput);
$heightOutput = $this->convertValueAndUnit($heightOutput, $heightInput);
- if (($widthInput==$widthOutput && $heightInput==$heightOutput) || $type=="svg") {
+ if (($widthInput==$widthOutput && $heightInput==$heightOutput) || $type=="svg" || $type=="") {
$src = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreImageLocation").$fileNameShort;
$width = $widthOutput;
$height = $heightOutput;
} else {
+ $pathThumb = $this->yellow->lookup->findMediaDirectory("coreThumbnailLocation");
$fileNameThumb = ltrim(str_replace(array("/", "\\", "."), "-", dirname($fileNameShort)."/".pathinfo($fileName, PATHINFO_FILENAME)), "-");
$fileNameThumb .= "-".$widthOutput."x".$heightOutput;
$fileNameThumb .= ".".pathinfo($fileName, PATHINFO_EXTENSION);
- $fileNameOutput = $this->yellow->system->get("imageThumbnailDirectory").$fileNameThumb;
+ $fileNameOutput = $pathThumb.$fileNameThumb;
if ($this->isFileNotUpdated($fileName, $fileNameOutput)) {
$image = $this->loadImage($fileName, $type);
$image = $this->resizeImage($image, $widthInput, $heightInput, $widthOutput, $heightOutput);
- $image = $this->orientImage($image, $fileName, $type);
+ $image = $this->orientImage($image, $orientation);
if (is_file($fileNameOutput)) $this->yellow->toolbox->deleteFile($fileNameOutput);
if (!$this->saveImage($image, $fileNameOutput, $type, $this->yellow->system->get("imageThumbnailJpgQuality")) ||
!$this->yellow->toolbox->modifyFile($fileNameOutput, $this->yellow->toolbox->getFileModified($fileName))) {
$this->yellow->page->error(500, "Can't write file '$fileNameOutput'!");
}
}
- $src = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("imageThumbnailLocation").$fileNameThumb;
+ $src = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreThumbnailLocation").$fileNameThumb;
list($width, $height) = $this->yellow->toolbox->detectImageInformation($fileNameOutput);
}
return array($src, $width, $height);
@@ -173,20 +168,15 @@ class YellowImage {
}
// Orient image automatically
- public function orientImage($image, $fileName, $type) {
- if ($type=="jpg") {
- $exif = @exif_read_data($fileName);
- if ($exif && isset($exif["Orientation"])) {
- switch ($exif["Orientation"]) {
- case 2: imageflip($image, IMG_FLIP_HORIZONTAL); break;
- case 3: $image = imagerotate($image, 180, 0); break;
- case 4: imageflip($image, IMG_FLIP_VERTICAL); break;
- case 5: $image = imagerotate($image, 90, 0); imageflip($image, IMG_FLIP_VERTICAL); break;
- case 6: $image = imagerotate($image, -90, 0); break;
- case 7: $image = imagerotate($image, 90, 0); imageflip($image, IMG_FLIP_HORIZONTAL); break;
- case 8: $image = imagerotate($image, 90, 0); break;
- }
- }
+ public function orientImage($image, $orientation) {
+ switch ($orientation) {
+ case 2: imageflip($image, IMG_FLIP_HORIZONTAL); break;
+ case 3: $image = imagerotate($image, 180, 0); break;
+ case 4: imageflip($image, IMG_FLIP_VERTICAL); break;
+ case 5: $image = imagerotate($image, 90, 0); imageflip($image, IMG_FLIP_VERTICAL); break;
+ case 6: $image = imagerotate($image, -90, 0); break;
+ case 7: $image = imagerotate($image, 90, 0); imageflip($image, IMG_FLIP_HORIZONTAL); break;
+ case 8: $image = imagerotate($image, 90, 0); break;
}
return $image;
}
diff --git a/system/extensions/install-blog.bin b/system/extensions/install-blog.bin
Binary files differ.
diff --git a/system/extensions/install-language.bin b/system/extensions/install-language.bin
Binary files differ.
diff --git a/system/extensions/install-wiki.bin b/system/extensions/install-wiki.bin
Binary files differ.
diff --git a/system/extensions/markdown.php b/system/extensions/markdown.php
@@ -1,12 +1,9 @@
<?php
-// Markdown extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/markdown
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Markdown extension, https://github.com/annaesvensson/yellow-markdown
class YellowMarkdown {
- const VERSION = "0.8.14";
- const TYPE = "feature";
- public $yellow; //access to API
+ const VERSION = "0.8.26";
+ public $yellow; // access to API
// Handle initialisation
public function onLoad($yellow) {
@@ -16,7 +13,9 @@ class YellowMarkdown {
// Handle page content in raw format
public function onParseContentRaw($page, $text) {
$markdown = new YellowMarkdownParser($this->yellow, $page);
- return $markdown->transform($text);
+ $text = $markdown->transform($text);
+ $text = $this->yellow->lookup->normaliseData($text, "html");
+ return $text;
}
}
@@ -3831,14 +3830,13 @@ class MarkdownExtraParser extends MarkdownParser {
}
}
-// Datenstrom Yellow Markdown parser
-// Copyright (c) 2013-2020 Datenstrom
+// Markdown parser, Copyright Datenstrom, License GPLv2
class YellowMarkdownParser extends MarkdownExtraParser {
- public $yellow; //access to API
- public $page; //access to page
- public $idAttributes; //id attributes
- public $noticeLevel; //recursive level
+ public $yellow; // access to API
+ public $page; // access to page
+ public $idAttributes; // id attributes
+ public $noticeLevel; // recursive level
public function __construct($yellow, $page) {
$this->yellow = $yellow;
@@ -3846,17 +3844,18 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$this->idAttributes = array();
$this->noticeLevel = 0;
$this->url_filter_func = function($url) use ($yellow, $page) {
- return $yellow->lookup->normaliseLocation($url, $page->location);
+ return $yellow->lookup->normaliseLocation($url, $page->getPage("main")->location);
};
$this->span_gamut += array("doStrikethrough" => 55);
$this->block_gamut += array("doNoticeBlocks" => 65);
+ $this->document_gamut += array("doFootnotesLinks" => 55);
$this->escape_chars .= "~";
parent::__construct();
}
// Handle striketrough
public function doStrikethrough($text) {
- $parts = preg_split("/(?<![~])(~~)(?![~])/", $text, null, PREG_SPLIT_DELIM_CAPTURE);
+ $parts = preg_split("/(?<![~])(~~)(?![~])/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
if (count($parts)>3) {
$text = "";
$open = false;
@@ -3877,7 +3876,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
public function doAutoLinks($text) {
$text = preg_replace_callback("/<(\w+:[^\'\">\s]+)>/", array($this, "_doAutoLinks_url_callback"), $text);
$text = preg_replace_callback("/<([\w\+\-\.]+@[\w\-\.]+)>/", array($this, "_doAutoLinks_email_callback"), $text);
- $text = preg_replace_callback("/^\s*\[(\w+)(.*?)\]\s*$/", array($this, "_doAutoLinks_shortcutBlock_callback"), $text);
+ $text = preg_replace_callback("/^\s*\[(\w+)([^\]]*)\]\s*$/", array($this, "_doAutoLinks_shortcutBlock_callback"), $text);
$text = preg_replace_callback("/\[(\w+)(.*?)\]/", array($this, "_doAutoLinks_shortcutInline_callback"), $text);
$text = preg_replace_callback("/\[\-\-(.*?)\-\-\]/", array($this, "_doAutoLinks_shortcutComment_callback"), $text);
$text = preg_replace_callback("/\:([\w\+\-\_]+)\:/", array($this, "_doAutoLinks_shortcutSymbol_callback"), $text);
@@ -3913,7 +3912,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
// Handle fenced code blocks
public function _doFencedCodeBlocks_callback($matches) {
$text = $matches[4];
- $name = empty($matches[2]) ? "" : trim("$matches[2] $matches[3]");
+ $name = is_string_empty($matches[2]) ? "" : trim("$matches[2] $matches[3]");
$output = $this->page->parseContentShortcut($name, $text, "code");
if (is_null($output)) {
$attr = $this->doExtraAttributes("pre", ".$matches[2] $matches[3]");
@@ -3928,7 +3927,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$text = $matches[1];
$level = $matches[3][0]=="=" ? 1 : 2;
$attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
- if (empty($attr) && $level>=2 && $level<=3) $attr = $this->getIdAttribute($text);
+ if (is_string_empty($attr) && $level>=2) $attr = $this->getIdAttribute($text);
$output = "<h$level$attr>".$this->runSpanGamut($text)."</h$level>";
return "\n".$this->hashBlock($output)."\n\n";
}
@@ -3938,7 +3937,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$text = $matches[2];
$level = strlen($matches[1]);
$attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
- if (empty($attr) && $level>=2 && $level<=3) $attr = $this->getIdAttribute($text);
+ if (is_string_empty($attr) && $level>=2) $attr = $this->getIdAttribute($text);
$output = "<h$level$attr>".$this->runSpanGamut($text)."</h$level>";
return "\n".$this->hashBlock($output)."\n\n";
}
@@ -3950,7 +3949,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$title = isset($matches[7]) ? $matches[7] : "";
$attr = $this->doExtraAttributes("a", $dummy =& $matches[8]);
$output = "<a href=\"".$this->encodeURLAttribute($url)."\"";
- if (!empty($title)) $output .= " title=\"".$this->encodeAttribute($title)."\"";
+ if (!is_string_empty($title)) $output .= " title=\"".$this->encodeAttribute($title)."\"";
$output .= $attr;
$output .= ">".$this->runSpanGamut($text)."</a>";
return $this->hashPart($output);
@@ -3966,8 +3965,8 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$title = isset($matches[7]) ? $matches[7] : $matches[2];
$attr = $this->doExtraAttributes("img", $dummy =& $matches[8]);
$output = "<img src=\"".$this->encodeURLAttribute($src)."\"";
- if (!empty($alt)) $output .= " alt=\"".$this->encodeAttribute($alt)."\"";
- if (!empty($title)) $output .= " title=\"".$this->encodeAttribute($title)."\"";
+ if (!is_string_empty($alt)) $output .= " alt=\"".$this->encodeAttribute($alt)."\"";
+ if (!is_string_empty($title)) $output .= " title=\"".$this->encodeAttribute($title)."\"";
$output .= $attr;
$output .= $this->empty_element_suffix;
return $this->hashPart($output);
@@ -3996,6 +3995,11 @@ class YellowMarkdownParser extends MarkdownExtraParser {
return "<li$attr>".$item."</li>\n";
}
+ // Handle blockquotes, CommonMark compatible
+ public function doBlockQuotes($text) {
+ return preg_replace_callback("/((?>^[ ]*>[ ]?.+\n(.+\n)*)+)/m", array($this, "_doBlockQuotes_callback"), $text);
+ }
+
// Handle notice blocks
public function doNoticeBlocks($text) {
return preg_replace_callback("/((?>^[ ]*!(?!\[)[ ]?.+\n(.+\n)*)+)/m", array($this, "_doNoticeBlocks_callback"), $text);
@@ -4013,7 +4017,7 @@ class YellowMarkdownParser extends MarkdownExtraParser {
$level = strspn(str_replace(array("![", " "), "", $lines), "!");
$attr = " class=\"notice$level\"";
}
- if (!empty($text)) {
+ if (!is_string_empty($text)) {
++$this->noticeLevel;
$output = "<div$attr>\n".$this->runBlockGamut($text)."\n</div>";
--$this->noticeLevel;
@@ -4023,6 +4027,25 @@ class YellowMarkdownParser extends MarkdownExtraParser {
return "\n".$this->hashBlock($output)."\n\n";
}
+ // Handle footnotes links, normalise ids and links
+ public function doFootnotesLinks($text) {
+ if (!is_null($this->footnotes_assembled)) {
+ $callbackId = function ($matches) {
+ $id = str_replace(":", "-", $matches[2]);
+ return "<$matches[1] id=\"$id\" $matches[3]>";
+ };
+ $text = preg_replace_callback("/<(li|sup) id=\"(fn:\d+)\"(.*?)>/", $callbackId, $text);
+ $text = preg_replace_callback("/<(li|sup) id=\"(fnref\d*:\d+)\"(.*?)>/", $callbackId, $text);
+ $callbackHref = function ($matches) {
+ $href = $this->page->base.$this->page->location.str_replace(":", "-", $matches[2]);
+ return "<$matches[1] href=\"$href\" $matches[3]>";
+ };
+ $text = preg_replace_callback("/<(a) href=\"(#fn:\d+)\"(.*?)>/", $callbackHref, $text);
+ $text = preg_replace_callback("/<(a) href=\"(#fnref\d*:\d+)\"(.*?)>/", $callbackHref, $text);
+ }
+ return $text;
+ }
+
// Return unique id attribute
public function getIdAttribute($text) {
$attr = "";
diff --git a/system/extensions/meta.php b/system/extensions/meta.php
@@ -1,68 +0,0 @@
-<?php
-// Meta extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/meta
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-class YellowMeta {
- const VERSION = "0.8.12";
- const TYPE = "feature";
- public $yellow; //access to API
-
- // Handle initialisation
- public function onLoad($yellow) {
- $this->yellow = $yellow;
- $this->yellow->system->setDefault("metaDefaultImage", "icon");
- }
-
- // Handle page extra data
- public function onParsePageExtra($page, $name) {
- $output = null;
- if ($name=="header" && !$page->isError()) {
- list($imageUrl, $imageAlt) = $this->getImageInformation($page);
- $locale = $this->yellow->text->getText("languageLocale", $page->get("language"));
- $output .= "<meta property=\"og:url\" content=\"".htmlspecialchars($page->getUrl().$this->yellow->toolbox->getLocationArguments())."\" />\n";
- $output .= "<meta property=\"og:locale\" content=\"".htmlspecialchars($locale)."\" />\n";
- $output .= "<meta property=\"og:type\" content=\"website\" />\n";
- $output .= "<meta property=\"og:title\" content=\"".$page->getHtml("title")."\" />\n";
- $output .= "<meta property=\"og:site_name\" content=\"".$page->getHtml("sitename")."\" />\n";
- $output .= "<meta property=\"og:description\" content=\"".$page->getHtml("description")."\" />\n";
- $output .= "<meta property=\"og:image\" content=\"".htmlspecialchars($imageUrl)."\" />\n";
- $output .= "<meta property=\"og:image:alt\" content=\"".htmlspecialchars($imageAlt)."\" />\n";
- }
- return $output;
- }
-
- // Handle page output data
- public function onParsePageOutput($page, $text) {
- $output = null;
- if ($text && preg_match("/^(.*?)<html(.*?)>(.*)$/s", $text, $matches)) {
- $output = $matches[1]."<html".$matches[2]." prefix=\"og: http://ogp.me/ns#\">".$matches[3];
- }
- return $output;
- }
-
- // Return image information for page
- public function getImageInformation($page) {
- if ($page->isExisting("image")) {
- $name = $page->get("image");
- $alt = $page->isExisting("imageAlt") ? $page->get("imageAlt") : $page->get("title");
- } elseif (preg_match("/\[image(\s.*?)\]/", $page->getContent(true), $matches)) {
- list($name, $alt) = $this->yellow->toolbox->getTextArguments(trim($matches[1]));
- if (empty($alt)) $alt = $page->get("title");
- } else {
- $name = $this->yellow->system->get("metaDefaultImage");
- $alt = $page->isExisting("imageAlt") ? $page->get("imageAlt") : $page->get("title");
- }
- if (!preg_match("/^\w+:/", $name)) {
- $location = $name!="icon" ? $this->yellow->system->get("coreImageLocation").$name :
- $this->yellow->system->get("coreResourceLocation").$page->get("theme")."-icon.png";
- $url = $this->yellow->lookup->normaliseUrl(
- $this->yellow->system->get("coreServerScheme"),
- $this->yellow->system->get("coreServerAddress"),
- $this->yellow->system->get("coreServerBase"), $location);
- } else {
- $url = $this->yellow->lookup->normaliseUrl("", "", "", $name);
- }
- return array($url, $alt);
- }
-}
diff --git a/system/extensions/serve.php b/system/extensions/serve.php
@@ -0,0 +1,61 @@
+<?php
+// Serve extension, https://github.com/annaesvensson/yellow-serve
+
+class YellowServe {
+ const VERSION = "0.8.24";
+ public $yellow; // access to API
+
+ // Handle initialisation
+ public function onLoad($yellow) {
+ $this->yellow = $yellow;
+ }
+
+ // Handle command
+ public function onCommand($command, $text) {
+ switch ($command) {
+ case "serve": $statusCode = $this->processCommandServe($command, $text); break;
+ default: $statusCode = 0;
+ }
+ return $statusCode;
+ }
+
+ // Handle command help
+ public function onCommandHelp() {
+ return "serve [url]";
+ }
+
+ // Process command to start web server
+ public function processCommandServe($command, $text) {
+ list($url) = $this->yellow->toolbox->getTextArguments($text);
+ if (is_string_empty($url)) $url = "http://localhost:8000/";
+ list($scheme, $address, $base) = $this->yellow->lookup->getUrlInformation($url);
+ if ($scheme=="http" && !is_string_empty($address) && is_string_empty($base)) {
+ if (!preg_match("/\:\d+$/", $address)) $address .= ":8000";
+ if ($this->checkServerSettings("$scheme://$address/")) {
+ echo "Starting web server. Open a web browser and go to $scheme://$address/\n";
+ echo "Press Ctrl+C to quit...\n";
+ exec(PHP_BINARY." -S $address yellow.php 2>&1", $outputLines, $returnStatus);
+ $statusCode = $returnStatus!=0 ? 500 : 200;
+ if ($statusCode!=200) {
+ $output = !is_array_empty($outputLines) ? end($outputLines) : "Please check arguments!";
+ if (preg_match("/^\[(.*?)\]\s*(.*)$/", $output, $matches)) $output = $matches[2];
+ echo "ERROR starting web server: $output\n";
+ }
+ } else {
+ $statusCode = 400;
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ echo "ERROR starting web server: Please configure `CoreServerUrl: auto` in file '$fileName'!\n";
+ }
+ } else {
+ $statusCode = 400;
+ echo "Yellow $command: Invalid arguments\n";
+ }
+ return $statusCode;
+ }
+
+ // Check server settings
+ public function checkServerSettings($url) {
+ return $this->yellow->system->get("coreServerUrl")=="auto" ||
+ $this->yellow->system->get("coreServerUrl")==$url;
+ }
+}
diff --git a/system/extensions/stockholm.php b/system/extensions/stockholm.php
@@ -1,12 +1,9 @@
<?php
-// Stockholm extension, https://github.com/datenstrom/yellow-extensions/tree/master/themes/stockholm
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Stockholm extension, https://github.com/annaesvensson/yellow-stockholm
class YellowStockholm {
- const VERSION = "0.8.8";
- const TYPE = "theme";
- public $yellow; //access to API
+ const VERSION = "0.8.14";
+ public $yellow; // access to API
// Handle initialisation
public function onLoad($yellow) {
@@ -15,12 +12,11 @@ class YellowStockholm {
// Handle update
public function onUpdate($action) {
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
if ($action=="install") {
$this->yellow->system->save($fileName, array("theme" => "stockholm"));
} elseif ($action=="uninstall" && $this->yellow->system->get("theme")=="stockholm") {
- $theme = reset(array_diff($this->yellow->extensions->getExtensions("theme"), array("stockholm")));
- $this->yellow->system->save($fileName, array("theme" => $theme));
+ $this->yellow->system->save($fileName, array("theme" => $this->yellow->system->getDifferent("theme")));
}
}
}
diff --git a/system/extensions/update-current.ini b/system/extensions/update-current.ini
@@ -0,0 +1,131 @@
+# Datenstrom Yellow update settings
+
+Extension: Core
+Version: 0.8.125
+Description: Core functionality of your website.
+DownloadUrl: https://github.com/annaesvensson/yellow-core/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-core
+DocumentationLanguage: en, de, sv
+Published: 2023-10-27 17:02:46
+Developer: Anna Svensson
+Tag: feature
+system/extensions/core.php: core.php, create, update
+system/layouts/default.html: default.html, create, update, careful
+system/layouts/error.html: error.html, create, update, careful
+system/layouts/header.html: header.html, create, update, careful
+system/layouts/footer.html: footer.html, create, update, careful
+system/layouts/navigation.html: navigation.html, create, update, careful
+system/layouts/pagination.html: pagination.html, create, update, careful
+
+Extension: Edit
+Version: 0.8.77
+Description: Edit your website in a web browser.
+DownloadUrl: https://github.com/annaesvensson/yellow-edit/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-edit
+DocumentationLanguage: en, de, sv
+Published: 2023-12-20 23:19:46
+Developer: Anna Svensson
+Tag: feature
+system/extensions/edit.php: edit.php, create, update
+system/extensions/edit.css: edit.css, create, update
+system/extensions/edit.js: edit.js, create, update
+system/extensions/edit-stack.svg: edit-stack.svg, create, update
+system/extensions/edit.woff: edit.woff, delete
+content/shared/page-new-default.md: page-new-default.md, create, optional
+
+Extension: Generate
+Version: 0.8.52
+Description: Generate a static website.
+DownloadUrl: https://github.com/annaesvensson/yellow-generate/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-generate
+DocumentationLanguage: en, de, sv
+Published: 2023-06-09 15:56:36
+Developer: Anna Svensson
+Tag: feature
+system/extensions/generate.php: generate.php, create, update
+
+Extension: Image
+Version: 0.8.19
+Description: Add images and thumbnails.
+DownloadUrl: https://github.com/annaesvensson/yellow-image/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-image
+DocumentationLanguage: en, de, sv
+Published: 2023-04-16 23:50:53
+Developer: Anna Svensson
+Tag: feature
+system/extensions/image.php: image.php, create, update
+media/images/photo.jpg: photo.jpg, create, optional
+media/thumbnails/photo-100x40.jpg: photo-100x40.jpg, create, optional
+
+Extension: Install
+Version: 0.8.93
+Description: Install a brand new website.
+DownloadUrl: https://github.com/annaesvensson/yellow-install/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-install
+DocumentationLanguage: en, de, sv
+Published: 2023-12-04 20:53:04
+Developer: Anna Svensson
+Status: unlisted
+system/extensions/install.php: install.php, create
+system/extensions/install-language.bin: install-language.bin, compress @source/yellow-language/, create
+system/extensions/install-wiki.bin: install-wiki.bin, compress @source/yellow-wiki/, create
+system/extensions/install-blog.bin: install-blog.bin, compress @source/yellow-blog/, create
+system/extensions/yellow-system.ini: yellow-system.ini, create
+system/extensions/yellow-user.ini: yellow-user.ini, create
+system/extensions/yellow-language.ini: yellow-language.ini, create
+content/1-home/page.md: 1-home-page.md, create
+content/9-about/page.md: 9-about-page.md, create
+content/shared/page-error-404.md: page-error-404.md, create
+media/downloads/yellow.pdf: yellow.pdf, create
+./yellow.php: yellow.php, create
+./robots.txt: robots.txt, create
+
+Extension: Markdown
+Version: 0.8.26
+Description: Text formatting for humans.
+DownloadUrl: https://github.com/annaesvensson/yellow-markdown/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-markdown
+DocumentationLanguage: en, de, sv
+Published: 2023-09-18 20:49:33
+Developer: Anna Svensson
+Tag: feature
+system/extensions/markdown.php: markdown.php, create, update
+
+Extension: Serve
+Version: 0.8.24
+Description: Built-in web server.
+DownloadUrl: https://github.com/annaesvensson/yellow-serve/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-serve
+DocumentationLanguage: en, de, sv
+Published: 2023-05-25 22:35:15
+Developer: Anna Svensson
+Tag: feature
+system/extensions/serve.php: serve.php, create, update
+
+Extension: Stockholm
+Version: 0.8.14
+Description: Stockholm is a clean theme.
+DownloadUrl: https://github.com/annaesvensson/yellow-stockholm/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-stockholm
+DocumentationLanguage: en, de, sv
+Published: 2022-10-20 12:44:02
+Designer: Anna Svensson
+Tag: theme
+system/extensions/stockholm.php: stockholm.php, create, update
+system/themes/stockholm.css: stockholm.css, create, update, careful
+system/themes/stockholm.png: stockholm.png, create
+system/themes/stockholm-opensans-bold.woff: stockholm-opensans-bold.woff, create, update, careful
+system/themes/stockholm-opensans-light.woff: stockholm-opensans-light.woff, create, update, careful
+system/themes/stockholm-opensans-regular.woff: stockholm-opensans-regular.woff, create, update, careful
+
+Extension: Update
+Version: 0.8.97
+Description: Keep your website up to date.
+DownloadUrl: https://github.com/annaesvensson/yellow-update/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-update
+DocumentationLanguage: en, de, sv
+Published: 2023-11-24 21:13:15
+Developer: Anna Svensson
+Tag: feature
+system/extensions/update.php: update.php, create, update
+system/extensions/updatepatch.bin: updatepatch.php, create, additional
diff --git a/system/extensions/update-latest.ini b/system/extensions/update-latest.ini
@@ -0,0 +1,775 @@
+# Datenstrom Yellow update settings
+
+Extension: Berlin
+Version: 0.8.14
+Description: Berlin is a theme inspired by Dieter Rams.
+DownloadUrl: https://github.com/annaesvensson/yellow-berlin/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-berlin
+DocumentationLanguage: en, de, sv
+Published: 2022-10-20 12:44:35
+Designer: Anna Svensson
+Tag: theme
+system/extensions/berlin.php: berlin.php, create, update
+system/layouts/berlin-default.html: berlin-default.html, create, update, careful
+system/themes/berlin.css: berlin.css, create, update, careful
+system/themes/berlin.png: berlin.png, create
+system/themes/berlin-opensans-bold.woff: berlin-opensans-bold.woff, create, update, careful
+system/themes/berlin-opensans-light.woff: berlin-opensans-light.woff, create, update, careful
+system/themes/berlin-opensans-regular.woff: berlin-opensans-regular.woff, create, update, careful
+
+Extension: Blog
+Version: 0.8.30
+Description: Blog for your website.
+DownloadUrl: https://github.com/annaesvensson/yellow-blog/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-blog
+DocumentationLanguage: en, de, sv
+Published: 2023-11-01 17:49:02
+Developer: Anna Svensson
+Tag: feature
+system/extensions/blog.php: blog.php, create, update
+system/layouts/blog.html: blog.html, create, update, careful
+system/layouts/blog-start.html: blog-start.html, create, update, careful
+content/shared/page-new-blog.md: page-new-blog.md, create, optional
+content/2-blog/page.md: page.md, create, optional
+content/2-blog/2020-04-07-blog-example-page.md: 2020-04-07-blog-example-page.md, create, optional
+content/2-blog/2020-12-06-made-for-people.md: 2020-12-06-made-for-people.md, create, optional
+
+Extension: Breadcrumb
+Version: 0.8.10
+Description: Breadcrumb navigation.
+DownloadUrl: https://github.com/annaesvensson/yellow-breadcrumb/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-breadcrumb
+DocumentationLanguage: en, de, sv
+Published: 2022-10-24 17:50:24
+Developer: Anna Svensson
+Tag: feature
+system/extensions/breadcrumb.php: breadcrumb.php, create, update
+
+Extension: Bundle
+Version: 0.8.31
+Description: Bundle website files.
+DownloadUrl: https://github.com/annaesvensson/yellow-bundle/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-bundle
+DocumentationLanguage: en, de, sv
+Published: 2023-11-28 16:26:34
+Developer: Anna Svensson
+Tag: feature
+system/extensions/bundle.php: bundle.php, create, update
+
+Extension: Catalan
+Version: 0.8.43
+Description: Catalan language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/catalan.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/catalan
+Published: 2023-04-13 22:54:43
+Translator: Andreu Ferrer
+Tag: language
+system/extensions/catalan.php: catalan.php, create, update
+
+Extension: Check
+Version: 0.8.2
+Description: Find broken links.
+DownloadUrl: https://github.com/annaesvensson/yellow-check/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-check
+DocumentationLanguage: en, de, sv
+Published: 2023-06-09 15:55:12
+Developer: Anna Svensson
+Tag: feature
+system/extensions/check.php: check.php, create, update
+
+Extension: Chinese
+Version: 0.8.43
+Description: Chinese language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/chinese.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/chinese
+Published: 2023-04-13 22:53:25
+Translator: Hyson Lee
+Tag: language
+system/extensions/chinese.php: chinese.php, create, update
+
+Extension: Contact
+Version: 0.8.23
+Description: Email contact page.
+DownloadUrl: https://github.com/annaesvensson/yellow-contact/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-contact
+DocumentationLanguage: en, de, sv
+Published: 2023-05-18 17:59:29
+Developer: Anna Svensson
+Tag: feature
+system/extensions/contact.php: contact.php, create, update
+system/layouts/contact.html: contact.html, create, update, careful
+content/contact/page.md: page.md, create, optional
+
+Extension: Copenhagen
+Version: 0.8.15
+Description: Copenhagen is a beautiful theme.
+DownloadUrl: https://github.com/annaesvensson/yellow-copenhagen/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-copenhagen
+DocumentationLanguage: en, de, sv
+Published: 2023-08-21 09:04:08
+Designer: Anna Svensson
+Tag: theme
+system/extensions/copenhagen.php: copenhagen.php, create, update
+system/themes/copenhagen.css: copenhagen.css, create, update, careful
+system/themes/copenhagen.png: copenhagen.png, create
+
+Extension: Core
+Version: 0.8.125
+Description: Core functionality of your website.
+DownloadUrl: https://github.com/annaesvensson/yellow-core/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-core
+DocumentationLanguage: en, de, sv
+Published: 2023-10-27 17:02:46
+Developer: Anna Svensson
+Tag: feature
+system/extensions/core.php: core.php, create, update
+system/layouts/default.html: default.html, create, update, careful
+system/layouts/error.html: error.html, create, update, careful
+system/layouts/header.html: header.html, create, update, careful
+system/layouts/footer.html: footer.html, create, update, careful
+system/layouts/navigation.html: navigation.html, create, update, careful
+system/layouts/pagination.html: pagination.html, create, update, careful
+
+Extension: Czech
+Version: 0.8.43
+Description: Czech language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/czech.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/czech
+Published: 2023-04-13 22:59:43
+Translator: Ufo Vyhuleny
+Tag: language
+system/extensions/czech.php: czech.php, create, update
+
+Extension: Danish
+Version: 0.8.43
+Description: Danish language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/danish.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/danish
+Published: 2023-04-13 23:07:01
+Translator: David Garcia
+Tag: language
+system/extensions/danish.php: danish.php, create, update
+
+Extension: Draft
+Version: 0.8.18
+Description: Support for draft pages.
+DownloadUrl: https://github.com/annaesvensson/yellow-draft/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-draft
+DocumentationLanguage: en, de, sv
+Published: 2023-05-19 13:21:26
+Developer: Anna Svensson
+Tag: feature
+system/extensions/draft.php: draft.php, create, update
+
+Extension: Dutch
+Version: 0.8.43
+Description: Dutch language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/dutch.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/dutch
+Published: 2023-04-13 23:07:33
+Translator: Robin Vannieuwenhuijse
+Tag: language
+system/extensions/dutch.php: dutch.php, create, update
+
+Extension: Edit
+Version: 0.8.77
+Description: Edit your website in a web browser.
+DownloadUrl: https://github.com/annaesvensson/yellow-edit/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-edit
+DocumentationLanguage: en, de, sv
+Published: 2023-12-20 23:19:46
+Developer: Anna Svensson
+Tag: feature
+system/extensions/edit.php: edit.php, create, update
+system/extensions/edit.css: edit.css, create, update
+system/extensions/edit.js: edit.js, create, update
+system/extensions/edit-stack.svg: edit-stack.svg, create, update
+system/extensions/edit.woff: edit.woff, delete
+content/shared/page-new-default.md: page-new-default.md, create, optional
+
+Extension: Emoji
+Version: 0.8.14
+Description: Lots and lots of emoji.
+DownloadUrl: https://github.com/annaesvensson/yellow-emoji/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-emoji
+DocumentationLanguage: en, de, sv
+Published: 2022-11-16 10:24:57
+Developer: Anna Svensson
+Tag: feature
+system/extensions/emoji.php: emoji.php, create, update
+system/extensions/emoji.css: emoji.css, create, update
+system/extensions/emoji-default-stack.svg: emoji-default-stack.svg, create, update
+system/extensions/emoji-extra1-stack.svg: emoji-extra1-stack.svg, create, update
+system/extensions/emoji-extra2-stack.svg: emoji-extra2-stack.svg, create, update
+system/extensions/emoji-extra3-stack.svg: emoji-extra3-stack.svg, create, update
+system/extensions/emoji-extra4-stack.svg: emoji-extra4-stack.svg, create, update
+system/extensions/emoji-extra5-stack.svg: emoji-extra5-stack.svg, create, update
+system/extensions/emoji-extra6-stack.svg: emoji-extra6-stack.svg, create, update
+system/extensions/emoji-extra7-stack.svg: emoji-extra7-stack.svg, create, update
+system/extensions/emoji-flags-stack.svg: emoji-flags-stack.svg, create, update
+
+Extension: English
+Version: 0.8.43
+Description: English language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/english.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/english
+Published: 2023-04-13 22:54:43
+Translator: Mark Seuffert
+Tag: language
+system/extensions/english.php: english.php, create, update
+
+Extension: Feed
+Version: 0.8.25
+Description: Feed with recent changes.
+DownloadUrl: https://github.com/annaesvensson/yellow-feed/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-feed
+DocumentationLanguage: en, de, sv
+Published: 2023-11-04 01:58:26
+Developer: Anna Svensson
+Tag: feature
+system/extensions/feed.php: feed.php, create, update
+system/layouts/feed.html: feed.html, create, update, careful
+content/feed/page.md: page.md, create, optional
+
+Extension: French
+Version: 0.8.43
+Description: French language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/french.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/french
+Published: 2023-04-13 23:07:59
+Translator: Juh Nibreh
+Tag: language
+system/extensions/french.php: french.php, create, update
+
+Extension: Gallery
+Version: 0.8.18
+Description: Image gallery with popup.
+DownloadUrl: https://github.com/annaesvensson/yellow-gallery/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-gallery
+DocumentationLanguage: en, de, sv
+Published: 2022-11-04 09:39:17
+Developer: Anna Svensson
+Tag: feature
+system/extensions/gallery.php: gallery.php, create, update
+system/extensions/gallery.js: gallery.js, create, update
+system/extensions/gallery.css: gallery.css, create, update
+system/extensions/gallery-photoswipe.min.js: gallery-photoswipe.min.js, create, update
+system/extensions/gallery-default-skin.png: gallery-default-skin.png, create, update
+system/extensions/gallery-default-skin.svg: gallery-default-skin.svg, create, update
+system/extensions/gallery-preloader.gif: gallery-preloader.gif, create, update
+
+Extension: Generate
+Version: 0.8.52
+Description: Generate a static website.
+DownloadUrl: https://github.com/annaesvensson/yellow-generate/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-generate
+DocumentationLanguage: en, de, sv
+Published: 2023-06-09 15:56:36
+Developer: Anna Svensson
+Tag: feature
+system/extensions/generate.php: generate.php, create, update
+
+Extension: German
+Version: 0.8.43
+Description: German language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/german.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/german
+Published: 2023-04-13 22:54:43
+Translator: David Fehrmann
+Tag: language
+system/extensions/german.php: german.php, create, update
+
+Extension: Googlecalendar
+Version: 0.8.17
+Description: Embed Google calendar.
+DownloadUrl: https://github.com/annaesvensson/yellow-googlecalendar/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-googlecalendar
+DocumentationLanguage: en, de, sv
+Published: 2023-04-18 01:21:56
+Developer: Anna Svensson
+Tag: feature
+system/extensions/googlecalendar.php: googlecalendar.php, create, update
+system/extensions/googlecalendar.js: googlecalendar.js, create, update
+system/extensions/googlecalendar.css: googlecalendar.css, create, update
+
+Extension: Googlemap
+Version: 0.8.9
+Description: Embed Google map.
+DownloadUrl: https://github.com/annaesvensson/yellow-googlemap/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-googlemap
+DocumentationLanguage: en, de, sv
+Published: 2023-04-18 01:19:46
+Developer: Anna Svensson
+Tag: feature
+system/extensions/googlemap.php: googlemap.php, create, update
+
+Extension: Helloworld
+Version: 0.8.15
+Description: Make animated text.
+DownloadUrl: https://github.com/schulle4u/yellow-helloworld/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/schulle4u/yellow-helloworld
+Published: 2020-08-13 16:12:30
+Developer: Steffen Schultz
+Tag: example, feature
+system/extensions/helloworld.php: helloworld.php, create, update
+system/extensions/helloworld.js: helloworld.js, create, update
+system/extensions/helloworld.css: helloworld.css, create, update
+
+Extension: Help
+Version: 0.8.23
+Description: Help for your website.
+DownloadUrl: https://github.com/annaesvensson/yellow-help/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-help
+DocumentationLanguage: en, de, sv
+Published: 2023-05-18 20:09:11
+Developer: Anna Svensson
+Tag: feature
+system/extensions/help.php: help.php, create, update
+content/9-help/api-for-developers.md: api-for-developers.md, create, optional, multi-language
+content/9-help/contributing-guidelines.md: contributing-guidelines.md, create, optional, multi-language
+content/9-help/how-to-change-the-content.md: how-to-change-the-content.md, create, optional, multi-language
+content/9-help/how-to-change-the-media.md: how-to-change-the-media.md, create, optional, multi-language
+content/9-help/how-to-change-the-system.md: how-to-change-the-system.md, create, optional, multi-language
+content/9-help/how-to-customise-a-language.md: how-to-customise-a-language.md, create, optional, multi-language
+content/9-help/how-to-customise-a-layout.md: how-to-customise-a-layout.md, create, optional, multi-language
+content/9-help/how-to-customise-a-theme.md: how-to-customise-a-theme.md, create, optional, multi-language
+content/9-help/how-to-get-started.md: how-to-get-started.md, create, optional, multi-language
+content/9-help/how-to-make-a-small-blog.md: how-to-make-a-small-blog.md, create, optional, multi-language
+content/9-help/how-to-make-a-small-website.md: how-to-make-a-small-website.md, create, optional, multi-language
+content/9-help/how-to-make-a-small-wiki.md: how-to-make-a-small-wiki.md, create, optional, multi-language
+content/9-help/page.md: page.md, create, optional, multi-language
+content/9-help/troubleshooting.md: troubleshooting.md, create, optional, multi-language
+content/9-help/what-s-new.md: what-s-new.md, create, optional, multi-language
+media/images/help-photo.jpg: help-photo.jpg, create, optional
+media/images/language-de.png: language-de.png, create, optional
+media/images/language-en.png: language-en.png, create, optional
+media/images/language-sv.png: language-sv.png, create, optional
+
+Extension: Highlight
+Version: 0.8.16
+Description: Highlight source code.
+DownloadUrl: https://github.com/annaesvensson/yellow-highlight/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-highlight
+DocumentationLanguage: en, de, sv
+Published: 2022-12-22 16:37:25
+Developer: Anna Svensson
+Tag: feature
+system/extensions/highlight.php: highlight.php, create, update
+system/extensions/highlight.css: highlight.css, create, update
+system/extensions/highlight-cpp.json: highlight-cpp.json, create, update
+system/extensions/highlight-css.json: highlight-css.json, create, update
+system/extensions/highlight-javascript.json: highlight-javascript.json, create, update
+system/extensions/highlight-json.json: highlight-json.json, create, update
+system/extensions/highlight-lua.json: highlight-lua.json, create, update
+system/extensions/highlight-php.json: highlight-php.json, create, update
+system/extensions/highlight-python.json: highlight-python.json, create, update
+system/extensions/highlight-xml.json: highlight-xml.json, create, update
+system/extensions/highlight-yaml.json: highlight-yaml.json, create, update
+
+Extension: Hungarian
+Version: 0.8.43
+Description: Hungarian language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/hungarian.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/hungarian
+Published: 2023-04-13 23:08:22
+Translator: Ádám Tuba
+Tag: language
+system/extensions/hungarian.php: hungarian.php, create, update
+
+Extension: Icon
+Version: 0.8.14
+Description: Icons and symbols.
+DownloadUrl: https://github.com/annaesvensson/yellow-icon/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-icon
+DocumentationLanguage: en, de, sv
+Published: 2022-11-16 11:09:07
+Developer: Anna Svensson
+Tag: feature
+system/extensions/icon.php: icon.php, create, update
+system/extensions/icon.css: icon.css, create, update
+system/extensions/icon.woff: icon.woff, create, update
+
+Extension: Image
+Version: 0.8.19
+Description: Add images and thumbnails.
+DownloadUrl: https://github.com/annaesvensson/yellow-image/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-image
+DocumentationLanguage: en, de, sv
+Published: 2023-04-16 23:50:53
+Developer: Anna Svensson
+Tag: feature
+system/extensions/image.php: image.php, create, update
+media/images/photo.jpg: photo.jpg, create, optional
+media/thumbnails/photo-100x40.jpg: photo-100x40.jpg, create, optional
+
+Extension: Instagram
+Version: 0.8.7
+Description: Embed Instagram photos.
+DocumentationUrl: https://github.com/GiovanniSalmeri/yellow-instagram
+DownloadUrl: https://github.com/GiovanniSalmeri/yellow-instagram/archive/refs/heads/main.zip
+Published: 2022-10-15 17:54:00
+Developer: Giovanni Salmeri
+Tag: feature
+system/extensions/instagram.php: instagram.php, create, update
+system/extensions/instagram.js: instagram.js, create, update
+
+Extension: Italian
+Version: 0.8.43
+Description: Italian language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/italian.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/italian
+Published: 2023-04-13 23:08:39
+Translator: Giovanni Salmeri
+Tag: language
+system/extensions/italian.php: italian.php, create, update
+
+Extension: Japanese
+Version: 0.8.43
+Description: Japanese language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/japanese.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/japanese
+Published: 2023-04-13 23:03:24
+Translator: Yuhko Senuma, Tomonori Ikeda
+Tag: language
+system/extensions/japanese.php: japanese.php, create, update
+
+Extension: Karlskrona
+Version: 0.8.18
+Description: Karlskrona is a semantic theme.
+DownloadUrl: https://github.com/pftnhr/yellow-karlskrona/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/pftnhr/yellow-karlskrona
+Published: 2023-09-11 20:00:13
+Designer: Robert Pfotenhauer
+Tag: theme
+system/extensions/karlskrona.php: karlskrona.php, create, update
+system/layouts/karlskrona-blog-start.html: karlskrona-blog-start.html, create, update, careful
+system/layouts/karlskrona-blog.html: karlskrona-blog.html, create, update, careful
+system/layouts/karlskrona-default.html: karlskrona-default.html, create, update, careful
+system/layouts/karlskrona-error.html: karlskrona-error.html, create, update, careful
+system/layouts/karlskrona-footer.html: karlskrona-footer.html, create, update, careful
+system/layouts/karlskrona-header.html: karlskrona-header.html, create, update, careful
+system/layouts/karlskrona-navigation.html: karlskrona-navigation.html, create, update, careful
+system/layouts/karlskrona-pagination.html: karlskrona-pagination.html, create, update, careful
+system/layouts/karlskrona-wiki-start.html: karlskrona-wiki-start.html, create, update, careful
+system/layouts/karlskrona-wiki.html: karlskrona-wiki.html, create, update, careful
+system/themes/karlskrona.css: karlskrona.css, create, update, careful
+system/themes/karlskrona.png: karlskrona.png, create
+
+Extension: Markdown
+Version: 0.8.26
+Description: Text formatting for humans.
+DownloadUrl: https://github.com/annaesvensson/yellow-markdown/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-markdown
+DocumentationLanguage: en, de, sv
+Published: 2023-09-18 20:49:33
+Developer: Anna Svensson
+Tag: feature
+system/extensions/markdown.php: markdown.php, create, update
+
+Extension: Meta
+Version: 0.8.17
+Description: Meta data for humans and machines.
+DownloadUrl: https://github.com/annaesvensson/yellow-meta/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-meta
+DocumentationLanguage: en, de, sv
+Published: 2023-05-19 00:58:40
+Developer: Anna Svensson
+Tag: feature
+system/extensions/meta.php: meta.php, create, update
+
+Extension: Norwegian
+Version: 0.8.43
+Description: Norwegian language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/norwegian.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/norwegian
+Published: 2023-04-13 23:08:53
+Translator: Per Arne Solvik
+Tag: language
+system/extensions/norwegian.php: norwegian.php, create, update
+
+Extension: Paris
+Version: 0.8.14
+Description: Paris is an elegant theme.
+DownloadUrl: https://github.com/annaesvensson/yellow-paris/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-paris
+DocumentationLanguage: en, de, sv
+Published: 2022-10-20 12:45:32
+Designer: Anna Svensson
+Tag: theme
+system/extensions/paris.php: paris.php, create, update
+system/layouts/paris-navigation.html: paris-navigation.html, create, update, careful
+system/themes/paris.css: paris.css, create, update, careful
+system/themes/paris.png: paris.png, create
+system/themes/paris-logo.png: paris-logo.png, create
+system/themes/paris-quote.png: paris-quote.png, create
+system/themes/paris-opensans-bold.woff: paris-opensans-bold.woff, create, update, careful
+system/themes/paris-opensans-light.woff: paris-opensans-light.woff, create, update, careful
+system/themes/paris-opensans-regular.woff: paris-opensans-regular.woff, create, update, careful
+
+Extension: Parsedown
+Version: 0.8.26
+Description: Text formatting for humans.
+DownloadUrl: https://github.com/annaesvensson/yellow-parsedown/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-parsedown
+DocumentationLanguage: en, de, sv
+Published: 2023-09-18 20:49:49
+Developer: Anna Svensson
+Tag: feature
+system/extensions/parsedown.php: parsedown.php, create, update
+
+Extension: Polish
+Version: 0.8.43
+Description: Polish language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/polish.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/polish
+Published: 2023-04-13 23:09:08
+Translator: Paweł Klockiewicz, Kanbeq
+Tag: language
+system/extensions/polish.php: polish.php, create, update
+
+Extension: Portuguese
+Version: 0.8.43
+Description: Portuguese language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/portuguese.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/portuguese
+Published: 2023-04-13 23:09:38
+Translator: Al Garcia
+Tag: language
+system/extensions/portuguese.php: portuguese.php, create, update
+
+Extension: Previousnext
+Version: 0.8.18
+Description: Show links to previous/next page.
+DownloadUrl: https://github.com/annaesvensson/yellow-previousnext/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-previousnext
+DocumentationLanguage: en, de, sv
+Published: 2023-04-03 09:37:00
+Developer: Anna Svensson
+Tag: feature
+system/extensions/previousnext.php: previousnext.php, create, update
+
+Extension: Private
+Version: 0.8.13
+Tag: feature, page, private, security
+Description: Support for password-protected pages.
+DownloadUrl: https://github.com/schulle4u/yellow-private/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/schulle4u/yellow-private
+DocumentationLanguage: en, de
+Published: 2023-05-22 14:35:25
+Developer: Steffen Schultz
+system/extensions/private.php: private.php, create, update
+
+Extension: Publish
+Version: 0.8.70
+Description: Make and publish extensions.
+DownloadUrl: https://github.com/annaesvensson/yellow-publish/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-publish
+DocumentationLanguage: en, de, sv
+Published: 2023-12-11 18:37:01
+Developer: Anna Svensson
+Tag: feature
+system/extensions/publish.php: publish.php, create, update
+
+Extension: Russian
+Version: 0.8.43
+Description: Russian language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/russian.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/russian
+Published: 2023-04-13 23:09:57
+Translator: Сергей Ворон
+Tag: language
+system/extensions/russian.php: russian.php, create, update
+
+Extension: Search
+Version: 0.8.29
+Description: Full-text search.
+DownloadUrl: https://github.com/annaesvensson/yellow-search/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-search
+DocumentationLanguage: en, de, sv
+Published: 2023-10-13 12:38:07
+Developer: Anna Svensson
+Tag: feature
+system/extensions/search.php: search.php, create, update
+system/layouts/search.html: search.html, create, update, careful
+content/search/page.md: page.md, create, optional
+
+Extension: Serve
+Version: 0.8.24
+Description: Built-in web server.
+DownloadUrl: https://github.com/annaesvensson/yellow-serve/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-serve
+DocumentationLanguage: en, de, sv
+Published: 2023-05-25 22:35:15
+Developer: Anna Svensson
+Tag: feature
+system/extensions/serve.php: serve.php, create, update
+
+Extension: Sitemap
+Version: 0.8.15
+Description: Sitemap with all pages.
+DownloadUrl: https://github.com/annaesvensson/yellow-sitemap/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-sitemap
+DocumentationLanguage: en, de, sv
+Published: 2023-10-26 16:26:39
+Developer: Anna Svensson
+Tag: feature
+system/extensions/sitemap.php: sitemap.php, create, update
+system/layouts/sitemap.html: sitemap.html, create, update, careful
+content/sitemap/page.md: page.md, create, optional
+
+Extension: Slider
+Version: 0.8.18
+Description: Image gallery with slider.
+DownloadUrl: https://github.com/annaesvensson/yellow-slider/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-slider
+DocumentationLanguage: en, de, sv
+Published: 2022-11-04 09:39:38
+Developer: Anna Svensson
+Tag: feature
+system/extensions/slider.php: slider.php, create, update
+system/extensions/slider.js: slider.js, create, update
+system/extensions/slider.css: slider.css, create, update
+system/extensions/slider-splide.min.js: slider-splide.min.js, create, update
+
+Extension: Slovak
+Version: 0.8.43
+Description: Slovak language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/slovak.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/slovak
+Published: 2023-04-13 23:10:38
+Translator: Ádám Tuba
+Tag: language
+system/extensions/slovak.php: slovak.php, create, update
+
+Extension: Spanish
+Version: 0.8.43
+Description: Spanish language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/spanish.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/spanish
+Published: 2023-04-13 23:11:01
+Translator: Al Garcia, David Garcia
+Tag: language
+system/extensions/spanish.php: spanish.php, create, update
+
+Extension: Stockholm
+Version: 0.8.14
+Description: Stockholm is a clean theme.
+DownloadUrl: https://github.com/annaesvensson/yellow-stockholm/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-stockholm
+DocumentationLanguage: en, de, sv
+Published: 2022-10-20 12:44:02
+Designer: Anna Svensson
+Tag: theme
+system/extensions/stockholm.php: stockholm.php, create, update
+system/themes/stockholm.css: stockholm.css, create, update, careful
+system/themes/stockholm.png: stockholm.png, create
+system/themes/stockholm-opensans-bold.woff: stockholm-opensans-bold.woff, create, update, careful
+system/themes/stockholm-opensans-light.woff: stockholm-opensans-light.woff, create, update, careful
+system/themes/stockholm-opensans-regular.woff: stockholm-opensans-regular.woff, create, update, careful
+
+Extension: Swedish
+Version: 0.8.43
+Description: Swedish language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/swedish.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/swedish
+Published: 2023-04-13 22:54:43
+Translator: Anna Svensson
+Tag: language
+system/extensions/swedish.php: swedish.php, create, update
+
+Extension: Toc
+Version: 0.8.11
+Description: Table of contents.
+DownloadUrl: https://github.com/annaesvensson/yellow-toc/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-toc
+DocumentationLanguage: en, de, sv
+Published: 2023-11-01 14:32:36
+Developer: Anna Svensson
+Tag: feature
+system/extensions/toc.php: toc.php, create, update
+
+Extension: Traffic
+Version: 0.8.32
+Description: Create traffic analytics from log files.
+DownloadUrl: https://github.com/annaesvensson/yellow-traffic/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-traffic
+DocumentationLanguage: en, de, sv
+Published: 2023-06-09 15:38:36
+Developer: Anna Svensson
+Tag: feature
+system/extensions/traffic.php: traffic.php, create, update
+
+Extension: Turkish
+Version: 0.8.43
+Description: Turkish language.
+DownloadUrl: https://github.com/annaesvensson/yellow-language/raw/main/downloads/turkish.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-language/tree/main/translations/turkish
+Published: 2023-04-13 23:11:16
+Translator: Osman Kars
+Tag: language
+system/extensions/turkish.php: turkish.php, create, update
+
+Extension: Update
+Version: 0.8.97
+Description: Keep your website up to date.
+DownloadUrl: https://github.com/annaesvensson/yellow-update/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-update
+DocumentationLanguage: en, de, sv
+Published: 2023-11-24 21:13:15
+Developer: Anna Svensson
+Tag: feature
+system/extensions/update.php: update.php, create, update
+system/extensions/updatepatch.bin: updatepatch.php, create, additional
+
+Extension: Wiki
+Version: 0.8.30
+Description: Wiki for your website.
+DownloadUrl: https://github.com/annaesvensson/yellow-wiki/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-wiki
+DocumentationLanguage: en, de, sv
+Published: 2023-11-01 17:49:10
+Developer: Anna Svensson
+Tag: feature
+system/extensions/wiki.php: wiki.php, create, update
+system/layouts/wiki.html: wiki.html, create, update, careful
+system/layouts/wiki-start.html: wiki-start.html, create, update, careful
+content/shared/page-new-wiki.md: page-new-wiki.md, create, optional
+content/2-wiki/page.md: page.md, create, optional
+content/2-wiki/wiki-example-page.md: wiki-example-page.md, create, optional
+
+Extension: Wittstock
+Version: 0.8.28
+Description: Wittstock is a classless theme.
+DownloadUrl: https://github.com/schulle4u/yellow-wittstock/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/schulle4u/yellow-wittstock
+DocumentationLanguage: en, de
+Published: 2023-10-27 13:55:23
+Designer: Steffen Schultz
+Tag: classless, theme
+system/extensions/wittstock.php: wittstock.php, create, update
+system/themes/wittstock.css: wittstock.css, create, update, careful
+system/layouts/wittstock-blog.html: wittstock-blog.html, create, update, careful
+system/layouts/wittstock-blog-start.html: wittstock-blog-start.html, create, update, careful
+system/layouts/wittstock-contact.html: wittstock-contact.html, create, update, careful
+system/layouts/wittstock-default.html: wittstock-default.html, create, update, careful
+system/layouts/wittstock-error.html: wittstock-error.html, create, update, careful
+system/layouts/wittstock-feed.html: wittstock-feed.html, create, update, careful
+system/layouts/wittstock-footer.html: wittstock-footer.html, create, update, careful
+system/layouts/wittstock-header.html: wittstock-header.html, create, update, careful
+system/layouts/wittstock-navigation.html: wittstock-navigation.html, create, update, careful
+system/layouts/wittstock-pagination.html: wittstock-pagination.html, create, update, careful
+system/layouts/wittstock-search.html: wittstock-search.html, create, update, careful
+system/layouts/wittstock-sitemap.html: wittstock-sitemap.html, create, update, careful
+system/layouts/wittstock-wiki.html: wittstock-wiki.html, create, update, careful
+system/layouts/wittstock-wiki-start.html: wittstock-wiki-start.html, create, update, careful
+
+Extension: Youtube
+Version: 0.8.7
+Description: Embed Youtube videos.
+DownloadUrl: https://github.com/annaesvensson/yellow-youtube/archive/refs/heads/main.zip
+DocumentationUrl: https://github.com/annaesvensson/yellow-youtube
+DocumentationLanguage: en, de, sv
+Published: 2023-04-18 01:25:49
+Developer: Anna Svensson
+Tag: feature
+system/extensions/youtube.php: youtube.php, create, update
diff --git a/system/extensions/update.php b/system/extensions/update.php
@@ -1,43 +1,59 @@
<?php
-// Update extension, https://github.com/datenstrom/yellow-extensions/tree/master/features/update
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Update extension, https://github.com/annaesvensson/yellow-update
class YellowUpdate {
- const VERSION = "0.8.23";
- const TYPE = "feature";
+ const VERSION = "0.8.97";
const PRIORITY = "2";
- public $yellow; //access to API
- public $updates; //number of updates
+ public $yellow; // access to API
+ public $extensions; // number of extensions
// Handle initialisation
public function onLoad($yellow) {
$this->yellow = $yellow;
- $this->yellow->system->setDefault("updateExtensionUrl", "https://github.com/datenstrom/yellow-extensions");
- $this->yellow->system->setDefault("updateExtensionDirectory", "/Users/yourname/Documents/GitHub/");
+ $this->yellow->system->setDefault("updateCurrentRelease", "none");
+ $this->yellow->system->setDefault("updateLatestUrl", "auto");
+ $this->yellow->system->setDefault("updateLatestFile", "update-latest.ini");
+ $this->yellow->system->setDefault("updateCurrentFile", "update-current.ini");
$this->yellow->system->setDefault("updateExtensionFile", "extension.ini");
- $this->yellow->system->setDefault("updateVersionFile", "version.ini");
- $this->yellow->system->setDefault("updateWaffleFile", "waffle.ini");
- $this->yellow->system->setDefault("updateNotification", "none");
+ $this->yellow->system->setDefault("updateEventPending", "none");
+ $this->yellow->system->setDefault("updateEventDaily", "0");
+ $this->yellow->system->setDefault("updateTrashTimeout", "7776660");
+ }
+
+ // Handle update
+ public function onUpdate($action) {
+ if ($action=="clean" || $action=="daily") {
+ $statusCode = 200;
+ $path = $this->yellow->system->get("coreExtensionDirectory");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.download$/", false, false) as $entry) {
+ if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
+ }
+ if ($statusCode==500) $this->yellow->toolbox->log("error", "Can't delete files in directory '$path'!");
+ $statusCode = 200;
+ $path = $this->yellow->system->get("coreTrashDirectory");
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, false) as $entry) {
+ $expire = $this->yellow->toolbox->getFileDeleted($entry) + $this->yellow->system->get("updateTrashTimeout");
+ if ($expire<=time() && !$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
+ }
+ foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", false, true) as $entry) {
+ $expire = $this->yellow->toolbox->getFileDeleted($entry) + $this->yellow->system->get("updateTrashTimeout");
+ if ($expire<=time() && !$this->yellow->toolbox->deleteDirectory($entry)) $statusCode = 500;
+ }
+ if ($statusCode==500) $this->yellow->toolbox->log("error", "Can't delete files in directory '$path'!");
+ }
}
// Handle request
public function onRequest($scheme, $address, $base, $location, $fileName) {
- $statusCode = 0;
- if ($this->yellow->lookup->isContentFile($fileName) && $this->isExtensionPending()) {
- $statusCode = $this->processRequestPending($scheme, $address, $base, $location, $fileName);
- }
- return $statusCode;
+ return $this->processRequestPending($scheme, $address, $base, $location, $fileName);
}
// Handle command
public function onCommand($command, $text) {
- $statusCode = 0;
- if ($this->isExtensionPending()) $statusCode = $this->processCommandPending();
+ $statusCode = $this->processCommandPending();
if ($statusCode==0) {
switch ($command) {
case "about": $statusCode = $this->processCommandAbout($command, $text); break;
- case "clean": $statusCode = $this->processCommandClean($command, $text); break;
case "install": $statusCode = $this->processCommandInstall($command, $text); break;
case "uninstall": $statusCode = $this->processCommandUninstall($command, $text); break;
case "update": $statusCode = $this->processCommandUpdate($command, $text); break;
@@ -49,379 +65,184 @@ class YellowUpdate {
// Handle command help
public function onCommandHelp() {
- $help = "about\n";
- $help .= "install [extension]\n";
- $help .= "uninstall [extension]\n";
- $help .= "update [extension]\n";
- return $help;
+ return array("about [extension]", "install [extension]", "uninstall [extension]", "update [extension]");
}
-
- // Handle update
- public function onUpdate($action) {
- if ($action=="update") { //TODO: remove later, converts old content settings
- if ($this->yellow->system->isExisting("multiLanguageMode")) {
- $coreMultiLanguageMode = $this->yellow->system->get("multiLanguageMode");
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
- $this->yellow->system->save($fileName, array("coreMultiLanguageMode" => $coreMultiLanguageMode));
- $path = $this->yellow->system->get("coreContentDirectory");
- foreach ($this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/^.*\.md$/", true, false) as $entry) {
- $fileData = $fileDataNew = $this->yellow->toolbox->readFile($entry);
- $fileStatusUnlisted = false;
- $tokens = explode("/", substru($entry, strlenu($path)));
- for ($i=0; $i<count($tokens)-1; ++$i) {
- if (!preg_match("/^[\d\-\_\.]+(.*)$/", $tokens[$i]) && $tokens[$i]!="shared") {
- $fileStatusUnlisted = true;
- break;
- }
- }
- $fileDataNew = preg_replace("/Status: hidden/i", "Status: shared", $fileDataNew);
- $fileDataNew = preg_replace("/Status: ignore/i", "Build: exclude", $fileDataNew);
- if ($fileStatusUnlisted && empty($this->yellow->toolbox->getMetaData($fileDataNew, "status"))) {
- $fileDataNew = $this->yellow->toolbox->setMetaData($fileDataNew, "status", "unlisted");
- }
- if ($fileData!=$fileDataNew) {
- $modified = $this->yellow->toolbox->getFileModified($entry);
- if (!$this->yellow->toolbox->deleteFile($entry) ||
- !$this->yellow->toolbox->createFile($entry, $fileDataNew) ||
- !$this->yellow->toolbox->modifyFile($entry, $modified)) {
- $this->yellow->log("error", "Can't write file '$entry'!");
- }
- }
- }
- }
- }
- if ($action=="update") { //TODO: remove later, converts old layout files
- if ($this->yellow->system->isExisting("coreLayoutDir")) {
- $path = $this->yellow->system->get("coreLayoutDir");
- foreach ($this->yellow->toolbox->getDirectoryEntriesRecursive($path, "/^.*\.html$/", true, false) as $entry) {
- $fileData = $fileDataNew = $this->yellow->toolbox->readFile($entry);
- $fileDataNew = str_replace("yellow->getLayoutArgs", "yellow->getLayoutArguments", $fileDataNew);
- $fileDataNew = str_replace("toolbox->getLocationArgs", "toolbox->getLocationArguments", $fileDataNew);
- $fileDataNew = str_replace("toolbox->getTextArgs", "toolbox->getTextArguments", $fileDataNew);
- $fileDataNew = str_replace("toolbox->normaliseArgs", "toolbox->normaliseArguments", $fileDataNew);
- $fileDataNew = str_replace("toolbox->isLocationArgs", "toolbox->isLocationArguments", $fileDataNew);
- $fileDataNew = str_replace("\$this->yellow->page->get(\"navigation\")", "\"navigation\"", $fileDataNew);
- $fileDataNew = str_replace("\$this->yellow->page->get(\"header\")", "\"header\"", $fileDataNew);
- $fileDataNew = str_replace("\$this->yellow->page->get(\"sidebar\")", "\"sidebar\"", $fileDataNew);
- $fileDataNew = str_replace("\$this->yellow->page->get(\"footer\")", "\"footer\"", $fileDataNew);
- if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($entry, $fileDataNew)) {
- $this->yellow->log("error", "Can't write file '$entry'!");
- }
- }
- }
- }
- if ($action=="update") { //TODO: remove later, converts old commandline
- if ($this->yellow->system->isExisting("coreStaticDir")) {
- $fileName = "yellow.php";
- $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName);
- $fileDataNew = str_replace("make websites", "make small websites", $fileDataNew);
- $fileDataNew = str_replace("command(\$argv[1], \$argv[2], \$argv[3], \$argv[4], \$argv[5], \$argv[6], \$argv[7])", "command()", $fileDataNew);
- if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileName, $fileDataNew)) {
- $this->yellow->log("error", "Can't write file '$fileName'!");
+
+ // Parse page content shortcut
+ public function onParseContentShortcut($page, $name, $text, $type) {
+ $output = null;
+ if ($name=="yellow" && $type=="inline") {
+ if ($text=="about") {
+ list($dummy, $settingsCurrent) = $this->getExtensionSettings(false);
+ $output = "Datenstrom Yellow ".YellowCore::RELEASE."<br />\n";
+ foreach ($settingsCurrent as $key=>$value) {
+ $output .= ucfirst($key)." ".$value->get("version")."<br />\n";
}
}
- }
- if ($action=="startup") {
- if ($this->yellow->system->get("updateNotification")!="none") {
- foreach (explode(",", $this->yellow->system->get("updateNotification")) as $token) {
- list($extension, $action) = $this->yellow->toolbox->getTextList($token, "/", 2);
- if ($this->yellow->extensions->isExisting($extension) && ($action!="startup" && $action!="uninstall")) {
- $value = $this->yellow->extensions->extensions[$extension];
- if (method_exists($value["obj"], "onUpdate")) $value["obj"]->onUpdate($action);
- }
- }
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
- $this->yellow->system->save($fileName, array("updateNotification" => "none"));
- $fileData = $this->yellow->toolbox->readFile($fileName);
- $fileDataHeader = $fileDataSettings = $fileDataFooter = "";
- $settings = new YellowDataCollection();
- $settings->exchangeArray($this->yellow->system->settingsDefaults->getArrayCopy());
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if (empty($fileDataHeader) && preg_match("/^\#/", $line)) {
- $fileDataHeader = $line;
- } elseif (!empty($matches[1]) && isset($settings[$matches[1]])) {
- $settings[$matches[1]] = $matches[2];
- } elseif (!empty($matches[1]) && substru($matches[1], 0, 1)!="#") {
- $fileDataFooter .= "# $line";
- } elseif (!empty($matches[1])) {
- $fileDataFooter .= $line;
+ if ($text=="release") $output = "Datenstrom Yellow ".YellowCore::RELEASE;
+ if ($text=="log") {
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreWebsiteFile");
+ $fileHandle = @fopen($fileName, "rb");
+ if ($fileHandle) {
+ clearstatcache(true, $fileName);
+ if (flock($fileHandle, LOCK_SH)) {
+ $dataBufferSize = 1024;
+ fseek($fileHandle, max(0, filesize($fileName) - $dataBufferSize));
+ $dataBuffer = fread($fileHandle, $dataBufferSize);
+ if (strlenb($dataBuffer)==$dataBufferSize) {
+ $dataBuffer = ($pos = strposu($dataBuffer, "\n")) ? substru($dataBuffer, $pos+1) : $dataBuffer;
+ }
+ flock($fileHandle, LOCK_UN);
}
+ fclose($fileHandle);
}
- unset($settings["coreSystemFile"]);
- foreach ($settings as $key=>$value) {
- if ($key=="coreStaticUrl") $fileDataSettings .= "\n";
- $fileDataSettings .= ucfirst($key).(strempty($value) ? ":\n" : ": $value\n");
- }
- if (!empty($fileDataHeader)) $fileDataHeader .= "\n";
- if (!empty($fileDataFooter)) $fileDataSettings .= "\n";
- $fileDataNew = $fileDataHeader.$fileDataSettings.$fileDataFooter;
- if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileName, $fileDataNew)) {
- $this->yellow->log("error", "Can't write file '$fileName'!");
- }
+ $output = str_replace("\n", "<br />\n", htmlspecialchars($dataBuffer));
}
}
+ return $output;
}
- // Process command to show website version and updates
+ // Process command to show current version
public function processCommandAbout($command, $text) {
- echo "Datenstrom Yellow ".YellowCore::VERSION."\n";
- list($statusCode, $dataCurrent) = $this->getExtensionsVersion();
- list($statusCode, $dataLatest) = $this->getExtensionsVersion(true);
- foreach ($dataCurrent as $key=>$value) {
- if (!isset($dataLatest[$key]) || strnatcasecmp($dataCurrent[$key], $dataLatest[$key])>=0) {
- echo ucfirst($key)." $value\n";
- } else {
- echo ucfirst($key)." $value - Update available\n";
+ $statusCode = 200;
+ $extensions = $this->getExtensionsFromText($text);
+ if (!is_array_empty($extensions)) {
+ list($statusCode, $settings) = $this->getExtensionAboutInformation($extensions);
+ if ($statusCode==200) {
+ foreach ($settings as $key=>$value) {
+ echo ucfirst($key)." ".$value->get("version")." - ".$this->getExtensionDescription($key, $value)."\n";
+ if ($value->isExisting("documentationUrl")) echo "Read more at ".$value->get("documentationUrl")."\n";
+ }
}
- }
- if ($statusCode!=200) echo "ERROR checking updates: ".$this->yellow->page->get("pageError")."\n";
- return $statusCode;
- }
-
- // Process command to clean downloads
- public function processCommandClean($command, $text) {
- $statusCode = 0;
- if ($command=="clean" && $text=="all") {
- $path = $this->yellow->system->get("coreExtensionDirectory");
- $regex = "/^.*\\".$this->yellow->system->get("coreDownloadExtension")."$/";
- foreach ($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, false) as $entry) {
- if (!$this->yellow->toolbox->deleteFile($entry)) $statusCode = 500;
+ if ($statusCode>=400) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n";
+ } else {
+ echo "Datenstrom Yellow ".YellowCore::RELEASE."\n";
+ list($statusCode, $settingsCurrent) = $this->getExtensionSettings(false);
+ foreach ($settingsCurrent as $key=>$value) {
+ echo ucfirst($key)." ".$value->get("version")."\n";
}
- if ($statusCode==500) echo "ERROR cleaning downloads: Can't delete files in directory '$path'!\n";
}
return $statusCode;
}
// Process command to install extensions
public function processCommandInstall($command, $text) {
- list($extensions) = $this->getExtensionInformation($text);
- if (!empty($extensions)) {
- $this->updates = 0;
- list($statusCode, $data) = $this->getInstallInformation($extensions);
- if ($statusCode==200) $statusCode = $this->downloadExtensions($data);
+ $extensions = $this->getExtensionsFromText($text);
+ if (!is_array_empty($extensions)) {
+ $this->extensions = 0;
+ list($statusCode, $settings) = $this->getExtensionInstallInformation($extensions);
+ if ($statusCode==200) $statusCode = $this->downloadExtensions($settings);
if ($statusCode==200) $statusCode = $this->updateExtensions("install");
- if ($statusCode>=400) echo "ERROR installing files: ".$this->yellow->page->get("pageError")."\n";
+ if ($statusCode>=400) echo "ERROR installing files: ".$this->yellow->page->errorMessage."\n";
echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated";
- echo ", $this->updates extension".($this->updates!=1 ? "s" : "")." installed\n";
+ echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." installed\n";
} else {
- $statusCode = $this->showExtensions();
+ list($statusCode, $settingsLatest) = $this->getExtensionSettings(true);
+ foreach ($settingsLatest as $key=>$value) {
+ echo ucfirst($key)." - ".$this->getExtensionDescription($key, $value)."\n";
+ }
+ if ($statusCode!=200) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n";
}
return $statusCode;
}
// Process command to uninstall extensions
public function processCommandUninstall($command, $text) {
- list($extensions) = $this->getExtensionInformation($text);
- if (!empty($extensions)) {
- $this->updates = 0;
- list($statusCode, $data) = $this->getUninstallInformation($extensions, "core, command, update");
- if ($statusCode==200) $statusCode = $this->removeExtensions($data);
- if ($statusCode>=400) echo "ERROR uninstalling files: ".$this->yellow->page->get("pageError")."\n";
+ $extensions = $this->getExtensionsFromText($text);
+ if (!is_array_empty($extensions)) {
+ $this->extensions = 0;
+ list($statusCode, $settings) = $this->getExtensionUninstallInformation($extensions, "core, update");
+ if ($statusCode==200) $statusCode = $this->removeExtensions($settings);
+ if ($statusCode>=400) echo "ERROR uninstalling files: ".$this->yellow->page->errorMessage."\n";
echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated";
- echo ", $this->updates extension".($this->updates!=1 ? "s" : "")." uninstalled\n";
+ echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." uninstalled\n";
} else {
- $statusCode = $this->showExtensions();
+ list($statusCode, $settingsCurrent) = $this->getExtensionSettings(false);
+ foreach ($settingsCurrent as $key=>$value) {
+ echo ucfirst($key)." - ".$this->getExtensionDescription($key, $value)."\n";
+ }
+ if ($statusCode!=200) echo "ERROR checking extensions: ".$this->yellow->page->errorMessage."\n";
}
return $statusCode;
}
-
+
// Process command to update website
public function processCommandUpdate($command, $text) {
- list($extensions, $force) = $this->getExtensionInformation($text);
- list($statusCode, $data) = $this->getUpdateInformation($extensions, $force);
- if ($statusCode!=200 || !empty($data)) {
- $this->updates = 0;
- if ($statusCode==200) $statusCode = $this->downloadExtensions($data);
- if ($statusCode==200) $statusCode = $this->updateExtensions("update", $force);
- if ($statusCode>=400) echo "ERROR updating files: ".$this->yellow->page->get("pageError")."\n";
- echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated";
- echo ", $this->updates update".($this->updates!=1 ? "s" : "")." installed\n";
+ $extensions = $this->getExtensionsFromText($text);
+ if (!is_array_empty($extensions)) {
+ list($statusCode, $settings) = $this->getExtensionUpdateInformation($extensions);
+ if ($statusCode!=200 || !is_array_empty($settings)) {
+ $this->extensions = 0;
+ if ($statusCode==200) $statusCode = $this->downloadExtensions($settings);
+ if ($statusCode==200) $statusCode = $this->updateExtensions("update");
+ if ($statusCode>=400) echo "ERROR updating files: ".$this->yellow->page->errorMessage."\n";
+ echo "Yellow $command: Website ".($statusCode!=200 ? "not " : "")."updated";
+ echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." updated\n";
+ } else {
+ echo "Your website is up to date\n";
+ }
} else {
- echo "Your website is up to date\n";
+ list($statusCode, $settings) = $this->getExtensionUpdateInformation(array("all"));
+ if (!is_array_empty($settings)) {
+ foreach ($settings as $key=>$value) {
+ echo ucfirst($key)." ".$value->get("version")."\n";
+ }
+ echo "Yellow $command: Updates are available. Please type 'php yellow.php update all'.\n";
+ } elseif ($statusCode!=200) {
+ echo "ERROR updating files: ".$this->yellow->page->errorMessage."\n";
+ } else {
+ echo "Your website is up to date\n";
+ }
}
return $statusCode;
}
- // Process command to install pending extension
+ // Process command for pending events
public function processCommandPending() {
- $statusCode = $this->updateExtensions("install");
- if ($statusCode!=200) echo "ERROR updating files: ".$this->yellow->page->get("pageError")."\n";
- echo "Your website has ".($statusCode!=200 ? "not " : "")."been updated: Please run command again\n";
- return $statusCode;
- }
-
- // Process request to install pending extension
- public function processRequestPending($scheme, $address, $base, $location, $fileName) {
- $statusCode = $this->updateExtensions("install");
- if ($statusCode==200) {
- $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
- $statusCode = $this->yellow->sendStatus(303, $location);
- }
- return $statusCode;
- }
-
- // Process update notification
- public function processUpdateNotification($extension, $action) {
- $statusCode = 200;
- if ($this->yellow->extensions->isExisting($extension) && $action=="uninstall") {
- $value = $this->yellow->extensions->extensions[$extension];
- if (method_exists($value["obj"], "onUpdate")) $value["obj"]->onUpdate($action);
- }
- $updateNotification = $this->yellow->system->get("updateNotification");
- if ($updateNotification=="none") $updateNotification = "";
- if (!empty($updateNotification)) $updateNotification .= ",";
- $updateNotification .= "$extension/$action";
- $fileName = $this->yellow->system->get("coreSettingDirectory").$this->yellow->system->get("coreSystemFile");
- if (!$this->yellow->system->save($fileName, array("updateNotification" => $updateNotification))) {
- $statusCode = 500;
- $this->yellow->page->error(500, "Can't write file '$fileName'!");
+ $statusCode = 0;
+ $this->extensions = 0;
+ $this->updatePatchPending();
+ $this->updateEventPending();
+ $statusCode = $this->updateExtensionPending();
+ if ($statusCode==303) {
+ echo "Detected ZIP file".($this->extensions!=1 ? "s" : "");
+ echo ", $this->extensions extension".($this->extensions!=1 ? "s" : "")." installed. Please run command again.\n";
}
return $statusCode;
}
- // Return extension information
- public function getExtensionInformation($text) {
- $extensions = array_unique(array_filter($this->yellow->toolbox->getTextArguments($text), "strlen"));
- $force = false;
- foreach ($extensions as $key=>$value) {
- if ($value=="force") {
- $force = true;
- unset($extensions[$key]);
- }
- }
- return array($extensions, $force);
- }
-
- // Return install information
- public function getInstallInformation($extensions) {
- $data = array();
- list($statusCodeCurrent, $dataCurrent) = $this->getExtensionsVersion();
- list($statusCodeLatest, $dataLatest) = $this->getExtensionsVersion(true, true);
- $statusCode = max($statusCodeCurrent, $statusCodeLatest);
- foreach ($extensions as $extension) {
- $found = false;
- foreach ($dataLatest as $key=>$value) {
- if (strtoloweru($key)==strtoloweru($extension)) {
- $data[$key] = $dataLatest[$key];
- $found = true;
- break;
- }
- }
- if (!$found) {
- $statusCode = 500;
- $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
- }
- }
- return array($statusCode, $data);
- }
-
- // Return uninstall information
- public function getUninstallInformation($extensions, $extensionsProtected) {
- $data = array();
- list($statusCodeCurrent, $dataCurrent) = $this->getExtensionsVersion();
- list($statusCodeLatest, $dataLatest) = $this->getExtensionsVersion(true, true);
- list($statusCodeRelevant, $dataRelevant) = $this->getExtensionsRelevant();
- $statusCode = max($statusCodeCurrent, $statusCodeLatest, $statusCodeRelevant);
- foreach ($extensions as $extension) {
- $found = false;
- foreach ($dataCurrent as $key=>$value) {
- if (strtoloweru($key)==strtoloweru($extension) && isset($dataLatest[$key]) && isset($dataRelevant[$key])) {
- $data[$key] = $dataRelevant[$key];
- $found = true;
- break;
- }
- }
- if (!$found) {
- $statusCode = 500;
- $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
- }
- }
- $protected = preg_split("/\s*,\s*/", $extensionsProtected);
- foreach ($data as $key=>$value) {
- if (in_array($key, $protected)) unset($data[$key]);
- }
- return array($statusCode, $data);
- }
-
- // Return update information
- public function getUpdateInformation($extensions, $force) {
- $data = array();
- list($statusCodeCurrent, $dataCurrent) = $this->getExtensionsVersion();
- list($statusCodeLatest, $dataLatest) = $this->getExtensionsVersion(true, true);
- list($statusCodeModified, $dataModified) = $this->getExtensionsModified();
- $statusCode = max($statusCodeCurrent, $statusCodeLatest, $statusCodeModified);
- if (empty($extensions)) {
- foreach ($dataCurrent as $key=>$value) {
- if (isset($dataLatest[$key])) {
- list($version, $dummy1, $dummy2) = $this->yellow->toolbox->getTextList($dataLatest[$key], ",", 3);
- if (strnatcasecmp($dataCurrent[$key], $version)<0) $data[$key] = $dataLatest[$key];
- if (isset($dataModified[$key]) && !empty($version) && $force) $data[$key] = $dataLatest[$key];
- }
- }
- } else {
- foreach ($extensions as $extension) {
- $found = false;
- foreach ($dataCurrent as $key=>$value) {
- if (isset($dataLatest[$key])) {
- list($version, $dummy1, $dummy2) = $this->yellow->toolbox->getTextList($dataLatest[$key], ",", 3);
- if (strtoloweru($key)==strtoloweru($extension) && !empty($version)) {
- $data[$key] = $dataLatest[$key];
- $dataModified = array_intersect_key($dataModified, $data);
- $found = true;
- break;
- }
- }
- }
- if (!$found) {
- $statusCode = 500;
- $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
- }
- }
- }
- if ($statusCode==200) {
- foreach (array_merge($dataModified, $data) as $key=>$value) {
- list($version, $dummy1, $dummy2) = $this->yellow->toolbox->getTextList($value, ",", 3);
- if (!isset($dataModified[$key]) || $force) {
- echo ucfirst($key)." $version\n";
- } else {
- echo ucfirst($key)." $version has been modified - Force update\n";
- }
+ // Process request for pending events
+ public function processRequestPending($scheme, $address, $base, $location, $fileName) {
+ $statusCode = 0;
+ if ($this->yellow->lookup->isContentFile($fileName)) {
+ $this->updatePatchPending();
+ $this->updateEventPending();
+ $statusCode = $this->updateExtensionPending();
+ if ($statusCode==303) {
+ $location = $this->yellow->lookup->normaliseUrl($scheme, $address, $base, $location);
+ $statusCode = $this->yellow->sendStatus(303, $location);
}
}
- return array($statusCode, $data);
- }
-
- // Show extensions
- public function showExtensions() {
- list($statusCode, $dataLatest) = $this->getExtensionsVersion(true, true);
- foreach ($dataLatest as $key=>$value) {
- list($dummy1, $dummy2, $text) = $this->yellow->toolbox->getTextList($value, ",", 3);
- echo ucfirst($key).": $text\n";
- }
- if ($statusCode!=200) echo "ERROR checking extensions: ".$this->yellow->page->get("pageError")."\n";
return $statusCode;
}
// Download extensions
- public function downloadExtensions($data) {
+ public function downloadExtensions($settings) {
$statusCode = 200;
$path = $this->yellow->system->get("coreExtensionDirectory");
- $fileExtension = $this->yellow->system->get("coreDownloadExtension");
- foreach ($data as $key=>$value) {
+ foreach ($settings as $key=>$value) {
$fileName = $path.$this->yellow->lookup->normaliseName($key, true, false, true).".zip";
- list($dummy1, $url, $dummy2) = $this->yellow->toolbox->getTextList($value, ",", 3);
- list($statusCode, $fileData) = $this->getExtensionFile($url);
- if (empty($fileData) || !$this->yellow->toolbox->createFile($fileName.$fileExtension, $fileData)) {
+ list($statusCode, $fileData) = $this->getExtensionFile($value->get("downloadUrl"));
+ if ($statusCode==200 && !$this->yellow->toolbox->createFile($fileName.".download", $fileData)) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
- break;
}
+ if ($statusCode!=200) break;
}
if ($statusCode==200) {
- foreach ($data as $key=>$value) {
+ foreach ($settings as $key=>$value) {
$fileName = $path.$this->yellow->lookup->normaliseName($key, true, false, true).".zip";
- if (!$this->yellow->toolbox->renameFile($fileName.$fileExtension, $fileName)) {
+ if (!$this->yellow->toolbox->renameFile($fileName.".download", $fileName)) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
}
@@ -431,12 +252,12 @@ class YellowUpdate {
}
// Update extensions
- public function updateExtensions($action, $force = false) {
+ public function updateExtensions($action) {
$statusCode = 200;
if (function_exists("opcache_reset")) opcache_reset();
$path = $this->yellow->system->get("coreExtensionDirectory");
foreach ($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.zip$/", true, false) as $entry) {
- $statusCode = max($statusCode, $this->updateExtensionArchive($entry, $action, $force));
+ $statusCode = max($statusCode, $this->updateExtensionArchive($entry, $action));
if (!$this->yellow->toolbox->deleteFile($entry)) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't delete file '$entry'!");
@@ -446,74 +267,67 @@ class YellowUpdate {
}
// Update extension from archive
- public function updateExtensionArchive($path, $action, $force = false) {
+ public function updateExtensionArchive($path, $action) {
$statusCode = 200;
$zip = new ZipArchive();
if ($zip->open($path)===true) {
- if (defined("DEBUG") && DEBUG>=2) echo "YellowUpdate::updateExtensionArchive file:$path<br/>\n";
- $extension = $version = $language = "";
- $modified = $lastModified = $lastPublished = 0;
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUpdate::updateExtensionArchive file:$path<br/>\n";
+ $pathBase = "";
if (preg_match("#^(.*\/).*?$#", $zip->getNameIndex(0), $matches)) $pathBase = $matches[1];
$fileData = $zip->getFromName($pathBase.$this->yellow->system->get("updateExtensionFile"));
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if (!empty($matches[1]) && !empty($matches[2]) && strposu($matches[1], "/")) {
- $fileName = $matches[1];
- if (is_file($fileName)) {
- $lastPublished = filemtime($fileName);
- break;
- }
- }
- }
- $rootPages = array();
- foreach ($this->yellow->content->scanLocation("") as $page) {
- if ($page->isAvailable() && $page->isVisible()) array_push($rootPages, $page);
- }
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
- if (lcfirst($matches[1])=="extension") $extension = lcfirst($matches[2]);
- if (lcfirst($matches[1])=="version") $version = lcfirst($matches[2]);
- if (lcfirst($matches[1])=="published") $modified = strtotime($matches[2]);
- if (lcfirst($matches[1])=="language") $language = $matches[2];
- if (!empty($matches[1]) && !empty($matches[2]) && strposu($matches[1], "/")) {
- $fileName = $matches[1];
- list($dummy, $entry, $flags) = $this->yellow->toolbox->getTextList($matches[2], ",", 3);
- foreach ($rootPages as $page) {
- list($fileNameSource, $fileNameDestination) = $this->getExtensionsFileNames($fileName, $entry, $flags, $language, $pathBase, $page);
+ $settings = $this->yellow->toolbox->getTextSettings($fileData, "");
+ list($extension, $version, $newModified, $oldModified) = $this->getExtensionInformation($settings);
+ if (!is_string_empty($extension) && !is_string_empty($version)) {
+ $statusCode = max($statusCode, $this->updateExtensionSettings($extension, $action, $settings));
+ $paths = $this->getExtensionDirectories($zip, $pathBase);
+ foreach ($this->getExtensionFileNames($settings) as $fileName) {
+ list($entry, $flags) = $this->yellow->toolbox->getTextList($settings[$fileName], ",", 2);
+ if (!$this->yellow->lookup->isContentFile($fileName)) {
+ $fileNameSource = $pathBase.$entry;
+ $fileData = $zip->getFromName($fileNameSource);
+ $lastModified = $this->yellow->toolbox->getFileModified($fileName);
+ $statusCode = max($statusCode, $this->updateExtensionFile($fileName, $fileData,
+ $newModified, $oldModified, $lastModified, $flags, $extension));
+ } else {
+ foreach ($this->getExtensionContentRootPages() as $page) {
+ list($fileNameSource, $fileNameDestination) = $this->getExtensionContentFileNames(
+ $fileName, $pathBase, $entry, $flags, $paths, $page);
$fileData = $zip->getFromName($fileNameSource);
$lastModified = $this->yellow->toolbox->getFileModified($fileNameDestination);
- $statusCode = $this->updateExtensionFile($fileNameDestination, $fileData,
- $modified, $lastModified, $lastPublished, $flags, $force, $extension);
+ $statusCode = max($statusCode, $this->updateExtensionFile($fileNameDestination, $fileData,
+ $newModified, $oldModified, $lastModified, $flags, $extension));
}
- if ($statusCode!=200) break;
}
}
+ $statusCode = max($statusCode, $this->updateExtensionNotification($extension, $action));
+ $this->yellow->toolbox->log($statusCode==200 ? "info" : "error", ucfirst($action)." extension '".ucfirst($extension)." $version'");
+ ++$this->extensions;
+ } else {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't detect file '$path'!");
}
$zip->close();
- $statusCode = max($statusCode, $this->processUpdateNotification($extension, $action));
- $this->yellow->log($statusCode==200 ? "info" : "error", ucfirst($action)." extension '".ucfirst($extension)." $version'");
- ++$this->updates;
} else {
$statusCode = 500;
- $this->yellow->page->error(500, "Can't open file '$path'!");
+ $this->yellow->page->error($statusCode, "Can't open file '$path'!");
}
return $statusCode;
}
// Update extension from file
- public function updateExtensionFile($fileName, $fileData, $modified, $lastModified, $lastPublished, $flags, $force, $extension) {
+ public function updateExtensionFile($fileName, $fileData, $newModified, $oldModified, $lastModified, $flags, $extension) {
$statusCode = 200;
- $fileName = $this->yellow->toolbox->normaliseTokens($fileName);
- if ($this->yellow->lookup->isValidFile($fileName) && !empty($extension)) {
+ $fileName = $this->yellow->lookup->normalisePath($fileName);
+ if ($this->yellow->lookup->isValidFile($fileName)) {
$create = $update = $delete = false;
- if (preg_match("/create/i", $flags) && !is_file($fileName) && !empty($fileData)) $create = true;
- if (preg_match("/update/i", $flags) && is_file($fileName) && !empty($fileData)) $update = true;
+ if (preg_match("/create/i", $flags) && !is_file($fileName) && !is_string_empty($fileData)) $create = true;
+ if (preg_match("/update/i", $flags) && is_file($fileName) && !is_string_empty($fileData)) $update = true;
if (preg_match("/delete/i", $flags) && is_file($fileName)) $delete = true;
- if (preg_match("/optional/i", $flags) && $this->yellow->extensions->isExisting($extension)) $create = $update = $delete = false;
- if (preg_match("/careful/i", $flags) && is_file($fileName) && $lastModified!=$lastPublished && !$force) $update = false;
+ if (preg_match("/optional/i", $flags) && $this->yellow->extension->isExisting($extension)) $create = $update = $delete = false;
+ if (preg_match("/careful/i", $flags) && is_file($fileName) && $lastModified!=$oldModified) $update = false;
if ($create) {
if (!$this->yellow->toolbox->createFile($fileName, $fileData, true) ||
- !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
+ !$this->yellow->toolbox->modifyFile($fileName, $newModified)) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
}
@@ -521,7 +335,7 @@ class YellowUpdate {
if ($update) {
if (!$this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory")) ||
!$this->yellow->toolbox->createFile($fileName, $fileData) ||
- !$this->yellow->toolbox->modifyFile($fileName, $modified)) {
+ !$this->yellow->toolbox->modifyFile($fileName, $newModified)) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
}
@@ -532,7 +346,7 @@ class YellowUpdate {
$this->yellow->page->error($statusCode, "Can't delete file '$fileName'!");
}
}
- if (defined("DEBUG") && DEBUG>=2) {
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
$debug = "action:".($create ? "create" : "").($update ? "update" : "").($delete ? "delete" : "");
if (!$create && !$update && !$delete) $debug = "action:none";
echo "YellowUpdate::updateExtensionFile file:$fileName $debug<br/>\n";
@@ -540,172 +354,586 @@ class YellowUpdate {
}
return $statusCode;
}
+
+ // Update pending patches
+ public function updatePatchPending() {
+ $fileName = $this->yellow->system->get("coreExtensionDirectory")."updatepatch.bin";
+ if (is_file($fileName)) {
+ if ($this->yellow->system->get("coreDebugMode")>=2) echo "YellowUpdate::updatePatchPending file:$fileName<br/>\n";
+ if (!$this->yellow->extension->isExisting("updatepatch")) {
+ require_once($fileName);
+ $this->yellow->extension->register("updatepatch", "YellowUpdatePatch");
+ }
+ if ($this->yellow->extension->isExisting("updatepatch")) {
+ $value = $this->yellow->extension->data["updatepatch"];
+ if (method_exists($value["object"], "onLoad")) $value["object"]->onLoad($this->yellow);
+ if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("patch");
+ }
+ unset($this->yellow->extension->data["updatepatch"]);
+ if (function_exists("opcache_reset")) opcache_reset();
+ if (!$this->yellow->toolbox->deleteFile($fileName)) {
+ $this->yellow->toolbox->log("error", "Can't delete file '$fileName'!");
+ }
+ }
+ }
+
+ // Update pending events
+ public function updateEventPending() {
+ if ($this->yellow->system->get("updateCurrentRelease")!="none") {
+ if ($this->yellow->system->get("updateCurrentRelease")!=YellowCore::RELEASE) {
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ if (!$this->yellow->system->save($fileName, array("updateCurrentRelease" => YellowCore::RELEASE))) {
+ $this->yellow->toolbox->log("error", "Can't write file '$fileName'!");
+ } else {
+ list($name, $version, $os) = $this->yellow->toolbox->detectServerInformation();
+ $product = "Datenstrom Yellow ".YellowCore::RELEASE;
+ $this->yellow->toolbox->log("info", "Update $product, PHP ".PHP_VERSION.", $name $version, $os");
+ }
+ }
+ if ($this->yellow->system->get("updateEventPending")!="none") {
+ foreach (explode(",", $this->yellow->system->get("updateEventPending")) as $token) {
+ list($extension, $action) = $this->yellow->toolbox->getTextList($token, "/", 2);
+ if ($this->yellow->extension->isExisting($extension) && $action!="uninstall") {
+ $value = $this->yellow->extension->data[$extension];
+ if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate($action);
+ }
+ }
+ $this->updateSystemSettings("all", $action);
+ $this->updateLanguageSettings("all", $action);
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ if (!$this->yellow->system->save($fileName, array("updateEventPending" => "none"))) {
+ $this->yellow->toolbox->log("error", "Can't write file '$fileName'!");
+ }
+ }
+ if ($this->yellow->system->get("updateEventDaily")<=time()) {
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate("daily");
+ }
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ if (!$this->yellow->system->save($fileName, array("updateEventDaily" => $this->getTimestampDaily()))) {
+ $this->yellow->toolbox->log("error", "Can't write file '$fileName'!");
+ }
+ }
+ }
+ }
+
+ // Update pending extensions
+ public function updateExtensionPending() {
+ $statusCode = 0;
+ $path = $this->yellow->system->get("coreExtensionDirectory");
+ if (!is_array_empty($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.zip$/", false, false))) {
+ $statusCode = $this->updateExtensions("install");
+ if ($statusCode==200) $statusCode = 303;
+ if ($statusCode>=400) {
+ $this->yellow->toolbox->log("error", $this->yellow->page->errorMessage);
+ $this->yellow->page->statusCode = 0;
+ $this->yellow->page->errorMessage = "";
+ $statusCode = 303;
+ }
+ }
+ return $statusCode;
+ }
- // Return extension file names
- public function getExtensionsFileNames($fileName, $entry, $flags, $language, $pathBase, $page) {
- if (preg_match("/multi-language/i", $flags)) {
- $languagesAvailable = preg_split("/\s*,\s*/", $language);
- $languagesWanted = array($page->get("language"), "en");
- foreach ($languagesWanted as $language) {
- if (in_array($language, $languagesAvailable)) {
- $languageFound = $language;
- break;
+ // Update extension settings
+ public function updateExtensionSettings($extension, $action, $settings) {
+ $statusCode = 200;
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateCurrentFile");
+ $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName);
+ if ($action=="install" || $action=="update") {
+ $settingsCurrent = $this->yellow->toolbox->getTextSettings($fileData, "extension");
+ $settingsCurrent[$extension] = new YellowArray();
+ foreach ($settings as $key=>$value) $settingsCurrent[$extension][$key] = $value;
+ $settingsCurrent->uksort("strnatcasecmp");
+ $fileDataNew = "";
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\#/", $line)) $fileDataNew = $line;
+ break;
+ }
+ foreach ($settingsCurrent as $extension=>$block) {
+ if (!is_string_empty($fileDataNew)) $fileDataNew .= "\n";
+ foreach ($block as $key=>$value) {
+ $fileDataNew .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
}
}
- $pathLanguage = $languageFound ? "$languageFound/" : "";
- $fileNameSource = $pathBase.$pathLanguage.basename($entry);
- } else {
- $fileNameSource = $pathBase.basename($entry);
+ } elseif ($action=="uninstall") {
+ $fileDataNew = $this->yellow->toolbox->unsetTextSettings($fileData, "extension", $extension);
}
- if ($this->yellow->system->get("coreMultiLanguageMode") && $this->yellow->lookup->isContentFile($fileName)) {
- $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
- $fileNameDestination = $page->fileName.substru($fileName, $contentDirectoryLength);
- } else {
- $fileNameDestination = $fileName;
+ if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileName, $fileDataNew)) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
}
- return array($fileNameSource, $fileNameDestination);
+ return $statusCode;
}
-
+
+ // Update system settings
+ public function updateSystemSettings($extension, $action) {
+ $statusCode = 200;
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName);
+ if ($action=="install" || $action=="update") {
+ $fileDataStart = $fileDataSettings = "";
+ $settings = new YellowArray();
+ $settings->exchangeArray($this->yellow->system->settingsDefaults->getArrayCopy());
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\#/", $line)) {
+ if (is_string_empty($fileDataStart)) $fileDataStart = $line."\n";
+ continue;
+ }
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ $settings[$matches[1]] = $matches[2];
+ }
+ }
+ }
+ foreach ($settings as $key=>$value) {
+ $fileDataSettings .= ucfirst($key).(is_string_empty($value) ? ":\n" : ": $value\n");
+ }
+ $fileDataNew = $fileDataStart.$fileDataSettings;
+ } elseif ($action=="uninstall") {
+ if (!is_string_empty($extension)) {
+ $fileDataNew = "";
+ $regex = "/^".ucfirst($extension)."[A-Z]+/";
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && preg_match($regex, $matches[1])) continue;
+ }
+ $fileDataNew .= $line;
+ }
+ }
+ }
+ if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileName, $fileDataNew)) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
+ }
+ return $statusCode;
+ }
+
+ // Update language settings
+ public function updateLanguageSettings($extension, $action) {
+ $statusCode = 200;
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreLanguageFile");
+ $fileData = $fileDataNew = $this->yellow->toolbox->readFile($fileName);
+ if ($action=="install" || $action=="update") {
+ $fileDataStart = $fileDataSettings = $language = "";
+ $settings = new YellowArray();
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\#/", $line)) {
+ if (is_string_empty($fileDataStart)) $fileDataStart = $line."\n";
+ continue;
+ }
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && !is_string_empty($matches[2])) {
+ if (lcfirst($matches[1])=="language") {
+ if (!is_array_empty($settings)) {
+ if (!is_string_empty($fileDataSettings)) $fileDataSettings .= "\n";
+ foreach ($settings as $key=>$value) {
+ $fileDataSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
+ }
+ }
+ $language = $matches[2];
+ $settings = new YellowArray();
+ $settings["language"] = $language;
+ $settings["languageLocale"] = "n/a";
+ $settings["languageDescription"] = "n/a";
+ $settings["languageTranslator"] = "Unknown";
+ foreach ($this->yellow->language->settingsDefaults as $key=>$value) {
+ $require = preg_match("/^([a-z]*)[A-Z]+/", $key, $tokens) ? $tokens[1] : "core";
+ if ($require=="language") $require = "core";
+ if ($this->yellow->extension->isExisting($require)) {
+ if ($this->yellow->language->isText($key, $language)) {
+ $settings[$key] = $this->yellow->language->getText($key, $language);
+ } else {
+ $settings[$key] = $this->yellow->language->getText($key, "en");
+ }
+ }
+ }
+ }
+ if (!is_string_empty($language)) {
+ $settings[$matches[1]] = $matches[2];
+ }
+ }
+ }
+ }
+ if (!is_array_empty($settings)) {
+ if (!is_string_empty($fileDataSettings)) $fileDataSettings .= "\n";
+ foreach ($settings as $key=>$value) {
+ $fileDataSettings .= (strposu($key, "/") ? $key : ucfirst($key)).": $value\n";
+ }
+ }
+ $fileDataNew = $fileDataStart.$fileDataSettings;
+ } elseif ($action=="uninstall") {
+ if (!is_string_empty($extension) && ucfirst($extension)!="Language") {
+ $fileDataNew = "";
+ $regex = "/^".ucfirst($extension)."[A-Z]+/";
+ foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
+ if (preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches)) {
+ if (!is_string_empty($matches[1]) && preg_match($regex, $matches[1])) continue;
+ }
+ $fileDataNew .= $line;
+ }
+ }
+ }
+ if ($fileData!=$fileDataNew && !$this->yellow->toolbox->createFile($fileName, $fileDataNew)) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
+ }
+ return $statusCode;
+ }
+
+ // Update extension notification
+ public function updateExtensionNotification($extension, $action) {
+ $statusCode = 200;
+ if ($this->yellow->extension->isExisting($extension) && $action=="uninstall") {
+ $value = $this->yellow->extension->data[$extension];
+ if (method_exists($value["object"], "onUpdate")) $value["object"]->onUpdate($action);
+ }
+ $updateEventPending = $this->yellow->system->get("updateEventPending");
+ if ($updateEventPending=="none") $updateEventPending = "";
+ if (!is_string_empty($updateEventPending)) $updateEventPending .= ",";
+ $updateEventPending .= "$extension/$action";
+ $fileName = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("coreSystemFile");
+ if (!$this->yellow->system->save($fileName, array("updateEventPending" => $updateEventPending))) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
+ }
+ return $statusCode;
+ }
+
// Remove extensions
- public function removeExtensions($data) {
+ public function removeExtensions($settings) {
$statusCode = 200;
if (function_exists("opcache_reset")) opcache_reset();
- foreach ($data as $key=>$value) {
- foreach (preg_split("/\s*,\s*/", $value) as $fileName) {
- $statusCode = max($statusCode, $this->removeExtensionsFile($fileName, $key));
+ foreach ($settings as $extension=>$block) {
+ $statusCode = max($statusCode, $this->removeExtensionArchive($extension, "uninstall", $block));
+ }
+ return $statusCode;
+ }
+
+ // Remove extension archive
+ public function removeExtensionArchive($extension, $action, $settings) {
+ $statusCode = 200;
+ $fileNames = $this->getExtensionFileNames($settings, true);
+ if (!is_array_empty($fileNames)) {
+ $statusCode = max($statusCode, $this->updateExtensionNotification($extension, $action));
+ foreach ($fileNames as $fileName) {
+ $statusCode = max($statusCode, $this->removeExtensionFile($fileName));
+ }
+ if ($statusCode==200) {
+ $statusCode = max($statusCode, $this->updateExtensionSettings($extension, $action, $settings));
+ $statusCode = max($statusCode, $this->updateSystemSettings($extension, $action));
+ $statusCode = max($statusCode, $this->updateLanguageSettings($extension, $action));
}
- $statusCode = max($statusCode, $this->processUpdateNotification($key, "uninstall"));
- $version = $this->yellow->extensions->isExisting($key) ? $this->yellow->extensions->extensions[$key]["version"] : "";
- $this->yellow->log($statusCode==200 ? "info" : "error", "Uninstall extension '".ucfirst($key)." $version'");
- ++$this->updates;
+ $version = $settings->get("version");
+ $this->yellow->toolbox->log($statusCode==200 ? "info" : "error", ucfirst($action)." extension '".ucfirst($extension)." $version'");
+ ++$this->extensions;
+ } else {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Please delete extension '$extension' manually!");
}
return $statusCode;
}
-
- // Remove extensions file
- public function removeExtensionsFile($fileName, $extension) {
+
+ // Remove extension file
+ public function removeExtensionFile($fileName) {
$statusCode = 200;
- $fileName = $this->yellow->toolbox->normaliseTokens($fileName);
- if ($this->yellow->lookup->isValidFile($fileName) && !empty($extension)) {
+ $fileName = $this->yellow->lookup->normalisePath($fileName);
+ if ($this->yellow->lookup->isValidFile($fileName) && is_file($fileName)) {
if (!$this->yellow->toolbox->deleteFile($fileName, $this->yellow->system->get("coreTrashDirectory"))) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't delete file '$fileName'!");
}
- if (defined("DEBUG") && DEBUG>=2) {
- echo "YellowUpdate::removeExtensionsFile file:$fileName action:delete<br/>\n";
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowUpdate::removeExtensionFile file:$fileName action:delete<br/>\n";
}
}
return $statusCode;
}
+
+ // Return extensions from text, space separated
+ public function getExtensionsFromText($text) {
+ return array_unique(array_filter($this->yellow->toolbox->getTextArguments($text), "strlen"));
+ }
- // Return extensions version
- public function getExtensionsVersion($latest = false, $rawFormat = false) {
- $data = array();
- if ($latest) {
- $url = $this->yellow->system->get("updateExtensionUrl")."/raw/master/".$this->yellow->system->get("updateVersionFile");
- list($statusCode, $fileData) = $this->getExtensionFile($url);
- if ($statusCode==200) {
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if (!empty($matches[1]) && !empty($matches[2])) {
- $extension = lcfirst($matches[1]);
- list($version, $dummy1, $dummy2) = $this->yellow->toolbox->getTextList($matches[2], ",", 3);
- $data[$extension] = $rawFormat ? $matches[2] : $version;
- }
+ // Return extension about information
+ public function getExtensionAboutInformation($extensions) {
+ $settings = array();
+ list($statusCode, $settingsCurrent) = $this->getExtensionSettings(false);
+ $settingsCurrent["Datenstrom Yellow"] = new YellowArray();
+ $settingsCurrent["Datenstrom Yellow"]["version"] = YellowCore::RELEASE;
+ $settingsCurrent["Datenstrom Yellow"]["description"] = "Datenstrom Yellow is for people who make small websites.";
+ $settingsCurrent["Datenstrom Yellow"]["documentationUrl"] = "https://datenstrom.se/yellow/";
+ foreach ($extensions as $extension) {
+ $found = false;
+ if (strtoloweru($extension)=="yellow") $extension = "Datenstrom Yellow";
+ foreach ($settingsCurrent as $key=>$value) {
+ if (strtoloweru($key)==strtoloweru($extension)) {
+ $settings[$key] = $settingsCurrent[$key];
+ $found = true;
+ break;
}
}
- } else {
- $statusCode = 200;
- $data = $this->yellow->extensions->getData();
+ if (!$found) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
+ }
}
- return array($statusCode, $data);
+ return array($statusCode, $settings);
}
-
- // Return extensions relevant files
- public function getExtensionsRelevant() {
- $data = array();
- $url = $this->yellow->system->get("updateExtensionUrl")."/raw/master/".$this->yellow->system->get("updateWaffleFile");
- list($statusCode, $fileData) = $this->getExtensionFile($url);
- if ($statusCode==200) {
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if (!empty($matches[1]) && !empty($matches[2])) {
- $fileName = $matches[1];
- list($extension, $dummy1, $dummy2) = $this->yellow->toolbox->getTextList(lcfirst($matches[2]), ",", 3);
- if (!isset($data[$extension])) {
- $data[$extension] = $fileName;
- } else {
- $data[$extension] .= ",".$fileName;
- }
+
+ // Return extension install information
+ public function getExtensionInstallInformation($extensions) {
+ $settings = array();
+ list($statusCodeCurrent, $settingsCurrent) = $this->getExtensionSettings(false);
+ list($statusCodeLatest, $settingsLatest) = $this->getExtensionSettings(true);
+ $statusCode = max($statusCodeCurrent, $statusCodeLatest);
+ foreach ($extensions as $extension) {
+ $found = false;
+ foreach ($settingsLatest as $key=>$value) {
+ if (strtoloweru($key)==strtoloweru($extension)) {
+ if (!$settingsCurrent->isExisting($key)) $settings[$key] = $settingsLatest[$key];
+ $found = true;
+ break;
}
}
+ if (!$found) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
+ }
}
- return array($statusCode, $data);
+ return array($statusCode, $settings);
+ }
+
+ // Return extension about information
+ public function getExtensionUninstallInformation($extensions, $extensionsProtected = "") {
+ $settings = array();
+ list($statusCode, $settingsCurrent) = $this->getExtensionSettings(false);
+ foreach ($extensions as $extension) {
+ $found = false;
+ foreach ($settingsCurrent as $key=>$value) {
+ if (strtoloweru($key)==strtoloweru($extension)) {
+ $settings[$key] = $settingsCurrent[$key];
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
+ }
+ }
+ $protected = preg_split("/\s*,\s*/", $extensionsProtected);
+ foreach ($settings as $key=>$value) {
+ if (in_array($key, $protected)) unset($settings[$key]);
+ }
+ return array($statusCode, $settings);
}
- // Return extensions modified files
- public function getExtensionsModified() {
- $data = array();
- $dataCurrent = $this->yellow->extensions->getData();
- $url = $this->yellow->system->get("updateExtensionUrl")."/raw/master/".$this->yellow->system->get("updateWaffleFile");
- list($statusCode, $fileData) = $this->getExtensionFile($url);
- if ($statusCode==200) {
- $extension = "";
- $lastModified = $lastPublished = 0;
- foreach ($this->yellow->toolbox->getTextLines($fileData) as $line) {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if (!empty($matches[1]) && !empty($matches[2])) {
- $fileName = $matches[1];
- list($extensionNew, $dummy, $flags) = $this->yellow->toolbox->getTextList(lcfirst($matches[2]), ",", 3);
- if ($extension!=$extensionNew) {
- $extension = $extensionNew;
- $lastPublished = $this->yellow->toolbox->getFileModified($fileName);
+ // Return extension update information
+ public function getExtensionUpdateInformation($extensions) {
+ $settings = array();
+ list($statusCodeCurrent, $settingsCurrent) = $this->getExtensionSettings(false);
+ list($statusCodeLatest, $settingsLatest) = $this->getExtensionSettings(true);
+ $statusCode = max($statusCodeCurrent, $statusCodeLatest);
+ if (in_array("all", $extensions)) {
+ foreach ($settingsCurrent as $key=>$value) {
+ if ($settingsLatest->isExisting($key)) {
+ $versionCurrent = $settingsCurrent[$key]->get("version");
+ $versionLatest = $settingsLatest[$key]->get("version");
+ if (strnatcasecmp($versionCurrent, $versionLatest)<0) {
+ $settings[$key] = $settingsLatest[$key];
}
- if (isset($dataCurrent[$extension])) {
- $lastModified = $this->yellow->toolbox->getFileModified($fileName);
- if (preg_match("/update/i", $flags) && preg_match("/careful/i", $flags) && $lastModified!=$lastPublished) {
- $data[$extension] = $dataCurrent[$extension];
- if (defined("DEBUG") && DEBUG>=2) {
- echo "YellowUpdate::getExtensionsModified detected file:$fileName extension:$extension<br/>\n";
- }
+ }
+ }
+ } else {
+ foreach ($extensions as $extension) {
+ $found = false;
+ foreach ($settingsCurrent as $key=>$value) {
+ if (strtoloweru($key)==strtoloweru($extension) && $settingsLatest->isExisting($key)) {
+ $versionCurrent = $settingsCurrent[$key]->get("version");
+ $versionLatest = $settingsLatest[$key]->get("version");
+ if (strnatcasecmp($versionCurrent, $versionLatest)<0) {
+ $settings[$key] = $settingsLatest[$key];
}
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't find extension '$extension'!");
+ }
+ }
+ }
+ return array($statusCode, $settings);
+ }
+
+ // Return extension settings
+ public function getExtensionSettings($latest) {
+ $statusCode = 200;
+ $settings = array();
+ if (!$latest) {
+ $fileNameCurrent = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateCurrentFile");
+ $fileData = $this->yellow->toolbox->readFile($fileNameCurrent);
+ $settings = $this->yellow->toolbox->getTextSettings($fileData, "extension");
+ foreach ($settings->getArrayCopy() as $key=>$value) {
+ if (!$this->yellow->extension->isExisting($key)) unset($settings[$key]);
+ }
+ foreach ($this->yellow->extension->data as $key=>$value) {
+ if (!$settings->isExisting($key)) $settings[$key] = new YellowArray();
+ $settings[$key]["extension"] = ucfirst($key);
+ $settings[$key]["version"] = $value["version"];
+ }
+ } else {
+ $fileNameLatest = $this->yellow->system->get("coreExtensionDirectory").$this->yellow->system->get("updateLatestFile");
+ $expire = $this->yellow->toolbox->getFileModified($fileNameLatest) + 60*10;
+ if ($expire<=time()) {
+ $url = $this->yellow->system->get("updateLatestUrl");
+ if ($url=="auto") $url = "https://raw.githubusercontent.com/datenstrom/yellow/main/system/extensions/update-latest.ini";
+ list($statusCode, $fileData) = $this->getExtensionFile($url);
+ if ($statusCode==200 && !$this->yellow->toolbox->createFile($fileNameLatest, $fileData)) {
+ $statusCode = 500;
+ $this->yellow->page->error($statusCode, "Can't write file '$fileNameLatest'!");
+ }
+ }
+ $fileData = $this->yellow->toolbox->readFile($fileNameLatest);
+ $settings = $this->yellow->toolbox->getTextSettings($fileData, "extension");
+ }
+ $settings->uksort("strnatcasecmp");
+ return array($statusCode, $settings);
+ }
+
+ // Return extension information
+ public function getExtensionInformation($settings) {
+ $extension = lcfirst($settings->get("extension"));
+ $version = $settings->get("version");
+ $newModified = strtotime($settings->get("published"));
+ $oldModified = 0;
+ $invalid = false;
+ foreach ($settings as $key=>$value) {
+ if (strposu($key, "/")) {
+ $fileName = $this->yellow->lookup->normalisePath($key);
+ if (!$this->yellow->lookup->isValidFile($fileName)) $invalid = true;
+ if ($oldModified==0) $oldModified = $this->yellow->toolbox->getFileModified($fileName);
+ }
+ }
+ if ($invalid) $extension = $version = "";
+ return array($extension, $version, $newModified, $oldModified);
+ }
+
+ // Return extension directories
+ public function getExtensionDirectories($zip, $pathBase) {
+ $paths = array();
+ for ($index=0; $index<$zip->numFiles; ++$index) {
+ $entry = substru($zip->getNameIndex($index), strlenu($pathBase));
+ if (preg_match("#^(.*\/).*?$#", $entry, $matches)) {
+ array_push($paths, $matches[1]);
+ }
+ }
+ return array_unique($paths);
+ }
+
+ // Return extension file names
+ public function getExtensionFileNames($settings, $reverse = false) {
+ $fileNames = array();
+ foreach ($settings as $key=>$value) {
+ if (strposu($key, "/")) array_push($fileNames, $key);
+ }
+ if ($reverse) $fileNames = array_reverse($fileNames);
+ return $fileNames;
+ }
+
+ // Return extension root pages for content files
+ public function getExtensionContentRootPages() {
+ $rootPages = array();
+ foreach ($this->yellow->content->scanLocation("") as $page) {
+ if ($page->isAvailable() && $page->isVisible()) array_push($rootPages, $page);
+ }
+ return $rootPages;
+ }
+
+ // Return extension files names for content files
+ public function getExtensionContentFileNames($fileName, $pathBase, $entry, $flags, $paths, $page) {
+ if (preg_match("/multi-language/i", $flags)) {
+ $pathMultiLanguage = "";
+ $languagesWanted = array($page->get("language"), "en");
+ foreach ($languagesWanted as $language) {
+ foreach ($paths as $path) {
+ if ($this->yellow->lookup->normaliseToken(rtrim($path, "/"))==$language) {
+ $pathMultiLanguage = $path;
+ break;
}
}
+ if (!is_string_empty($pathMultiLanguage)) break;
}
+ $fileNameSource = $pathBase.$pathMultiLanguage.$entry;
+ } else {
+ $fileNameSource = $pathBase.$entry;
}
- return array($statusCode, $data);
+ if ($this->yellow->system->get("coreMultiLanguageMode")) {
+ $contentDirectoryLength = strlenu($this->yellow->system->get("coreContentDirectory"));
+ $fileNameDestination = $page->fileName.substru($fileName, $contentDirectoryLength);
+ } else {
+ $fileNameDestination = $fileName;
+ }
+ return array($fileNameSource, $fileNameDestination);
+ }
+
+ // Return extension description including responsible developer/designer/translator
+ public function getExtensionDescription($key, $value) {
+ $description = $responsible = "";
+ if ($value->isExisting("description")) $description = $value->get("description");
+ if ($value->isExisting("developer")) $responsible = "Developed by ".$value["developer"].".";
+ if ($value->isExisting("designer")) $responsible = "Designed by ".$value["designer"].".";
+ if ($value->isExisting("translator")) $responsible = "Translated by ".$value["translator"].".";
+ if (is_string_empty($description)) $description = "No description available.";
+ return "$description $responsible";
}
// Return extension file
public function getExtensionFile($url) {
- $urlRequest = $url;
- if (preg_match("#^https://github.com/(.+)/raw/(.+)$#", $url, $matches)) $urlRequest = "https://raw.githubusercontent.com/".$matches[1]."/".$matches[2];
$curlHandle = curl_init();
- curl_setopt($curlHandle, CURLOPT_URL, $urlRequest);
- curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; DatenstromYellow/".YellowCore::VERSION."; SoftwareUpdater)");
+ curl_setopt($curlHandle, CURLOPT_URL, $this->getExtensionDownloadUrl($url));
+ curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; YellowUpdate/".YellowUpdate::VERSION."; SoftwareUpdater)");
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 30);
- $rawData = curl_exec($curlHandle);
+ $fileData = curl_exec($curlHandle);
$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
- $fileData = "";
+ $redirectUrl = ($statusCode>=300 && $statusCode<=399) ? curl_getinfo($curlHandle, CURLINFO_REDIRECT_URL) : "";
curl_close($curlHandle);
- if ($statusCode==200) {
- $fileData = $rawData;
- } elseif ($statusCode==0) {
- $statusCode = 500;
- list($scheme, $address) = $this->yellow->lookup->getUrlInformation($url);
- $this->yellow->page->error($statusCode, "Can't connect to server '$scheme://$address'!");
- } else {
+ if ($statusCode==0) {
+ $statusCode = 450;
+ $this->yellow->page->error($statusCode, "Can't connect to the update server!");
+ }
+ if ($statusCode!=450 && $statusCode!=200) {
$statusCode = 500;
$this->yellow->page->error($statusCode, "Can't download file '$url'!");
}
- if (defined("DEBUG") && DEBUG>=2) echo "YellowUpdate::getExtensionFile status:$statusCode url:$url<br/>\n";
+ if ($this->yellow->system->get("coreDebugMode")>=2 && !is_string_empty($redirectUrl)) {
+ echo "YellowUpdate::getExtensionFile redirected to url:$redirectUrl<br/>\n";
+ }
+ if ($this->yellow->system->get("coreDebugMode")>=2) {
+ echo "YellowUpdate::getExtensionFile status:$statusCode url:$url<br/>\n";
+ }
return array($statusCode, $fileData);
}
-
- // Check if extension pending
- public function isExtensionPending() {
- $path = $this->yellow->system->get("coreExtensionDirectory");
- return count($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.zip$/", false, false))>0;
+
+ // Return extension download URL, redirect to known URL if necessary
+ public function getExtensionDownloadUrl($url) {
+ if (preg_match("#^https://github.com/(.+)/archive/refs/heads/main.zip$#", $url, $matches)) {
+ $url = "https://codeload.github.com/".$matches[1]."/zip/refs/heads/main";
+ }
+ if (preg_match("#^https://github.com/(.+)/raw/main/(.+)$#", $url, $matches)) {
+ $url = "https://raw.githubusercontent.com/".$matches[1]."/main/".$matches[2];
+ }
+ return $url;
+ }
+
+ // Return time of next daily update
+ public function getTimestampDaily() {
+ $timeOffset = 0;
+ foreach (str_split($this->yellow->system->get("sitename")) as $char) {
+ $timeOffset = ($timeOffset+ord($char)) % 60;
+ }
+ return mktime(0, 0, 0) + 60*60*24 + $timeOffset;
}
}
diff --git a/system/extensions/yellow-language.ini b/system/extensions/yellow-language.ini
@@ -0,0 +1,5 @@
+# Datenstrom Yellow language settings
+
+Language: en
+CoreDateFormatMedium: Y-m-d
+picture.jpg: This is an example image
diff --git a/system/settings/system.ini b/system/extensions/yellow-system.ini
diff --git a/system/settings/user.ini b/system/extensions/yellow-user.ini
diff --git a/system/layouts/default.html b/system/layouts/default.html
@@ -2,7 +2,7 @@
<div class="content">
<div class="main" role="main">
<h1><?php echo $this->yellow->page->getHtml("titleContent") ?></h1>
-<?php echo $this->yellow->page->getContent() ?>
+<?php echo $this->yellow->page->getContentHtml() ?>
</div>
</div>
<?php $this->yellow->layout("footer") ?>
diff --git a/system/layouts/error.html b/system/layouts/error.html
@@ -2,7 +2,7 @@
<div class="content">
<div class="main" role="main">
<h1><?php echo $this->yellow->page->getHtml("titleContent") ?></h1>
-<?php echo $this->yellow->page->getContent() ?>
+<?php echo $this->yellow->page->getContentHtml() ?>
</div>
</div>
<?php $this->yellow->layout("footer") ?>
diff --git a/system/layouts/footer.html b/system/layouts/footer.html
@@ -1,11 +1,10 @@
<div class="footer" role="contentinfo">
<div class="siteinfo">
-<?php if ($page = $this->yellow->content->shared("footer")) $this->yellow->page->setPage("footer", $page) ?>
-<?php if ($this->yellow->page->isPage("footer")) echo $this->yellow->page->getPage("footer")->getContent() ?>
+<?php echo $this->yellow->page->getPage("footer")->getContentHtml() ?>
</div>
<div class="siteinfo-banner"></div>
</div>
</div>
-<?php echo $this->yellow->page->getExtra("footer") ?>
+<?php echo $this->yellow->page->getExtraHtml("footer") ?>
</body>
</html>
diff --git a/system/layouts/header.html b/system/layouts/header.html
@@ -7,17 +7,14 @@
<meta name="author" content="<?php echo $this->yellow->page->getHtml("author") ?>" />
<meta name="generator" content="Datenstrom Yellow" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
-<?php $resourceLocation = $this->yellow->system->get("coreServerBase").$this->yellow->system->get("coreResourceLocation") ?>
-<link rel="icon" type="image/png" href="<?php echo $resourceLocation.$this->yellow->page->getHtml("theme")."-icon.png" ?>" />
-<?php echo $this->yellow->page->getExtra("header") ?>
+<?php echo $this->yellow->page->getExtraHtml("header") ?>
</head>
<body>
<div class="<?php echo "page layout-".$this->yellow->page->getHtml("layout") ?>">
<div class="header" role="banner">
<div class="sitename">
<h1><a href="<?php echo $this->yellow->page->getBase(true)."/" ?>"><i class="sitename-logo"></i><span class="latex">M<sub>I</sub>K<sup>U</sup>L<sup>I</sup>.C<sub>Z</sub></span></a></h1>
-<?php if ($page = $this->yellow->content->shared("header")) $this->yellow->page->setPage("header", $page) ?>
-<?php if ($this->yellow->page->isPage("header")) echo $this->yellow->page->getPage("header")->getContent() ?>
+<?php echo $this->yellow->page->getPage("header")->getContentHtml() ?>
</div>
<div class="sitename-banner"></div>
<?php $this->yellow->layout("navigation") ?>
diff --git a/system/layouts/navigation.html b/system/layouts/navigation.html
@@ -1,6 +1,6 @@
<?php $pages = $this->yellow->content->top() ?>
<?php $this->yellow->page->setLastModified($pages->getModified()) ?>
-<div class="navigation" role="navigation">
+<div class="navigation" role="navigation" aria-label="<?php echo $this->yellow->language->getTextHtml("coreNavigation") ?>">
<ul>
<?php foreach ($pages as $page): ?>
<li><a<?php echo $page->isActive() ? " class=\"active\" aria-current=\"page\"" : "" ?> href="<?php echo $page->getLocation(true) ?>"><?php echo $page->getHtml("titleNavigation") ?></a></li>
diff --git a/system/layouts/pagination.html b/system/layouts/pagination.html
@@ -1,11 +1,11 @@
<?php list($name, $pages) = $this->yellow->getLayoutArguments() ?>
<?php if ($pages->isPagination()): ?>
-<div class="pagination" role="navigation">
+<div class="pagination" role="navigation" aria-label="<?php echo $this->yellow->language->getTextHtml("corePagination") ?>">
<?php if ($pages->getPaginationPrevious()): ?>
-<a class="previous" href="<?php echo $pages->getPaginationPrevious() ?>"><?php echo $this->yellow->text->getHtml("corePaginationPrevious") ?></a>
+<a class="previous" href="<?php echo $pages->getPaginationPrevious() ?>"><?php echo $this->yellow->language->getTextHtml("corePaginationPrevious") ?></a>
<?php endif ?>
<?php if ($pages->getPaginationNext()): ?>
-<a class="next" href="<?php echo $pages->getPaginationNext() ?>"><?php echo $this->yellow->text->getHtml("corePaginationNext") ?></a>
+<a class="next" href="<?php echo $pages->getPaginationNext() ?>"><?php echo $this->yellow->language->getTextHtml("corePaginationNext") ?></a>
<?php endif ?>
</div>
<?php endif ?>
diff --git a/system/resources/stockholm-opensans-license.txt b/system/resources/stockholm-opensans-license.txt
@@ -1,201 +0,0 @@
-Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-\ No newline at end of file
diff --git a/system/resources/stockholm.css b/system/resources/stockholm.css
@@ -1,386 +0,0 @@
-/* Stockholm extension, https://github.com/datenstrom/yellow-extensions/tree/master/themes/stockholm */
-/* Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se */
-/* This file may be used and distributed under the terms of the public license. */
-
-html, body, div, form, pre, span, tr, th, td, img {
- margin: 0;
- padding: 0;
- border: 0;
- vertical-align: baseline;
-}
-@font-face {
- font-family: "Open Sans";
- font-style: normal;
- font-weight: 300;
- src: url(stockholm-opensans-light.woff) format("woff");
-}
-@font-face {
- font-family: "Open Sans";
- font-style: normal;
- font-weight: 400;
- src: url(stockholm-opensans-regular.woff) format("woff");
-}
-@font-face {
- font-family: "Open Sans";
- font-style: normal;
- font-weight: 700;
- src: url(stockholm-opensans-bold.woff) format("woff");
-}
-body {
- margin: 1em;
- background-color: #fff;
- color: #666;
- font-family: "Open Sans", Helvetica, sans-serif;
- font-size: 1em;
- font-weight: 300;
- line-height: 1.5;
-}
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- color: #111;
- font-weight: 400;
- font-family: serif;
-}
-h1 {
- font-size: 2em;
-}
-hr {
- height: 1px;
- background: #ddd;
- border: 0;
-}
-strong {
- font-weight: bold;
-}
-code {
- font-size: 1.1em;
-}
-a {
- color: #07d;
- text-decoration: none;
-}
-a:hover {
- color: #07d;
- text-decoration: underline;
-}
-
-/* Content */
-
-.content h1 {
- margin: 1em 0;
-}
-.content h1 a {
- color: #111;
-}
-.content h1 a:hover {
- color: #111;
- text-decoration: none;
-}
-.content img {
- max-width: 100%;
- height: auto;
-}
-.content form {
- margin: 1em 0;
-}
-.content table {
- border-spacing: 0;
- border-collapse: collapse;
-}
-.content th {
- text-align: left;
- padding: 0.3em;
-}
-.content td {
- text-align: left;
- padding: 0.3em;
- border-top: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
-}
-.content code,
-.content pre {
- font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
- font-size: 90%;
-}
-.content code {
- padding: 0.15em 0.4em;
- margin: 0;
- background-color: #f7f7f7;
- border-radius: 3px;
-}
-.content pre > code {
- padding: 0;
- margin: 0;
- white-space: pre;
- background: transparent;
- border: 0;
- font-size: inherit;
-}
-.content pre {
- padding: 1em;
- overflow: auto;
- line-height: 1.45;
- background-color: #f7f7f7;
- border-radius: 3px;
-}
-.content blockquote {
- margin-left: 0;
- padding-left: 1em;
- border-left: 1px solid #ddd;
-}
-.content .notice1 {
- margin: 1em 0;
- padding: 10px 1em;
- background-color: #fffbf0;
- border-left: 10px solid #fb0;
-}
-.content .notice2 {
- margin: 1em 0;
- padding: 10px 1em;
- background-color: #fdf0f0;
- border-left: 10px solid #d00;
-}
-.content .notice3,
-.content .notice4,
-.content .notice5,
-.content .notice6 {
- margin: 1em 0;
- padding: 10px 1em;
- background-color: #f0f8fe;
- border-left: 10px solid #08e;
-}
-.content .flexible {
- position: relative;
- padding-top: 0;
- padding-bottom: 56.25%;
-}
-.content .flexible iframe {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
-}
-.content .task-list-item {
- list-style-type: none;
-}
-.content .task-list-item input {
- margin: 0 0.2em 0.25em -1.75em;
- vertical-align: middle;
-}
-.content .toc {
- margin: 0;
- padding: 0;
- list-style: none;
-}
-.content .wikipages ul,
-.content .wikitags ul,
-.content .wikilinks ul {
- padding: 0;
- list-style: none;
- column-width: 19em;
-}
-.content .entry-links .previous {
- margin-right: 1em;
-}
-.content .pagination .previous {
- margin-right: 1em;
-}
-.content .pagination {
- margin: 1em 0;
-}
-.content .left {
- float: left;
- margin: 0 1em 0 0;
-}
-.content .center {
- display: block;
- margin: 0 auto;
-}
-.content .right {
- float: right;
- margin: 0 0 0 1em;
-}
-.content .rounded {
- border-radius: 4px;
-}
-
-/* Header */
-
-.header {
- margin: 2em 0;
-}
-.header .sitename {
- display: block;
- float: left;
-}
-.header .sitename h1 {
- margin: 0;
- font-size: 1em;
- font-weight: 300;
-}
-.header .sitename h1 a {
- color: #666;
- border-bottom: solid 3px #fff;
- text-decoration: none;
- padding: 0.5em 0;
-}
-.header .sitename h1 a:hover {
- color: #07d;
- border-bottom: solid 3px #29f;
-}
-.header .sitename p {
- margin-top: 0;
- color: #666;
-}
-
-/* Navigation */
-
-.navigation {
- display: block;
- float: right;
- margin-top: 0.5em;
- font-size: 1em;
-}
-.navigation a {
- color: #666;
- border-bottom: solid 3px #fff;
- text-decoration: none;
- padding: 0.5em 0;
- margin: 0 0.5em;
-}
-.navigation a:hover {
- color: #07d;
- border-bottom: solid 3px #29f;
-}
-.navigation ul {
- margin: 0 -0.5em;
- padding: 0;
- list-style: none;
-}
-.navigation li {
- display: inline;
-}
-.navigation li a.active {
- border-bottom: solid 3px #29f;
-}
-.navigation-banner {
- clear: both;
-}
-
-/* Footer */
-
-.footer {
- margin: 2em 0;
-}
-.footer .siteinfo a {
- color: #07d;
-}
-.footer .siteinfo a:hover {
- color: #07d;
- text-decoration: underline;
-}
-
-/* Forms and buttons */
-
-.form-control {
- margin: 0;
- padding: 2px 4px;
- display: inline-block;
- min-width: 7em;
- background-color: #fff;
- color: #666;
- background-image: linear-gradient(to bottom, #fff, #fff);
- border: 1px solid #bbb;
- border-radius: 4px;
- font-size: 0.9em;
- font-family: inherit;
- font-weight: normal;
- line-height: normal;
-}
-.btn {
- margin: 0;
- padding: 4px 22px;
- display: inline-block;
- min-width: 7em;
- background-color: #eaeaea;
- color: #333333;
- background-image: linear-gradient(to bottom, #f8f8f8, #e1e1e1);
- border: 1px solid #bbb;
- border-color: #c1c1c1 #c1c1c1 #aaaaaa;
- border-radius: 4px;
- outline-offset: -2px;
- font-size: 0.9em;
- font-family: inherit;
- font-weight: normal;
- line-height: 1;
- text-align: center;
- text-decoration: none;
- box-sizing: border-box;
-}
-.btn:hover,
-.btn:focus,
-.btn:active {
- color: #333333;
- background-image: none;
- text-decoration: none;
-}
-.btn:active {
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-/* Responsive and print */
-
-.page {
- margin: 0 auto;
- max-width: 1000px;
-}
-
-@media screen and (min-width: 62em) {
- body {
- width: 60em;
- margin: 1em auto;
- }
- .page {
- margin: 0;
- max-width: none;
- }
-}
-@media screen and (max-width: 32em) {
- body {
- margin: 0.5em;
- font-size: 0.9em;
- }
- .content h1,
- .content h2 {
- font-size: 1.5em;
- }
-}
-@media print {
- .page {
- border: none !important;
- }
-}
-.latex sub {
- vertical-align: -0.6ex;
- margin-left: -0.125em;
- margin-right: -0.125em;
- font-size: 1em;
-}
-
-.latex {
- font-family: serif;
- font-size: 1.5em;
-}
-
-.latex sup {
- font-size: 0.7em;
- vertical-align: 0.3em;
- margin-left: -0.20em;
- margin-right: -0.2em;
-}
-
diff --git a/system/settings/text.ini b/system/settings/text.ini
@@ -1,5 +0,0 @@
-# Datenstrom Yellow text settings
-
-Language: en
-CoreDateFormatMedium: Y-m-d
-picture.jpg: This is an example image
diff --git a/system/resources/bundle-4ae45fbba2.min.js b/system/themes/bundle-4ae45fbba2.min.js
diff --git a/system/resources/stockholm-opensans-bold.woff b/system/themes/stockholm-opensans-bold.woff
Binary files differ.
diff --git a/system/resources/stockholm-opensans-light.woff b/system/themes/stockholm-opensans-light.woff
Binary files differ.
diff --git a/system/resources/stockholm-opensans-regular.woff b/system/themes/stockholm-opensans-regular.woff
Binary files differ.
diff --git a/system/themes/stockholm.css b/system/themes/stockholm.css
@@ -0,0 +1,410 @@
+/* Stockholm extension, https://github.com/annaesvensson/yellow-stockholm */
+
+/* Colors and fonts */
+
+:root {
+ --bg: #fff;
+ --code-bg: #f7f7f7;
+ --notice1-bg: #fffbf0;
+ --notice2-bg: #fdf0f0;
+ --notice3-bg: #f0f8fe;
+ --heading: #111;
+ --text: #666;
+ --code: #666;
+ --link: #07d;
+ --link-active: #29f;
+ --blockquote-accent: #29f;
+ --notice1-accent: #fb0;
+ --notice2-accent: #d00;
+ --notice3-accent: #08e;
+ --separator: #ddd;
+ --border: #bbb;
+ --font: "Open Sans", Helvetica, sans-serif;
+ --monospace-font: Consolas, Menlo, Courier, monospace;
+}
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+ src: url(stockholm-opensans-light.woff) format("woff");
+}
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(stockholm-opensans-regular.woff) format("woff");
+}
+@font-face {
+ font-family: "Open Sans";
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(stockholm-opensans-bold.woff) format("woff");
+}
+
+/* General */
+
+html, body, div, form, pre, span, tr, th, td, img {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ vertical-align: baseline;
+}
+body {
+ margin: 1em;
+ background-color: var(--bg);
+ color: var(--text);
+ font-family: var(--font);
+ font-size: 1em;
+ font-weight: 300;
+ line-height: 1.5;
+}
+h1, h2, h3, h4, h5, h6 {
+ color: var(--heading);
+ font-weight: 400;
+ font-family: serif;
+}
+h1 {
+ font-size: 2em;
+}
+hr {
+ height: 1px;
+ background: var(--separator);
+ border: 0;
+}
+strong {
+ font-weight: bold;
+}
+code {
+ font-size: 1.1em;
+}
+a {
+ color: var(--link);
+ text-decoration: none;
+}
+a:hover {
+ color: var(--link);
+ text-decoration: underline;
+}
+
+/* Content */
+
+.content h1 {
+ margin: 1em 0;
+}
+.content h1 a {
+ color: var(--heading);
+}
+.content h1 a:hover {
+ color: var(--heading);
+ text-decoration: none;
+}
+.content img {
+ max-width: 100%;
+ height: auto;
+}
+.content form {
+ margin: 1em 0;
+}
+.content table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+.content th {
+ text-align: left;
+ padding: 0.3em;
+ border-bottom: 1px solid var(--separator);
+}
+.content td {
+ text-align: left;
+ padding: 0.3em;
+ padding-right: 2em;
+}
+.content code,
+.content pre {
+ font-family: var(--monospace-font);
+ font-size: 90%;
+}
+.content code {
+ padding: 0.15em 0.4em;
+ margin: 0;
+ background-color: var(--code-bg);
+ color: var(--code);
+ border-radius: 3px;
+}
+.content pre > code {
+ padding: 0;
+ margin: 0;
+ white-space: pre;
+ background: transparent;
+ border: 0;
+ font-size: inherit;
+}
+.content pre {
+ padding: 1em;
+ overflow: auto;
+ line-height: 1.45;
+ background-color: var(--code-bg);
+ color: var(--code);
+ border-radius: 3px;
+}
+.content blockquote {
+ margin-left: 0;
+ padding-left: 1em;
+ font-weight: bold;
+ border-left: 3px solid var(--blockquote-accent);
+}
+.content .notice1 {
+ margin: 1em 0;
+ padding: 10px 1em;
+ background-color: var(--notice1-bg);
+ border-left: 10px solid var(--notice1-accent);
+}
+.content .notice2 {
+ margin: 1em 0;
+ padding: 10px 1em;
+ background-color: var(--notice2-bg);
+ border-left: 10px solid var(--notice2-accent);
+}
+.content .notice3,
+.content .notice4,
+.content .notice5,
+.content .notice6 {
+ margin: 1em 0;
+ padding: 10px 1em;
+ background-color: var(--notice3-bg);
+ border-left: 10px solid var(--notice3-accent);
+}
+.content .flexible {
+ position: relative;
+ padding-top: 0;
+ padding-bottom: 56.25%;
+}
+.content .flexible iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.content .task-list-item {
+ list-style-type: none;
+}
+.content .task-list-item input {
+ margin: 0 0.2em 0.25em -1.75em;
+ vertical-align: middle;
+}
+.content .toc {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+.content .wikipages ul,
+.content .wikitags ul,
+.content .wikilinks ul {
+ padding: 0;
+ list-style: none;
+ column-width: 19em;
+}
+.content .previousnext .previous {
+ margin-right: 1em;
+}
+.content .pagination .previous {
+ margin-right: 1em;
+}
+.content .pagination {
+ margin: 1em 0;
+}
+.content .left {
+ float: left;
+ margin: 0 1em 0 0;
+}
+.content .center {
+ display: block;
+ margin: 0 auto;
+}
+.content .right {
+ float: right;
+ margin: 0 0 0 1em;
+}
+.content .rounded {
+ border-radius: 4px;
+}
+
+/* Header */
+
+.header {
+ margin: 2em 0;
+}
+.header .sitename {
+ display: block;
+ float: left;
+}
+.header .sitename h1 {
+ margin: 0;
+ font-size: 1em;
+ font-weight: 300;
+}
+.header .sitename h1 a {
+ color: var(--text);
+ border-bottom: solid 3px var(--bg);
+ text-decoration: none;
+ padding: 0.5em 0;
+}
+.header .sitename h1 a:hover {
+ color: var(--link);
+ border-bottom: solid 3px var(--link-active);
+}
+.header .sitename p {
+ margin-top: 0;
+ color: var(--text);
+}
+
+/* Navigation */
+
+.navigation {
+ display: block;
+ float: right;
+ margin-top: 0.5em;
+ font-size: 1em;
+}
+.navigation a {
+ color: var(--text);
+ border-bottom: solid 3px var(--bg);
+ text-decoration: none;
+ padding: 0.5em 0;
+ margin: 0 0.5em;
+}
+.navigation a:hover {
+ color: var(--link);
+ border-bottom: solid 3px var(--link-active);
+}
+.navigation ul {
+ margin: 0 -0.5em;
+ padding: 0;
+ list-style: none;
+}
+.navigation li {
+ display: inline;
+}
+.navigation li a.active {
+ border-bottom: solid 3px var(--link-active);
+}
+.navigation-banner {
+ clear: both;
+}
+
+/* Footer */
+
+.footer {
+ margin: 2em 0;
+}
+.footer .siteinfo a {
+ color: var(--link);
+}
+.footer .siteinfo a:hover {
+ color: var(--link);
+ text-decoration: underline;
+}
+
+/* Forms and buttons */
+
+.form-control {
+ margin: 0;
+ padding: 2px 4px;
+ display: inline-block;
+ min-width: 7em;
+ background-color: var(--bg);
+ color: var(--text);
+ background-image: linear-gradient(to bottom, var(--bg), var(--bg));
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ font-size: 0.9em;
+ font-family: inherit;
+ font-weight: normal;
+ line-height: normal;
+}
+.btn {
+ margin: 0;
+ padding: 4px 22px;
+ display: inline-block;
+ min-width: 7em;
+ background-color: #eaeaea;
+ color: #333333;
+ background-image: linear-gradient(to bottom, #f8f8f8, #e1e1e1);
+ border: 1px solid var(--border);
+ border-color: #c1c1c1 #c1c1c1 #aaaaaa;
+ border-radius: 4px;
+ outline-offset: -2px;
+ font-size: 0.9em;
+ font-family: inherit;
+ font-weight: normal;
+ line-height: 1;
+ text-align: center;
+ text-decoration: none;
+ box-sizing: border-box;
+}
+.btn:hover,
+.btn:focus,
+.btn:active {
+ color: #333333;
+ background-image: none;
+ text-decoration: none;
+}
+.btn:active {
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+/* Responsive and print */
+
+.page {
+ margin: 0 auto;
+ max-width: 1000px;
+}
+
+@media screen and (min-width: 62em) {
+ body {
+ width: 60em;
+ margin: 1em auto;
+ }
+ .page {
+ margin: 0;
+ max-width: none;
+ }
+}
+@media screen and (max-width: 32em) {
+ body {
+ margin: 0.5em;
+ font-size: 0.9em;
+ }
+ .content h1,
+ .content h2 {
+ font-size: 1.5em;
+ }
+}
+@media print {
+ .page {
+ border: none !important;
+ }
+}
+.latex sub {
+ vertical-align: -0.6ex;
+ margin-left: -0.125em;
+ margin-right: -0.125em;
+ font-size: 1em;
+}
+
+.latex {
+ font-family: serif;
+ font-size: 1.5em;
+}
+
+.latex sup {
+ font-size: 0.7em;
+ vertical-align: 0.3em;
+ margin-left: -0.20em;
+ margin-right: -0.2em;
+}
+
diff --git a/system/resources/stockholm-icon.png b/system/themes/stockholm.png
Binary files differ.
diff --git a/yellow.php b/yellow.php
@@ -1,9 +1,7 @@
<?php
-// Datenstrom Yellow is for people who make small websites, https://datenstrom.se/yellow/
-// Copyright (c) 2013-2020 Datenstrom, https://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
+// Datenstrom Yellow, https://github.com/datenstrom/yellow
-require_once("system/extensions/core.php");
+require("system/extensions/core.php");
if (PHP_SAPI!="cli") {
$yellow = new YellowCore();