commit c6cf750609e10aef600d3b4489ba6b0d2a22722d
parent ee5b9ff185f1bc445978b4ca2c469561a57c81d5
Author: markseu <mark2011@mayberg.se>
Date: Tue, 6 Oct 2015 14:19:11 +0200
Core update (fika remix)
Diffstat:
18 files changed, 5192 insertions(+), 5022 deletions(-)
diff --git a/media/downloads/yellow.pdf b/media/downloads/yellow.pdf
Binary files differ.
diff --git a/media/images/icon.png b/media/images/icon.png
Binary files differ.
diff --git a/system/config/config.ini b/system/config/config.ini
@@ -1,53 +1,52 @@
-// Yellow site configuration
+# Yellow main configuration
-sitename = Yellow
-author = Yellow
-language = en
-theme = flatsite
+Sitename: Yellow
+Author: Yellow
+Language: en
+Theme: flatsite
-// timeZone = UTC
-// serverScheme = http
-// serverName = your.domain.name
-// serverBase =
+# ServerScheme: http
+# ServerName: your.domain.name
+# ServerBase:
+# ServerTime: UTC
-imageLocation = /media/images/
-pluginLocation = /media/plugins/
-themeLocation = /media/themes/
-systemDir = system/
-configDir = system/config/
-coreDir = system/core/
-pluginDir = system/plugins/
-themeDir = system/themes/
-snippetDir = system/themes/snippets/
-templateDir = system/themes/templates/
-mediaDir = media/
-imageDir = media/images/
-staticDir = cache/
-staticAccessFile = .htaccess
-staticDefaultFile = index.html
-staticErrorFile = error.html
-contentDir = content/
-contentRootDir = default/
-contentHomeDir = home/
-contentDefaultFile = page.txt
-contentPagination = page
-contentExtension = .txt
-configExtension = .ini
-textFile = language-(.*).ini
-errorFile = page-error-(.*).txt
-robotsFile = robots.txt
-iconFile = icon.png
-template = default
-navigation = navigation
-sidebar = sidebar
-parser = markdown
-parserSafeMode = 0
-multiLanguageMode = 0
-webinterfaceLocation = /edit/
-webinterfaceServerScheme = http
-webinterfaceUserHashAlgorithm = bcrypt
-webinterfaceUserHashCost = 10
-webinterfaceUserHome = /
-webinterfaceUserFile = user.ini
-webinterfaceNewFile = page-new-(.*).txt
-webinterfaceMetaFilePrefix = published
+ImageLocation: /media/images/
+PluginLocation: /media/plugins/
+ThemeLocation: /media/themes/
+SystemDir: system/
+ConfigDir: system/config/
+PluginDir: system/plugins/
+ThemeDir: system/themes/
+SnippetDir: system/themes/snippets/
+TemplateDir: system/themes/templates/
+MediaDir: media/
+ImageDir: media/images/
+StaticDir: cache/
+StaticAccessFile: .htaccess
+StaticDefaultFile: index.html
+StaticErrorFile: error.html
+ContentDir: content/
+ContentRootDir: default/
+ContentHomeDir: home/
+ContentDefaultFile: page.txt
+ContentPagination: page
+ContentExtension: .txt
+ConfigExtension: .ini
+TextFile: language-(.*).ini
+ErrorFile: page-error-(.*).txt
+RobotsFile: robots.txt
+IconFile: icon.png
+Template: default
+Navigation: navigation
+Sidebar: sidebar
+Parser: markdown
+ParserSafeMode: 0
+MultiLanguageMode: 0
+WebinterfaceLocation: /edit/
+WebinterfaceServerScheme: http
+WebinterfaceUserHashAlgorithm: bcrypt
+WebinterfaceUserHashCost: 10
+WebinterfaceUserHome: /
+WebinterfaceUserFile: user.ini
+WebinterfaceNewFile: page-new-(.*).txt
+WebinterfaceMetaFilePrefix: published
diff --git a/system/config/language-en.ini b/system/config/language-en.ini
@@ -1,31 +1,54 @@
-// Yellow text strings
+# Yellow text strings
-language = en
-languageDescription = English
-languageAuthor = Mark Seuffert
-languageVersion = 0.5.0
+Language: en
+LanguageDescription: English
+LanguageAuthor: Mark Seuffert
+LanguageVersion: 0.6.1
-dateMonths = January, February, March, April, May, June, July, August, September, October, November, December
-dateWeekdays = Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
-dateFormatShort = F Y
-dateFormatMedium = Y-m-d
-dateFormatLong = Y-m-d H:i
-paginationPrevious = ← Previous
-paginationNext = Next →
-webinterfaceLoginText = Yellow login
-webinterfaceLoginEmail = Email:
-webinterfaceLoginPassword = Password:
-webinterfaceLoginButton = Login
-webinterfaceCreateButton = Create
-webinterfaceEditButton = Save
-webinterfaceDeleteButton = Delete
-webinterfaceCancelButton = Cancel
-webinterfaceEdit = Edit page
-webinterfaceCreate = +
-webinterfaceDelete = -
-webinterfaceCreateTitle = New page
-webinterfaceDeleteTitle = Delete page
-webinterfaceUserHelp = Help
-webinterfaceUserHelpUrl = https://github.com/datenstrom/yellow/wiki
-webinterfaceUserAccountUrl = https://github.com/datenstrom/yellow/wiki/How-to-add-a-user-account
-webinterfaceUserLogout = Logout
+DateMonths: January, February, March, April, May, June, July, August, September, October, November, December
+DateWeekdays: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
+DateFormatShort: F Y
+DateFormatMedium: Y-m-d
+DateFormatLong: Y-m-d H:i
+PaginationPrevious: ← Previous
+PaginationNext: Next →
+
+WebinterfaceLoginText: Yellow login
+WebinterfaceLoginEmail: Email:
+WebinterfaceLoginPassword: Password:
+WebinterfaceLoginButton: Log in
+WebinterfaceSignupButton: Sign up
+WebinterfaceCreateButton: Create
+WebinterfaceEditButton: Save
+WebinterfaceDeleteButton: Delete
+WebinterfaceCancelButton: Cancel
+WebinterfaceEdit: Edit page
+WebinterfaceCreate: +
+WebinterfaceDelete: -
+WebinterfaceCreateTitle: New page
+WebinterfaceDeleteTitle: Delete page
+WebinterfaceUserHelp: Help
+WebinterfaceUserHelpUrl: https://github.com/datenstrom/yellow/wiki
+WebinterfaceUserAccountUrl: https://github.com/datenstrom/yellow/wiki/How-to-add-a-user-account
+WebinterfaceUserLogout: Logout
+
+BlogBy: by
+BlogFilter: Blog:
+BlogTag: Tags:
+BlogMore: Read more…
+WikiFilter: Wiki:
+WikiTag: Tags:
+WikiSpecialChanges: Recent changes
+SearchQuery: Search:
+SearchResultsNone: Enter a search term.
+SearchResultsEmpty: No results found.
+SearchButton: Search
+ContactName: Name:
+ContactEmail: Email:
+ContactMessage: Message:
+ContactButton: Send message
+ContactStatusNone: Say hello. Your feedback is very welcome.
+ContactStatusIncomplete: Please fill out all fields.
+ContactStatusInvalid: Please enter a valid email.
+ContactStatusDone: You have sucessfully sent an email. Thank you!
+ContactStatusError: Email could not be sent, please try again later!
+\ No newline at end of file
diff --git a/system/config/page-error-401.txt b/system/config/page-error-401.txt
@@ -1,4 +0,0 @@
----
-Title: Unauthorised
----
-You are not authorised on this server. [Please log in](javascript:yellow.action('login');).
-\ No newline at end of file
diff --git a/system/config/user.ini b/system/config/user.ini
@@ -1,3 +1,2 @@
// Yellow user accounts
-// Format: Email, password hash, name, language, home
diff --git a/system/core/core-commandline.php b/system/core/core-commandline.php
@@ -1,444 +0,0 @@
-<?php
-// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-// Command line core plugin
-class YellowCommandline
-{
- const Version = "0.5.5";
- var $yellow; //access to API
- var $content; //number of content pages
- var $media; //number of media files
- var $system; //number of system files
- var $error; //number of build errors
- var $locationsArgs; //locations with location arguments detected
- var $locationsArgsPagination; //locations with pagination arguments detected
-
- // Handle initialisation
- function onLoad($yellow)
- {
- $this->yellow = $yellow;
- }
-
- // Handle command
- function onCommand($args)
- {
- list($name, $command) = $args;
- switch($command)
- {
- case "": $statusCode = $this->helpCommand(); break;
- case "version": $statusCode = $this->versionCommand(); break;
- case "build": $statusCode = $this->buildCommand($args); break;
- case "clean": $statusCode = $this->cleanCommand($args); break;
- default: $statusCode = $this->pluginCommand($args);
- }
- if($statusCode == 0)
- {
- $statusCode = 400;
- echo "Yellow $command: Command not found\n";
- }
- return $statusCode;
- }
-
- // Handle command help
- function onCommandHelp()
- {
- $help .= "version\n";
- $help .= "build [DIRECTORY LOCATION]\n";
- $help .= "clean [DIRECTORY LOCATION]\n";
- return $help;
- }
-
- // Show available commands
- function helpCommand()
- {
- echo "Yellow ".Yellow::Version."\n";
- foreach($this->getCommandHelp() as $line) echo (++$lineCounter>1 ? " " : "Syntax: ")."yellow.php $line\n";
- return 200;
- }
-
- // Show software version
- function versionCommand()
- {
- echo "Yellow ".Yellow::Version."\n";
- foreach($this->getPluginVersion() as $line) echo "$line\n";
- return 200;
- }
-
- // Build static pages
- function buildCommand($args)
- {
- $statusCode = 0;
- list($dummy, $command, $path, $location) = $args;
- if(empty($location) || $location[0]=='/')
- {
- if($this->checkStaticConfig())
- {
- $statusCode = $this->buildStatic($path, $location);
- } else {
- $statusCode = 500;
- list($this->content, $this->media, $this->system, $this->error) = array(0, 0, 0, 1);
- $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("configFile");
- echo "ERROR bulding pages: Please configure serverScheme, serverName and serverBase in file '$fileName'!\n";
- }
- echo "Yellow $command: $this->content content, $this->media media, $this->system system";
- echo ", $this->error error".($this->error!=1 ? 's' : '');
- echo ", status $statusCode\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Build static pages and files
- function buildStatic($path, $location)
- {
- $this->yellow->toolbox->timerStart($time);
- $path = rtrim(empty($path) ? $this->yellow->config->get("staticDir") : $path, '/');
- $this->content = $this->media = $this->system = $this->error = $statusCode = 0;
- $this->locationsArgs = $this->locationsArgsPagination = array();
- if(empty($location))
- {
- $statusCode = $this->cleanStatic($path, $location);
- foreach($this->getStaticLocations() as $location)
- {
- $statusCode = max($statusCode, $this->buildStaticPage($path, $location, true));
- }
- foreach($this->locationsArgs as $location)
- {
- $statusCode = max($statusCode, $this->buildStaticPage($path, $location, true));
- }
- foreach($this->locationsArgsPagination as $location)
- {
- if(substru($location, -1) != ':')
- {
- $statusCode = max($statusCode, $this->buildStaticPage($path, $location, false, true));
- }
- for($pageNumber=2; $pageNumber<=999; ++$pageNumber)
- {
- $statusCodeLocation = $this->buildStaticPage($path, $location.$pageNumber, false, true);
- $statusCode = max($statusCode, $statusCodeLocation);
- if($statusCodeLocation == 0) break;
- }
- }
- $statusCode = max($statusCode, $this->buildStaticPage($path, "/error", false, false, true));
- foreach($this->getStaticFilesMedia($path) as $fileNameSource=>$fileNameDest)
- {
- $statusCode = max($statusCode, $this->buildStaticFile($fileNameSource, $fileNameDest, true));
- }
- foreach($this->getStaticFilesSystem($path) as $fileNameSource=>$fileNameDest)
- {
- $statusCode = max($statusCode, $this->buildStaticFile($fileNameSource, $fileNameDest, false));
- }
- } else {
- $statusCode = $this->buildStaticPage($path, $location);
- }
- $this->yellow->toolbox->timerStop($time);
- if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStatic time:$time ms\n";
- return $statusCode;
- }
-
- // Build static page
- function buildStaticPage($path, $location, $analyse = false, $probe = false, $error = false)
- {
- ob_start();
- $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1";
- $_SERVER["SERVER_NAME"] = $this->yellow->config->get("serverName");
- $_SERVER["REQUEST_URI"] = $this->yellow->config->get("serverBase").$location;
- $_SERVER["SCRIPT_NAME"] = $this->yellow->config->get("serverBase")."/yellow.php";
- $_REQUEST = array();
- $statusCode = $this->yellow->request();
- if($statusCode<400 || $error)
- {
- $fileData = ob_get_contents();
- $modified = strtotime($this->yellow->page->getHeader("Last-Modified"));
- if($statusCode>=301 && $statusCode<=303)
- {
- $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location"));
- $modified = time();
- }
- $fileName = $this->getStaticFile($path, $location, $statusCode);
- 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'!");
- }
- }
- ob_end_clean();
- if($statusCode==200 && $analyse) $this->analyseStaticPage($fileData);
- if($statusCode==404 && $error) $statusCode = 200;
- if($statusCode==404 && $probe) $statusCode = 0;
- if($statusCode != 0) ++$this->content;
- if($statusCode >= 400)
- {
- ++$this->error;
- echo "ERROR building content location '$location', ".$this->yellow->page->getStatusCode(true)."\n";
- }
- if(defined("DEBUG") && DEBUG>=3) echo $fileData;
- if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStaticPage status:$statusCode location:$location\n";
- return $statusCode;
- }
-
- // Build static file
- function buildStaticFile($fileNameSource, $fileNameDest, $fileTypeMedia)
- {
- $statusCode = $this->yellow->toolbox->copyFile($fileNameSource, $fileNameDest, true) &&
- $this->yellow->toolbox->modifyFile($fileNameDest, filemtime($fileNameSource)) ? 200 : 500;
- if($fileTypeMedia) { ++$this->media; } else { ++$this->system; }
- if($statusCode >= 400)
- {
- ++$this->error;
- $fileType = $fileTypeMedia ? "media file" : "system file";
- $fileError = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
- echo "ERROR building $fileType, $fileError: Can't write file '$fileNameDest'!\n";
- }
- if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStaticFile status:$statusCode file:$fileNameDest\n";
- return $statusCode;
- }
-
- // Analyse static page, detect locations with arguments
- function analyseStaticPage($text)
- {
- $serverName = $this->yellow->config->get("serverName");
- $serverBase = $this->yellow->config->get("serverBase");
- $pagination = $this->yellow->config->get("contentPagination");
- preg_match_all("/<a(.*?)href=\"([^\"]+)\"(.*?)>/i", $text, $matches);
- foreach($matches[2] as $match)
- {
- if(preg_match("/^\w+:\/+(.*?)(\/.*)$/", $match, $tokens))
- {
- if($tokens[1] != $serverName) continue;
- $match = $tokens[2];
- }
- if(!$this->yellow->toolbox->isLocationArgs($match)) continue;
- if(substru($match, 0, strlenu($serverBase)) != $serverBase) continue;
- $location = rawurldecode(substru($match, strlenu($serverBase)));
- if(!$this->yellow->toolbox->isLocationArgsPagination($location, $pagination))
- {
- $location = rtrim($location, '/').'/';
- if(is_null($this->locationsArgs[$location]))
- {
- $this->locationsArgs[$location] = $location;
- if(defined("DEBUG") && DEBUG>=2) echo "YellowCommandline::analyseStaticPage detected location:$location\n";
- }
- } else {
- $location = rtrim($location, "0..9");
- if(is_null($this->locationsArgsPagination[$location]))
- {
- $this->locationsArgsPagination[$location] = $location;
- if(defined("DEBUG") && DEBUG>=2) echo "YellowCommandline::analyseStaticPage detected location:$location\n";
- }
- }
- }
- }
-
- // Clean static pages
- function cleanCommand($args)
- {
- $statusCode = 0;
- list($dummy, $command, $path, $location) = $args;
- if(empty($location) || $location[0]=='/')
- {
- $statusCode = $this->cleanStatic($path, $location);
- echo "Yellow $command: Static page".(empty($location) ? "s" : "")." ".($statusCode!=200 ? "not " : "")."cleaned\n";
- } else {
- $statusCode = 400;
- echo "Yellow $command: Invalid arguments\n";
- }
- return $statusCode;
- }
-
- // Clean static directories and files
- function cleanStatic($path, $location)
- {
- $statusCode = 200;
- $path = rtrim(empty($path) ? $this->yellow->config->get("staticDir") : $path, '/');
- if(empty($location))
- {
- $statusCode = max($statusCode, $this->pluginCommand(array("all", "clean")));
- $statusCode = max($statusCode, $this->cleanStaticDirectory($path));
- } else {
- $statusCode = $this->cleanStaticFile($path, $location);
- }
- return $statusCode;
- }
-
- // Clean static directory
- function cleanStaticDirectory($path)
- {
- $statusCode = 200;
- if(is_dir($path))
- {
- if(!$this->checkStaticDirectory($path) || !$this->yellow->toolbox->deleteDirectory($path, true))
- {
- $statusCode = 500;
- echo "ERROR cleaning pages: Can't delete directory '$path'!\n";
- }
- }
- return $statusCode;
- }
-
- // Clean static file
- function cleanStaticFile($path, $location)
- {
- $statusCode = 200;
- $fileName = $this->getStaticFile($path, $location, $statusCode);
- if(is_file($fileName))
- {
- if(!$this->checkStaticDirectory($path) || !$this->yellow->toolbox->deleteFile($fileName))
- {
- $statusCode = 500;
- echo "ERROR cleaning pages: Can't delete file '$fileName'!\n";
- }
- }
- return $statusCode;
- }
-
- // Forward plugin command
- function pluginCommand($args)
- {
- $statusCode = 0;
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if($key == "commandline") continue;
- if(method_exists($value["obj"], "onCommand"))
- {
- $statusCode = $value["obj"]->onCommand($args);
- if($statusCode != 0) break;
- }
- }
- return $statusCode;
- }
-
- // Check static configuration
- function checkStaticConfig()
- {
- $serverScheme = $this->yellow->config->get("serverScheme");
- $serverName = $this->yellow->config->get("serverName");
- $serverBase = $this->yellow->config->get("serverBase");
- return !empty($serverScheme) && !empty($serverName) &&
- $this->yellow->lookup->isValidLocation($serverBase) && $serverBase!="/";
- }
-
- // Check static directory
- function checkStaticDirectory($path)
- {
- $ok = false;
- if(!empty($path))
- {
- if($path == rtrim($this->yellow->config->get("staticDir"), '/')) $ok = true;
- if(is_file("$path/".$this->yellow->config->get("staticAccessFile"))) $ok = true;
- if(is_file("$path/yellow.php")) $ok = false;
- }
- return $ok;
- }
-
- // Return static locations from file system
- function getStaticLocations()
- {
- $locations = array();
- $serverScheme = $this->yellow->config->get("serverScheme");
- $serverName = $this->yellow->config->get("serverName");
- $serverBase = $this->yellow->config->get("serverBase");
- $this->yellow->page->setRequestInformation($serverScheme, $serverName, $serverBase, "", "");
- foreach($this->yellow->pages->index(true, true) as $page)
- {
- if($page->get("status")!="ignore" && $page->get("status")!="draft")
- {
- array_push($locations, $page->location);
- }
- }
- if(!$this->yellow->pages->find("/") && $this->yellow->config->get("multiLanguageMode")) array_unshift($locations, "/");
- return $locations;
- }
-
- // Return static media files
- function getStaticFilesMedia($path)
- {
- $files = array();
- $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive(
- $this->yellow->config->get("mediaDir"), "/.*/", false, false);
- foreach($fileNames as $fileName) $files[$fileName] = "$path/$fileName";
- return $files;
- }
-
- // Return static system files
- function getStaticFilesSystem($path)
- {
- $files = array();
- $fileNames = $this->yellow->toolbox->getDirectoryEntries(
- $this->yellow->config->get("pluginDir"), "/\.(css|js|jpg|png|txt|woff)/", false, false);
- foreach($fileNames as $fileName)
- {
- $files[$fileName] = $path.$this->yellow->config->get("pluginLocation").basename($fileName);
- }
- $fileNames = $this->yellow->toolbox->getDirectoryEntries(
- $this->yellow->config->get("themeDir"), "/\.(css|js|jpg|png|txt|woff)/", false, false);
- foreach($fileNames as $fileName)
- {
- $files[$fileName] = $path.$this->yellow->config->get("themeLocation").basename($fileName);
- }
- $fileNames = array();
- array_push($fileNames, $this->yellow->config->get("staticAccessFile"));
- array_push($fileNames, $this->yellow->config->get("configDir").$this->yellow->config->get("robotsFile"));
- foreach($fileNames as $fileName) $files[$fileName] = "$path/".basename($fileName);
- return $files;
- }
-
- // Return static file
- function getStaticFile($path, $location, $statusCode)
- {
- if($statusCode < 400)
- {
- $fileName = $path.$location;
- if(!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->config->get("staticDefaultFile");
- } else if($statusCode == 404) {
- $fileName = $path."/".$this->yellow->config->get("staticErrorFile");
- }
- return $fileName;
- }
-
- // Return static redirect
- 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 command help
- function getCommandHelp()
- {
- $data = array();
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onCommandHelp"))
- {
- foreach(preg_split("/[\r\n]+/", $value["obj"]->onCommandHelp()) as $line)
- {
- list($command, $text) = explode(' ', $line, 2);
- if(!empty($command) && is_null($data[$command])) $data[$command] = $line;
- }
- }
- }
- uksort($data, strnatcasecmp);
- return $data;
- }
-
- // Return plugin version
- function getPluginVersion()
- {
- $data = array();
- foreach($this->yellow->plugins->plugins as $key=>$value) $data[$key] = "$value[class] $value[version]";
- usort($data, strnatcasecmp);
- return $data;
- }
-}
-
-$yellow->plugins->register("commandline", "YellowCommandline", YellowCommandline::Version);
-?>
-\ No newline at end of file
diff --git a/system/core/core-webinterface.css b/system/core/core-webinterface.css
@@ -1,104 +0,0 @@
-/* Yellow web interface 0.5.20 */
-
-.yellow-bar { position:relative; overflow:hidden; height:2em; margin-bottom:10px; }
-.yellow-bar-left { display:block; float:left; }
-.yellow-bar-right { display:block; float:right; }
-.yellow-bar-right a { margin-left:1em; }
-.yellow-bar-right #yellow-pane-create-link { padding:0 0.5em; }
-.yellow-bar-right #yellow-pane-delete-link { padding:0 0.5em; }
-.yellow-body-modal-open { overflow:hidden; }
-.yellow-body-modal-open .page { display:none; }
-
-.yellow-pane {
- position:absolute; display:none; z-index:100;
- margin:10px 0px; padding:10px;
- background-color:#fff; color:#000;
- border:1px solid #bbb;
- border-radius:4px; box-shadow:2px 4px 10px rgba(0, 0, 0, 0.2);
-}
-.yellow-pane h1 { color:#000; }
-.yellow-pane p { margin:0.5em; }
-.yellow-pane ul { list-style:none; margin:0 0.5em; padding:0; }
-.yellow-pane div { overflow:hidden; }
-.yellow-cancel { display:block; float:right; padding:0 0.5em; color:#bbb; }
-.yellow-cancel:hover { text-decoration:none; color:#000; }
-.yellow-arrow { position:absolute; top:0; left:0; }
-.yellow-arrow:after, .yellow-arrow:before {
- position:absolute;
- bottom:100%;
- height:0; width:0;
- border:solid transparent;
- content:" ";
-}
-.yellow-arrow:after {
- border-color:rgba(255, 255, 255, 0);
- border-bottom-color:#fff;
- border-width:10px;
- margin-left:-10px;
-}
-.yellow-arrow:before {
- border-color:rgba(187, 187, 187, 0);
- border-bottom-color:#bbb;
- border-width:11px;
- margin-left:-11px;
-}
-
-.yellow-form-control {
- margin:0; padding:2px 4px;
- display:inline-block;
- background-color:#fff; color:#000;
- 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;
-}
-.yellow-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;
-}
-.yellow-btn:hover, .yellow-btn:focus, .yellow-btn:active {
- color:#333333;
- background-image:none;
- text-decoration:none;
-}
-.yellow-btn:active { box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.1); }
-.yellow-btn-create {
- background-color:#3cc335; color:#ffffff;
- background-image:linear-gradient(to bottom, #5fee5b, #36bd2f);
- border-color:#31b121 #31b121 #20b020;
-}
-.yellow-btn-create:hover, .yellow-btn-create:focus, .yellow-btn-create:active { color:#ffffff; }
-.yellow-btn-edit {
- background-color:#3cc335; color:#ffffff;
- background-image:linear-gradient(to bottom, #5fee5b, #36bd2f);
- border-color:#31b121 #31b121 #20b020;
-}
-.yellow-btn-edit:hover, .yellow-btn-edit:focus, .yellow-btn-edit:active { color:#ffffff; }
-.yellow-btn-delete {
- background-color:#c33c35; color:#ffffff;
- background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);
- border-color:#b13121 #b13121 #802020;
-}
-.yellow-btn-delete:hover, .yellow-btn-delete:focus, .yellow-btn-delete:active { color:#ffffff; }
-
-#yellow-pane-login { text-align:center; }
-#yellow-pane-login h1 { margin:0 1em; font-size:2em; }
-#yellow-pane-login-fields { width:12em; text-align:left; margin:0 auto; }
-#yellow-pane-login input { width:12em; box-sizing:border-box; }
-#yellow-pane-login .yellow-btn { margin:0.5em 0; }
-#yellow-pane-edit { }
-#yellow-pane-edit h1 { margin:0 0 10px 0; font-size:1.5em; }
-#yellow-pane-edit-page { padding:5px; outline:none; resize:none; }
-#yellow-pane-edit-buttons { margin-top:5px; }
-#yellow-pane-edit-buttons input { margin-right:10px; }
-#yellow-pane-user { cursor:pointer; }
-#yellow-pane-user a { text-decoration:none; }
-#yellow-pane-user a:hover { text-decoration:underline; }
diff --git a/system/core/core-webinterface.js b/system/core/core-webinterface.js
@@ -1,579 +0,0 @@
-// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-// Yellow main API
-var yellow =
-{
- version: "0.5.20",
- action: function(text) { yellow.webinterface.action(text); },
- onClick: function(e) { yellow.webinterface.hidePanesOnClick(yellow.toolbox.getEventElement(e)); },
- onKeydown: function(e) { yellow.webinterface.hidePanesOnKeydown(yellow.toolbox.getEventKeycode(e)); },
- onResize: function() { yellow.webinterface.resizePanes(); },
- onUpdate: function() { yellow.webinterface.updatePane(yellow.webinterface.paneId, yellow.webinterface.paneType); },
- webinterface:{}, toolbox:{}, page:{}, config:{}, text:{}
-}
-
-// Yellow web interface
-yellow.webinterface =
-{
- loaded: false, //web interface loaded? (boolean)
- intervalId: 0, //timer interval ID
- paneId: 0, //visible pane ID
- paneType: 0, //visible pane type
-
- // Initialise web interface
- init: function()
- {
- this.intervalId = setInterval("yellow.webinterface.load()", 1);
- yellow.toolbox.addEvent(document, "click", yellow.onClick);
- yellow.toolbox.addEvent(document, "keydown", yellow.onKeydown);
- yellow.toolbox.addEvent(window, "resize", yellow.onResize);
- },
-
- // Load web interface
- load: function()
- {
- var body = document.getElementsByTagName("body")[0];
- if(body && body.firstChild && !this.loaded)
- {
- this.loaded = true;
- if(yellow.config.webinterfaceLocation)
- {
- if(yellow.debug) console.log("yellow.webinterface.load email:"+yellow.config.userEmail+" "+yellow.config.userName);
- if(yellow.config.userEmail)
- {
- this.createBar("yellow-bar", true, body.firstChild);
- this.createPane("yellow-pane-edit", true, body.firstChild);
- this.createPane("yellow-pane-user", true, body.firstChild);
- yellow.toolbox.addEvent(document.getElementById("yellow-pane-edit-page"), "keyup", yellow.onUpdate);
- yellow.toolbox.addEvent(document.getElementById("yellow-pane-edit-page"), "change", yellow.onUpdate);
- } else {
- this.createBar("yellow-bar", false, body.firstChild);
- this.createPane("yellow-pane-login", false, body.firstChild);
- if(yellow.config.login) this.showPane("yellow-pane-login");
- }
- }
- clearInterval(this.intervalId);
- }
- },
-
- // Execute action
- action: function(text)
- {
- switch(text)
- {
- case "create": this.togglePane("yellow-pane-edit", "create", true); break;
- case "edit": this.togglePane("yellow-pane-edit", "edit", true); break;
- case "delete": this.togglePane("yellow-pane-edit", "delete", true); break;
- case "user": this.togglePane("yellow-pane-user"); break;
- case "send": this.sendPane(this.paneId, this.paneType); break;
- case "cancel": this.hidePane(this.paneId); break;
- case "login": this.togglePane("yellow-pane-login"); break;
- case "logout": yellow.toolbox.submitForm({"action":"logout"}); break;
- }
- },
-
- // Create bar
- createBar: function(id, normal, elementReference)
- {
- if(yellow.debug) console.log("yellow.webinterface.createBar id:"+id);
- var elementBar = document.createElement("div");
- elementBar.className = "yellow-bar";
- elementBar.setAttribute("id", id);
- if(normal)
- {
- elementBar.innerHTML =
- "<div class=\"yellow-bar-left\">"+
- "<a href=\"#\" onclick=\"yellow.action('edit'); return false;\" id=\"yellow-pane-edit-link\">"+this.getText("Edit")+"</a>"+
- "</div>"+
- "<div class=\"yellow-bar-right\">"+
- "<a href=\"#\" onclick=\"yellow.action('create'); return false;\" id=\"yellow-pane-create-link\">"+this.getText("Create")+"</a>"+
- "<a href=\"#\" onclick=\"yellow.action('delete'); return false;\" id=\"yellow-pane-delete-link\">"+this.getText("Delete")+"</a>"+
- "<a href=\"#\" onclick=\"yellow.action('user'); return false;\" id=\"yellow-pane-user-link\">"+yellow.config.userName+"</a>"+
- "</div>";
- }
- yellow.toolbox.insertBefore(elementBar, elementReference);
- },
-
- // Create pane
- createPane: function(paneId, bubble, elementReference)
- {
- if(yellow.debug) console.log("yellow.webinterface.createPane id:"+paneId);
- var elementPane = document.createElement("div");
- elementPane.className = "yellow-pane";
- elementPane.setAttribute("id", paneId);
- elementPane.style.display = "none";
- if(bubble)
- {
- var elementArrow = document.createElement("span");
- elementArrow.className = "yellow-arrow";
- elementArrow.setAttribute("id", paneId+"-arrow");
- elementPane.appendChild(elementArrow);
- }
- var elementDiv = document.createElement("div");
- elementDiv.setAttribute("id", paneId+"-content");
- if(paneId == "yellow-pane-login")
- {
- elementDiv.innerHTML =
- "<form method=\"post\">"+
- "<a href=\"#\" onclick=\"yellow.action('cancel'); return false;\" class=\"yellow-cancel\">x</a>"+
- "<h1>"+this.getText("LoginText")+"</h1>"+
- "<div id=\"yellow-pane-login-fields\">"+
- "<input type=\"hidden\" name=\"action\" value=\"login\" />"+
- "<p><label for=\"email\">"+this.getText("LoginEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"email\" maxlength=\"64\" value=\""+yellow.config.loginEmail+"\" /></p>"+
- "<p><label for=\"password\">"+this.getText("LoginPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"password\" maxlength=\"64\" value=\""+yellow.config.loginPassword+"\" /></p>"+
- "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+
- "</div>"+
- "</form>";
- } else if(paneId == "yellow-pane-edit") {
- elementDiv.innerHTML =
- "<form method=\"post\">"+
- "<h1 id=\"yellow-pane-edit-title\">"+this.getText("Edit")+"</h1>"+
- "<textarea id=\"yellow-pane-edit-page\" class=\"yellow-form-control\" name=\"rawdataedit\"></textarea>"+
- "<div id=\"yellow-pane-edit-buttons\">"+
- "<input id=\"yellow-pane-edit-send\" class=\"yellow-btn\" type=\"button\" onclick=\"yellow.action('send'); return false;\" value=\""+this.getText("EditButton")+"\" />"+
- "<input id=\"yellow-pane-edit-cancel\" class=\"yellow-btn\" type=\"button\" onclick=\"yellow.action('cancel'); return false;\" value=\""+this.getText("CancelButton")+"\" />"+
- "</div>"+
- "</form>";
- } else if(paneId == "yellow-pane-user") {
- elementDiv.innerHTML =
- "<p>"+yellow.config.userEmail+"</p>"+
- "<p><a href=\""+this.getText("UserHelpUrl")+"\" target=\"_blank\" onclick=\"yellow.action('user'); return true;\">"+this.getText("UserHelp")+"</a></p>" +
- "<p><a href=\"#\" onclick=\"yellow.action('logout'); return false;\">"+this.getText("UserLogout")+"</a></p>";
- }
- elementPane.appendChild(elementDiv);
- yellow.toolbox.insertAfter(elementPane, elementReference);
- },
-
- // Update pane
- updatePane: function(paneId, paneType, init)
- {
- if(yellow.debug) console.log("yellow.webinterface.updatePane id:"+paneId);
- if(paneId == "yellow-pane-edit")
- {
- if(init)
- {
- var title = yellow.page.title;
- var string = yellow.page.rawDataEdit;
- switch(paneType)
- {
- case "create": title = this.getText("CreateTitle"); string = yellow.page.rawDataNew; break;
- case "delete": title = this.getText("DeleteTitle"); break;
- }
- document.getElementById("yellow-pane-edit-title").innerHTML = yellow.toolbox.encodeHtml(title);
- document.getElementById("yellow-pane-edit-page").value = string;
- }
- var action = this.getPaneAction(paneId, paneType)
- if(action)
- {
- var key, className;
- switch(action)
- {
- case "create": key = "CreateButton"; className = "yellow-btn yellow-btn-create"; break;
- case "edit": key = "EditButton"; className = "yellow-btn yellow-btn-edit"; break;
- case "delete": key = "DeleteButton"; className = "yellow-btn yellow-btn-delete"; break;
- }
- document.getElementById("yellow-pane-edit-send").value = this.getText(key);
- document.getElementById("yellow-pane-edit-send").className = className;
- } else {
- document.getElementById("yellow-pane-edit-send").style.display = "none";
- }
- }
- },
-
- // Send pane
- sendPane: function(paneId, paneType)
- {
- if(yellow.debug) console.log("yellow.webinterface.sendPane id:"+paneId);
- if(paneId == "yellow-pane-edit")
- {
- var action = this.getPaneAction(paneId, paneType);
- if(action)
- {
- var params = {};
- params.action = action;
- params.rawdatasource = yellow.page.rawDataSource;
- params.rawdataedit = document.getElementById("yellow-pane-edit-page").value;
- yellow.toolbox.submitForm(params, true);
- } else {
- this.hidePane(paneId);
- }
- }
- },
-
- // Show or hide pane
- togglePane: function(paneId, paneType, modal)
- {
- if(this.paneId!=paneId || this.paneType!=paneType)
- {
- this.hidePane(this.paneId);
- this.showPane(paneId, paneType, modal);
- } else {
- this.hidePane(paneId);
- }
- },
-
- // Show pane
- showPane: function(paneId, paneType, modal)
- {
- var element = document.getElementById(paneId);
- if(!yellow.toolbox.isVisible(element))
- {
- if(yellow.debug) console.log("yellow.webinterface.showPane id:"+paneId);
- element.style.display = "block";
- if(modal) yellow.toolbox.addClass(document.body, "yellow-body-modal-open");
- this.paneId = paneId;
- this.paneType = paneType;
- this.resizePanes();
- this.updatePane(paneId, paneType, true);
- }
- },
-
- // Hide pane
- hidePane: function(paneId)
- {
- var element = document.getElementById(paneId);
- if(yellow.toolbox.isVisible(element))
- {
- if(yellow.debug) console.log("yellow.webinterface.hidePane id:"+paneId);
- element.style.display = "none";
- yellow.toolbox.removeClass(document.body, "yellow-body-modal-open");
- this.paneId = 0;
- this.paneType = 0;
- }
- },
-
- // Hide all panes
- hidePanes: function()
- {
- for(var element=document.getElementById("yellow-bar"); element; element=element.nextSibling)
- {
- if(element.className && element.className.indexOf("yellow-pane")>=0)
- {
- this.hidePane(element.getAttribute("id"));
- }
- }
- },
-
- // Hide all panes on mouse click outside
- hidePanesOnClick: function(element)
- {
- while(element = element.parentNode)
- {
- if(element.className)
- {
- if(element.className.indexOf("yellow-pane")>=0 || element.className.indexOf("yellow-bar")>=0) return;
- }
- }
- this.hidePanes();
- },
-
- // Hide all panes on ESC key
- hidePanesOnKeydown: function(keycode)
- {
- if(keycode == 27) this.hidePanes();
- },
-
- // Resize panes, recalculate width and height where needed
- resizePanes: function()
- {
- if(document.getElementById("yellow-bar"))
- {
- var elementBar = document.getElementById("yellow-bar");
- var paneTop = yellow.toolbox.getOuterTop(elementBar) + yellow.toolbox.getOuterHeight(elementBar);
- var paneWidth = yellow.toolbox.getOuterWidth(elementBar, true);
- var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - yellow.toolbox.getOuterHeight(elementBar);
- if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-login")))
- {
- yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-login"), paneTop);
- yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-login"), paneWidth);
- }
- if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-edit")))
- {
- yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-edit"), paneTop);
- yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit"), paneHeight);
- yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit"), paneWidth);
- yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-page"), yellow.toolbox.getWidth(document.getElementById("yellow-pane-edit")));
- var height1 = yellow.toolbox.getHeight(document.getElementById("yellow-pane-edit"));
- var height2 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-content"));
- var height3 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-page"));
- yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit-page"), height1 - height2 + height3);
- var elementLink = document.getElementById("yellow-pane-"+this.paneType+"-link");
- var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
- position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-edit")) + 1;
- yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-edit-arrow"), position);
- }
- if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-user")))
- {
- yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-user"), paneTop);
- yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-user"), paneHeight, true);
- yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-user"), paneWidth - yellow.toolbox.getOuterWidth(document.getElementById("yellow-pane-user")), true);
- var elementLink = document.getElementById("yellow-pane-user-link");
- var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
- position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-user"));
- yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-user-arrow"), position);
- }
- if(yellow.debug) console.log("yellow.webinterface.resizePanes bar:"+elementBar.offsetWidth+"/"+elementBar.offsetHeight);
- }
- },
-
- // Return pane action
- getPaneAction: function(paneId, paneType)
- {
- var action = "";
- if(paneId == "yellow-pane-edit")
- {
- if(yellow.page.userPermission)
- {
- var string = document.getElementById("yellow-pane-edit-page").value;
- switch(paneType)
- {
- case "create": action = "create"; break;
- case "edit": action = string ? "edit" : "delete"; break;
- case "delete": action = "delete"; break;
- }
- if(yellow.page.statusCode==424 && paneType!="delete") action = "create";
- }
- }
- return action;
- },
-
- // Return text string
- getText: function(key)
- {
- return ("webinterface"+key in yellow.text) ? yellow.text["webinterface"+key] : "[webinterface"+key+"]";
- }
-}
-
-// Yellow toolbox with helpers
-yellow.toolbox =
-{
- // Insert element before reference element
- insertBefore: function(element, elementReference)
- {
- elementReference.parentNode.insertBefore(element, elementReference);
- },
-
- // Insert element after reference element
- insertAfter: function(element, elementReference)
- {
- elementReference.parentNode.insertBefore(element, elementReference.nextSibling);
- },
-
- // Add element class
- addClass: function(element, name)
- {
- var string = element.className + " " + name;
- element.className = string.replace(/^\s+|\s+$/, "");
- },
-
- // Remove element class
- removeClass: function(element, name)
- {
- var string = (" " + element.className + " ").replace(" " + name + " ", " ");
- element.className = string.replace(/^\s+|\s+$/, "");
- },
-
- // Add event handler
- addEvent: function(element, type, handler)
- {
- if(element.addEventListener) element.addEventListener(type, handler, false);
- else element.attachEvent('on'+type, handler);
- },
-
- // Return element of event
- getEventElement: function(e)
- {
- e = e ? e : window.event;
- return e.target ? e.target : e.srcElement;
- },
-
- // Return keycode of event
- getEventKeycode: function(e)
- {
- e = e ? e : window.event;
- return e.keyCode
- },
-
- // Set element width/height in pixel, including padding and border
- setOuterWidth: function(element, width, maxWidth)
- {
- width -= this.getBoxSize(element).width;
- if(maxWidth)
- {
- element.style.maxWidth = Math.max(0, width) + "px";
- } else {
- element.style.width = Math.max(0, width) + "px";
- }
- },
-
- setOuterHeight: function(element, height, maxHeight)
- {
- height -= this.getBoxSize(element).height;
- if(maxHeight)
- {
- element.style.maxHeight = Math.max(0, height) + "px";
- } else {
- element.style.height = Math.max(0, height) + "px";
- }
- },
-
- // Return element width/height in pixel, including padding and border
- getOuterWidth: function(element, includeMargin)
- {
- var width = element.offsetWidth;
- if(includeMargin) width += this.getMarginSize(element).width;
- return width;
- },
-
- getOuterHeight: function(element, includeMargin)
- {
- var height = element.offsetHeight;
- if(includeMargin) height += this.getMarginSize(element).height;
- return height;
- },
-
- // Return element width/height in pixel
- getWidth: function(element)
- {
- return element.offsetWidth - this.getBoxSize(element).width;
- },
-
- getHeight: function(element)
- {
- return element.offsetHeight - this.getBoxSize(element).height;
- },
-
- // Set element top/left position in pixel
- setOuterTop: function(element, top, marginTop)
- {
- if(marginTop)
- {
- element.style.marginTop = Math.max(0, top) + "px";
- } else {
- element.style.top = Math.max(0, top) + "px";
- }
- },
-
- setOuterLeft: function(element, left, marginLeft)
- {
- if(marginLeft)
- {
- element.style.marginLeft = Math.max(0, left) + "px";
- } else {
- element.style.left = Math.max(0, left) + "px";
- }
- },
-
- // Return element top/left position in pixel
- getOuterTop: function(element)
- {
- var top = element.getBoundingClientRect().top;
- return top + (window.pageYOffset || document.documentElement.scrollTop);
- },
-
- getOuterLeft: function(element)
- {
- var left = element.getBoundingClientRect().left;
- return left + (window.pageXOffset || document.documentElement.scrollLeft);
- },
-
- // Return window width/height in pixel
- getWindowWidth: function()
- {
- return window.innerWidth || document.documentElement.clientWidth;
- },
-
- getWindowHeight: function()
- {
- return window.innerHeight || document.documentElement.clientHeight;
- },
-
- // Return element CSS property
- getStyle: function(element, property)
- {
- var string = "";
- if(window.getComputedStyle)
- {
- string = window.getComputedStyle(element, null).getPropertyValue(property);
- } else {
- property = property.replace(/\-(\w)/g, function(match, m) { return m.toUpperCase(); });
- string = element.currentStyle[property];
- }
- return string;
- },
-
- // Return element CSS padding and border
- getBoxSize: function(element)
- {
- var paddingLeft = parseFloat(this.getStyle(element, "padding-left")) || 0;
- var paddingRight = parseFloat(this.getStyle(element, "padding-right")) || 0;
- var borderLeft = parseFloat(this.getStyle(element, "border-left-width")) || 0;
- var borderRight = parseFloat(this.getStyle(element, "border-right-width")) || 0;
- var width = paddingLeft + paddingRight + borderLeft + borderRight;
- var paddingTop = parseFloat(this.getStyle(element, "padding-top")) || 0;
- var paddingBottom = parseFloat(this.getStyle(element, "padding-bottom")) || 0;
- var borderTop = parseFloat(this.getStyle(element, "border-top-width")) || 0;
- var borderBottom = parseFloat(this.getStyle(element, "border-bottom-width")) || 0;
- var height = paddingTop + paddingBottom + borderTop + borderBottom;
- return { "width":width, "height":height };
- },
-
- // Return element CSS margin
- getMarginSize: function(element)
- {
- var marginLeft = parseFloat(this.getStyle(element, "margin-left")) || 0;
- var marginRight = parseFloat(this.getStyle(element, "margin-right")) || 0;
- var width = marginLeft + marginRight;
- var marginTop = parseFloat(this.getStyle(element, "margin-top")) || 0;
- var marginBottom = parseFloat(this.getStyle(element, "margin-bottom")) || 0;
- var height = marginTop + marginBottom;
- return { "width":width, "height":height };
- },
-
- // Check if element exists and is visible
- isVisible: function(element)
- {
- return element && element.style.display != "none";
- },
-
- // Encode newline characters
- encodeNewline: function(string)
- {
- return string
- .replace(/[%]/g, "%25")
- .replace(/[\r]/g, "%0d")
- .replace(/[\n]/g, "%0a");
- },
-
- // Encode HTML special characters
- encodeHtml: function(string)
- {
- return string
- .replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">")
- .replace(/"/g, """);
- },
-
- // Submit form with post method
- submitForm: function(params, encodeNewline)
- {
- var elementForm = document.createElement("form");
- elementForm.setAttribute("method", "post");
- for(var key in params)
- {
- if(!params.hasOwnProperty(key)) continue;
- var value = encodeNewline ? this.encodeNewline(params[key]) : params[key];
- var elementInput = document.createElement("input");
- elementInput.setAttribute("type", "hidden");
- elementInput.setAttribute("name", key);
- elementInput.setAttribute("value", value);
- elementForm.appendChild(elementInput);
- }
- document.body.appendChild(elementForm);
- elementForm.submit();
- }
-}
-
-yellow.webinterface.init();
-\ No newline at end of file
diff --git a/system/core/core-webinterface.php b/system/core/core-webinterface.php
@@ -1,932 +0,0 @@
-<?php
-// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-// Web interface core plugin
-class YellowWebinterface
-{
- const Version = "0.5.23";
- var $yellow; //access to API
- var $active; //web interface is active? (boolean)
- var $userLoginFailed; //web interface login failed? (boolean)
- var $userPermission; //web interface can change page? (boolean)
- var $users; //web interface users
- var $merge; //web interface merge
- var $rawDataSource; //raw data of page for comparison
- var $rawDataEdit; //raw data of page for editing
-
- // Handle initialisation
- function onLoad($yellow)
- {
- $this->yellow = $yellow;
- $this->users = new YellowWebinterfaceUsers($yellow);
- $this->merge = new YellowWebinterfaceMerge($yellow);
- $this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
- $this->yellow->config->setDefault("webinterfaceServerScheme", "http");
- $this->yellow->config->setDefault("webinterfaceServerName", $this->yellow->config->get("serverName"));
- $this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
- $this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
- $this->yellow->config->setDefault("webinterfaceUserHome", "/");
- $this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
- $this->yellow->config->setDefault("webinterfaceNewFile", "page-new-(.*).txt");
- $this->yellow->config->setDefault("webinterfaceMetaFilePrefix", "published");
- $this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
- }
-
- // Handle request
- function onRequest($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if($this->checkRequest($location))
- {
- list($serverScheme, $serverName, $base, $location, $fileName) = $this->updateRequestInformation();
- $statusCode = $this->processRequest($serverScheme, $serverName, $base, $location, $fileName);
- } else {
- $activeLocation = $this->yellow->config->get("webinterfaceLocation");
- if(rtrim($location, '/') == rtrim($activeLocation, '/'))
- {
- $statusCode = 301;
- $location = $this->yellow->lookup->normaliseUrl(
- $this->yellow->config->get("webinterfaceServerScheme"),
- $this->yellow->config->get("webinterfaceServerName"), $base, $activeLocation);
- $this->yellow->sendStatus($statusCode, $location);
- }
- }
- return $statusCode;
- }
-
- // Handle page meta data parsing
- function onParseMeta($page)
- {
- if($this->isActive() && $this->isUser())
- {
- if($page == $this->yellow->page)
- {
- if(empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
- if(empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
- if($page->statusCode == 424)
- {
- $title = $this->yellow->toolbox->createTextTitle($page->location);
- $this->rawDataEdit = $this->getDataNew($title);
- }
- }
- }
- }
-
- // Handle page content parsing of custom block
- function onParseContentBlock($page, $name, $text, $shortcut)
- {
- $output = NULL;
- if($name=="edit" && $shortcut)
- {
- $editText = "$name $text";
- if(substru($text, 0, 2)=="- ") $editText = trim(substru($text, 2));
- $output = "<a href=\"".$page->get("pageEdit")."\">".htmlspecialchars($editText)."</a>";
- }
- if($name=="debug" && $shortcut)
- {
- $output = "<div class=\"".htmlspecialchars($name)."\">\n";
- if(empty($text))
- {
- $serverSoftware = $this->yellow->toolbox->getServerSoftware();
- $output .= "Yellow ".Yellow::Version.", PHP ".PHP_VERSION.", $serverSoftware\n";
- } else {
- foreach($this->yellow->config->getData($text) as $key=>$value) $output .= htmlspecialchars("$key = $value")."<br />\n";
- if($page->parserSafeMode) $page->error(500, "Debug '$text' is not allowed!");
- }
- $output .= "</div>\n";
- }
- return $output;
- }
-
- // Handle page extra HTML data
- function onExtra($name)
- {
- $output = NULL;
- if($this->isActive() && $name=="header")
- {
- if($this->users->getNumber())
- {
- $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("pluginLocation")."core-webinterface";
- $output = "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".htmlspecialchars($location).".css\" />\n";
- $output .= "<script type=\"text/javascript\" src=\"".htmlspecialchars($location).".js\"></script>\n";
- $output .= "<script type=\"text/javascript\">\n";
- $output .= "// <![CDATA[\n";
- if($this->isUser())
- {
- $output .= "yellow.page.title = ".json_encode($this->getDataTitle($this->rawDataEdit)).";\n";
- $output .= "yellow.page.rawDataSource = ".json_encode($this->rawDataSource).";\n";
- $output .= "yellow.page.rawDataEdit = ".json_encode($this->rawDataEdit).";\n";
- $output .= "yellow.page.rawDataNew = ".json_encode($this->getDataNew()).";\n";
- $output .= "yellow.page.pageFile = ".json_encode($this->yellow->page->get("pageFile")).";\n";
- $output .= "yellow.page.userPermission = ".json_encode($this->userPermission).";\n";
- $output .= "yellow.page.parserSafeMode = ".json_encode($this->yellow->page->parserSafeMode).";\n";
- $output .= "yellow.page.statusCode = ".json_encode($this->yellow->page->statusCode).";\n";
- }
- $output .= "yellow.config = ".json_encode($this->getDataConfig()).";\n";
- $language = $this->isUser() ? $this->users->getLanguage() : $this->yellow->page->get("language");
- if(!$this->yellow->text->isLanguage($language)) $language = $this->yellow->config->get("language");
- $output .= "yellow.text = ".json_encode($this->yellow->text->getData("webinterface", $language)).";\n";
- if(defined("DEBUG")) $output .= "yellow.debug = ".json_encode(DEBUG).";\n";
- $output .= "// ]]>\n";
- $output .= "</script>\n";
- }
- }
- return $output;
- }
-
- // Handle command
- function onCommand($args)
- {
- list($name, $command) = $args;
- switch($command)
- {
- case "user": $statusCode = $this->userCommand($args); break;
- default: $statusCode = 0;
- }
- return $statusCode;
- }
-
- // Handle command help
- function onCommandHelp()
- {
- return "user [EMAIL PASSWORD NAME LANGUAGE HOME]\n";
- }
-
- // Update user account
- function userCommand($args)
- {
- $statusCode = 0;
- list($dummy, $command, $email, $password, $name, $language, $home) = $args;
- if(empty($home) || $home[0]=='/')
- {
- if(!empty($email) && !empty($password))
- {
- $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
- $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
- $cost = $this->yellow->config->get("webinterfaceUserHashCost");
- $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
- if(empty($hash))
- {
- $statusCode = 500;
- echo "ERROR creating hash: Algorithm '$algorithm' not supported!\n";
- } else {
- $statusCode = $this->users->createUser($fileName, $email, $hash, $name, $language, $home) ? 200 : 500;
- if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n";
- }
- echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "");
- echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
- } else {
- $statusCode = 200;
- foreach($this->getUserData() as $line) echo "$line\n";
- if(!$this->users->getNumber()) echo "Yellow $command: No user accounts\n";
- }
- } else {
- echo "Yellow $command: Invalid arguments\n";
- $statusCode = 400;
- }
- return $statusCode;
- }
-
- // Process request
- function processRequest($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if($this->checkUser($location, $fileName))
- {
- switch($_POST["action"])
- {
- case "": $statusCode = $this->processRequestShow($serverScheme, $serverName, $base, $location, $fileName); break;
- case "create": $statusCode = $this->processRequestCreate($serverScheme, $serverName, $base, $location, $fileName); break;
- case "edit": $statusCode = $this->processRequestEdit($serverScheme, $serverName, $base, $location, $fileName); break;
- case "delete": $statusCode = $this->processRequestDelete($serverScheme, $serverName, $base, $location, $fileName); break;
- case "login": $statusCode = $this->processRequestLogin($serverScheme, $serverName, $base, $location, $fileName); break;
- case "logout": $statusCode = $this->processRequestLogout($serverScheme, $serverName, $base, $location, $fileName); break;
- }
- }
- if($statusCode == 0)
- {
- $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- if($this->users->getNumber())
- {
- if($this->userLoginFailed) $this->yellow->page->error(401);
- } else {
- $url = $this->yellow->text->get("webinterfaceUserAccountUrl");
- $this->yellow->page->error(500, "You are not authorised on this server, [please add a user account]($url)!");
- }
- }
- return $statusCode;
- }
-
- // Process request to show page
- function processRequestShow($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if(is_readable($fileName))
- {
- $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- } else {
- if($this->yellow->isRequestContentDirectory($location))
- {
- $statusCode = 301;
- $location = $this->yellow->lookup->isFileLocation($location) ? "$location/" : "/".$this->yellow->getRequestLanguage()."/";
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
- $this->yellow->sendStatus($statusCode, $location);
- } else {
- $statusCode = $this->userPermission ? 424 : 404;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- $this->yellow->page->error($statusCode);
- }
- }
- return $statusCode;
- }
-
- // Process request to create page
- function processRequestCreate($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if($this->userPermission && !empty($_POST["rawdataedit"]))
- {
- $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
- $page = $this->getPageNew($serverScheme, $serverName, $base, $location, $fileName, rawurldecode($_POST["rawdataedit"]));
- if(!$page->isError())
- {
- if($this->yellow->toolbox->createFile($page->fileName, $page->rawData))
- {
- $statusCode = 303;
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $page->location);
- $this->yellow->sendStatus($statusCode, $location);
- } else {
- $statusCode = 500;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
- }
- } else {
- $statusCode = 500;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, $false);
- $this->yellow->page->error($statusCode, $page->get("pageError"));
- }
- }
- return $statusCode;
- }
-
- // Process request to edit page
- function processRequestEdit($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if($this->userPermission && !empty($_POST["rawdataedit"]))
- {
- $this->rawDataSource = rawurldecode($_POST["rawdatasource"]);
- $this->rawDataEdit = rawurldecode($_POST["rawdataedit"]);
- $page = $this->getPageUpdate($serverScheme, $serverName, $base, $location, $fileName,
- $this->rawDataSource, $this->rawDataEdit, $this->yellow->toolbox->getFileData($fileName));
- if(!$page->isError())
- {
- if($this->yellow->toolbox->renameFile($fileName, $page->fileName) &&
- $this->yellow->toolbox->createFile($page->fileName, $page->rawData))
- {
- $statusCode = 303;
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $page->location);
- $this->yellow->sendStatus($statusCode, $location);
- } else {
- $statusCode = 500;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
- }
- } else {
- $statusCode = 500;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- $this->yellow->page->error($statusCode, $page->get("pageError"));
- }
- }
- return $statusCode;
- }
-
- // Process request to delete page
- function processRequestDelete($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- if($this->userPermission)
- {
- $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
- if(!is_file($fileName) || $this->yellow->toolbox->deleteFile($fileName))
- {
- $statusCode = 303;
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
- $this->yellow->sendStatus($statusCode, $location);
- } else {
- $statusCode = 500;
- $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
- $this->yellow->page->error($statusCode, "Can't delete file '$fileName'!");
- }
- }
- return $statusCode;
- }
-
- // Process request for user login
- function processRequestLogin($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 0;
- $home = $this->users->getHome();
- if(substru($location, 0, strlenu($home)) == $home)
- {
- $statusCode = 303;
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
- $this->yellow->sendStatus($statusCode, $location);
- } else {
- $statusCode = 302;
- $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $home);
- $this->yellow->sendStatus($statusCode, $location);
- }
- return $statusCode;
- }
-
- // Process request for user logout
- function processRequestLogout($serverScheme, $serverName, $base, $location, $fileName)
- {
- $statusCode = 302;
- $this->users->destroyCookie("login");
- $this->users->email = "";
- $location = $this->yellow->lookup->normaliseUrl(
- $this->yellow->config->get("serverScheme"),
- $this->yellow->config->get("serverName"),
- $this->yellow->config->get("serverBase"), $location);
- $this->yellow->sendStatus($statusCode, $location);
- return $statusCode;
- }
-
- // Check web interface request
- function checkRequest($location)
- {
- if($this->yellow->toolbox->getServerScheme()==$this->yellow->config->get("webinterfaceServerScheme") &&
- $this->yellow->toolbox->getServerName()==$this->yellow->config->get("webinterfaceServerName"))
- {
- $locationLength = strlenu($this->yellow->config->get("webinterfaceLocation"));
- $this->active = substru($location, 0, $locationLength) == $this->yellow->config->get("webinterfaceLocation");
- }
- return $this->isActive();
- }
-
- // Check web interface user
- function checkUser($location, $fileName)
- {
- if($_POST["action"] == "login")
- {
- $email = $_POST["email"];
- $password = $_POST["password"];
- if($this->users->checkUser($email, $password))
- {
- $this->users->createCookie("login", $email);
- $this->users->email = $email;
- $this->userPermission = $this->getUserPermission($location, $fileName);
- } else {
- $this->userLoginFailed = true;
- }
- } else if(isset($_COOKIE["login"])) {
- list($email, $session) = $this->users->getCookieInformation($_COOKIE["login"]);
- if($this->users->checkCookie($email, $session))
- {
- $this->users->email = $email;
- $this->userPermission = $this->getUserPermission($location, $fileName);
- } else {
- $this->userLoginFailed = true;
- }
- }
- return $this->isUser();
- }
-
- // Return permission to change page
- function getUserPermission($location, $fileName)
- {
- $userPermission = NULL;
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onUserPermission"))
- {
- $userPermission = $value["obj"]->onUserPermission($location, $fileName, $this->users);
- if(!is_null($userPermission)) break;
- }
- }
- if(is_null($userPermission))
- {
- $userPermission = is_dir(dirname($fileName)) && strlenu(basename($fileName))<128;
- $userPermission &= substru($location, 0, strlenu($this->users->getHome())) == $this->users->getHome();
- }
- return $userPermission;
- }
-
- // Return user data
- function getUserData()
- {
- $data = array();
- foreach($this->users->users as $key=>$value) $data[$key] = "$value[email] - $value[name] $value[language] $value[home]";
- usort($data, strnatcasecmp);
- return $data;
- }
-
- // Update request information
- function updateRequestInformation()
- {
- $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
- $serverName = $this->yellow->config->get("webinterfaceServerName");
- $base = rtrim($this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation"), '/');
- $this->yellow->page->base = $base;
- return $this->yellow->getRequestInformation($serverScheme, $serverName, $base);
- }
-
- // Update page data with title
- function updateDataTitle($rawData, $title)
- {
- foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
- {
- if(preg_match("/^(\s*Title\s*:\s*)(.*?)(\s*)$/i", $line, $matches)) $line = $matches[1].$title.$matches[3];
- $rawDataNew .= $line;
- }
- return $rawDataNew;
- }
-
- // Return page data title
- function getDataTitle($rawData)
- {
- $title = $this->yellow->page->get("title");
- if(preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)[\r\n]+\-\-\-[\r\n]+/s", $rawData))
- {
- foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
- {
- if(preg_match("/^(\s*Title\s*:\s*)(.*?)(\s*)$/i", $line, $matches)) { $title = $matches[2]; break; }
- }
- }
- return $title;
- }
-
- // Return new page
- function getPageNew($serverScheme, $serverName, $base, $location, $fileName, $rawData)
- {
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
- $page->parseData($rawData, false, 0);
- $page->fileName = $this->yellow->lookup->findFileFromTitle(
- $page->get($this->yellow->config->get("webinterfaceMetaFilePrefix")), $page->get("title"), $fileName,
- $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
- $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
- if($this->yellow->pages->find($page->location))
- {
- preg_match("/^(.*?)(\d*)$/", $page->get("title"), $matches);
- $titleText = $matches[1];
- $titleNumber = $matches[2];
- if(strempty($titleNumber)) { $titleNumber = 2; $titleText = $titleText.' '; }
- for(; $titleNumber<=999; ++$titleNumber)
- {
- $page->rawData = $this->updateDataTitle($rawData, $titleText.$titleNumber);
- $page->fileName = $this->yellow->lookup->findFileFromTitle(
- $page->get($this->yellow->config->get("webinterfaceMetaFilePrefix")), $titleText.$titleNumber, $fileName,
- $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
- $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
- if(!$this->yellow->pages->find($page->location)) { $ok = true; break; }
- }
- if(!$ok) $page->error(500, "Page '".$page->get("title")."' can not be created!");
- }
- if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
- return $page;
- }
-
- // Return modified page
- function getPageUpdate($serverScheme, $serverName, $base, $location, $fileName, $rawDataSource, $rawDataEdit, $rawDataFile)
- {
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
- $page->parseData($this->merge->merge($rawDataSource, $rawDataEdit, $rawDataFile), false, 0);
- if(empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
- if($this->yellow->lookup->isFileLocation($location) && !$page->isError())
- {
- $pageSource = new YellowPage($this->yellow);
- $pageSource->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
- $pageSource->parseData($rawDataSource, false, 0);
- $prefix = $this->yellow->config->get("webinterfaceMetaFilePrefix");
- if($pageSource->get($prefix)!=$page->get($prefix) || $pageSource->get("title")!=$page->get("title"))
- {
- $page->fileName = $this->yellow->lookup->findFileFromTitle(
- $page->get($prefix), $page->get("title"), $fileName,
- $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
- $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
- if($pageSource->location!=$page->location && $this->yellow->pages->find($page->location))
- {
- $page->error(500, "Page '".$page->get("title")."' already exists!");
- }
- }
- }
- if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
- return $page;
- }
-
- // Return content data for new page
- function getDataNew($title = "")
- {
- $fileName = $this->yellow->lookup->findFileFromLocation($this->yellow->page->location);
- $fileName = $this->yellow->lookup->findFileNew($fileName,
- $this->yellow->config->get("webinterfaceNewFile"), $this->yellow->config->get("configDir"),
- $this->yellow->config->get("template"));
- $fileData = $this->yellow->toolbox->getFileData($fileName);
- $fileData = preg_replace("/@datetime/i", date("Y-m-d H:i:s"), $fileData);
- $fileData = preg_replace("/@date/i", date("Y-m-d"), $fileData);
- $fileData = preg_replace("/@username/i", $this->users->getName(), $fileData);
- $fileData = preg_replace("/@userlanguage/i", $this->users->getLanguage(), $fileData);
- if(!empty($title)) $fileData = $this->updateDataTitle($fileData, $title);
- return $fileData;
- }
-
- // Return configuration data including information of current user
- function getDataConfig()
- {
- $data = $this->yellow->config->getData("", "Location");
- if($this->isUser())
- {
- $data["userEmail"] = $this->users->email;
- $data["userName"] = $this->users->getName();
- $data["userLanguage"] = $this->users->getLanguage();
- $data["userHome"] = $this->users->getHome();
- $data["serverScheme"] = $this->yellow->config->get("serverScheme");
- $data["serverName"] = $this->yellow->config->get("serverName");
- $data["serverBase"] = $this->yellow->config->get("serverBase");
- } else {
- $data["login"] = $this->yellow->page->statusCode==200;
- $data["loginEmail"] = $this->yellow->config->get("loginEmail");
- $data["loginPassword"] = $this->yellow->config->get("loginPassword");
- }
- return $data;
- }
-
- // Check if web interface request
- function isActive()
- {
- return $this->active;
- }
-
- // Check if user is logged in
- function isUser()
- {
- return !empty($this->users->email);
- }
-}
-
-// Yellow web interface users
-class YellowWebinterfaceUsers
-{
- var $yellow; //access to API
- var $users; //registered users
- var $email; //current user
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->users = array();
- }
-
- // Load users from file
- function load($fileName)
- {
- $fileData = @file($fileName);
- if($fileData)
- {
- foreach($fileData as $line)
- {
- if(preg_match("/^\//", $line)) continue;
- preg_match("/^(.*?),\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
- if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]))
- {
- $this->set($matches[1], $matches[2], $matches[3], $matches[4], $matches[5]);
- if(defined("DEBUG") && DEBUG>=3) echo "YellowWebinterfaceUsers::load email:$matches[1] $matches[3]<br/>\n";
- }
- }
- }
- }
-
- // Set user data
- function set($email, $hash, $name, $language, $home)
- {
- $this->users[$email] = array();
- $this->users[$email]["email"] = $email;
- $this->users[$email]["hash"] = $hash;
- $this->users[$email]["name"] = $name;
- $this->users[$email]["language"] = $language;
- $this->users[$email]["home"] = $home;
- }
-
- // Create or update user in file
- function createUser($fileName, $email, $hash, $name, $language, $home)
- {
- $email = strreplaceu(',', '-', $email);
- $hash = strreplaceu(',', '-', $hash);
- $fileData = @file($fileName);
- if($fileData)
- {
- foreach($fileData as $line)
- {
- preg_match("/^(.*?),\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
- if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]))
- {
- if($matches[1] == $email)
- {
- $name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
- $language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
- $home = strreplaceu(',', '-', empty($home) ? $matches[5] : $home);
- $fileDataNew .= "$email,$hash,$name,$language,$home\n";
- $found = true;
- continue;
- }
- }
- $fileDataNew .= $line;
- }
- }
- if(!$found)
- {
- $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
- $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
- $home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home);
- $fileDataNew .= "$email,$hash,$name,$language,$home\n";
- }
- return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
- }
-
- // Check user login
- function checkUser($email, $password)
- {
- $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
- return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]);
- }
-
- // Create browser cookie
- function createCookie($cookieName, $email)
- {
- if($this->isExisting($email))
- {
- $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
- $serverName = $this->yellow->config->get("webinterfaceServerName");
- $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
- $expire = time()+60*60*24*30*365;
- $session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256");
- if(empty($session)) $session = "error-hash-algorithm-sha256";
- if($serverName == "localhost") $serverName = false;
- setcookie($cookieName, "$email,$session", $expire, $location, $serverName, $serverScheme=="https");
- }
- }
-
- // Destroy browser cookie
- function destroyCookie($cookieName)
- {
- $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
- $serverName = $this->yellow->config->get("webinterfaceServerName");
- $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
- if($serverName == "localhost") $serverName = false;
- setcookie($cookieName, "", time()-3600, $location, $serverName, $serverScheme=="https");
- }
-
- // Return information from browser cookie
- function getCookieInformation($cookie)
- {
- return explode(',', $cookie, 2);
- }
-
- // Check user login from browser cookie
- function checkCookie($email, $session)
- {
- return $this->isExisting($email) && $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session);
- }
-
- // Retun user login information
- function getUserInfo($email, $password, $name, $language, $home)
- {
- $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
- $cost = $this->yellow->config->get("webinterfaceUserHashCost");
- $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
- if(!empty($hash))
- {
- $email = strreplaceu(',', '-', $email);
- $hash = strreplaceu(',', '-', $hash);
- $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
- $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
- $home = strreplaceu(',', '-', empty($home) ? "/" : $home);
- $user = "$email,$hash,$name,$language,$home\n";
- }
- return $user;
- }
-
- // Return user name
- function getName($email = "")
- {
- if(empty($email)) $email = $this->email;
- return $this->isExisting($email) ? $this->users[$email]["name"] : "";
- }
-
- // Return user language
- function getLanguage($email = "")
- {
- if(empty($email)) $email = $this->email;
- return $this->isExisting($email) ? $this->users[$email]["language"] : "";
- }
-
- // Return user home
- function getHome($email = "")
- {
- if(empty($email)) $email = $this->email;
- return $this->isExisting($email) ? $this->users[$email]["home"] : "";
- }
-
- // Return number of users
- function getNumber()
- {
- return count($this->users);
- }
-
- // Check if user exists
- function isExisting($email)
- {
- return !is_null($this->users[$email]);
- }
-}
-
-// Yellow web interface merge
-class YellowWebinterfaceMerge
-{
- var $yellow; //access to API
- const Add = '+'; //merge types
- const Modify = '*';
- const Remove = '-';
- const Same = ' ';
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- }
-
- // Merge text, NULL if not possible
- function merge($textSource, $textMine, $textYours, $showDiff = false)
- {
- if($textMine != $textYours)
- {
- $diffMine = $this->buildDiff($textSource, $textMine);
- $diffYours = $this->buildDiff($textSource, $textYours);
- $diff = $this->mergeDiff($diffMine, $diffYours);
- $output = $this->getOutput($diff, $showDiff);
- } else {
- $output = $textMine;
- }
- return $output;
- }
-
- // Build differences to common source
- function buildDiff($textSource, $textOther)
- {
- $diff = array();
- $lastRemove = -1;
- $textStart = 0;
- $textSource = $this->yellow->toolbox->getTextLines($textSource);
- $textOther = $this->yellow->toolbox->getTextLines($textOther);
- $sourceEnd = $sourceSize = count($textSource);
- $otherEnd = $otherSize = count($textOther);
- while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$textStart]==$textOther[$textStart]) ++$textStart;
- while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$sourceEnd-1]==$textOther[$otherEnd-1])
- {
- --$sourceEnd; --$otherEnd;
- }
- for($pos=0; $pos<$textStart; ++$pos) array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$pos], false));
- $lcs = $this->buildDiffLCS($textSource, $textOther, $textStart, $sourceEnd-$textStart, $otherEnd-$textStart);
- for($x=0,$y=0,$xEnd=$otherEnd-$textStart,$yEnd=$sourceEnd-$textStart; $x<$xEnd || $y<$yEnd;)
- {
- $max = $lcs[$y][$x];
- if($y<$yEnd && $lcs[$y+1][$x]==$max)
- {
- array_push($diff, array(YellowWebinterfaceMerge::Remove, $textSource[$textStart+$y], false));
- if($lastRemove == -1) $lastRemove = count($diff)-1;
- ++$y;
- continue;
- }
- if($x<$xEnd && $lcs[$y][$x+1]==$max)
- {
- if($lastRemove==-1 || $diff[$lastRemove][0]!=YellowWebinterfaceMerge::Remove)
- {
- array_push($diff, array(YellowWebinterfaceMerge::Add, $textOther[$textStart+$x], false));
- $lastRemove = -1;
- } else {
- $diff[$lastRemove] = array(YellowWebinterfaceMerge::Modify, $textOther[$textStart+$x], false);
- ++$lastRemove; if(count($diff)==$lastRemove) $lastRemove = -1;
- }
- ++$x;
- continue;
- }
- array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$textStart+$y], false));
- $lastRemove = -1;
- ++$x;
- ++$y;
- }
- for($pos=$sourceEnd;$pos<$sourceSize; ++$pos) array_push($diff, array(YellowWebinterfaceMerge::Same, $textSource[$pos], false));
- return $diff;
- }
-
- // Build longest common subsequence
- function buildDiffLCS($textSource, $textOther, $textStart, $yEnd, $xEnd)
- {
- $lcs = array_fill(0, $yEnd+1, array_fill(0, $xEnd+1, 0));
- for($y=$yEnd-1; $y>=0; --$y)
- {
- for($x=$xEnd-1; $x>=0; --$x)
- {
- if($textSource[$textStart+$y] == $textOther[$textStart+$x])
- {
- $lcs[$y][$x] = $lcs[$y+1][$x+1]+1;
- } else {
- $lcs[$y][$x] = max($lcs[$y][$x+1], $lcs[$y+1][$x]);
- }
- }
- }
- return $lcs;
- }
-
- // Merge differences
- function mergeDiff($diffMine, $diffYours)
- {
- $diff = array();
- $posMine = $posYours = 0;
- while($posMine<count($diffMine) && $posYours<count($diffYours))
- {
- $typeMine = $diffMine[$posMine][0];
- $typeYours = $diffYours[$posYours][0];
- if($typeMine==YellowWebinterfaceMerge::Same)
- {
- array_push($diff, $diffYours[$posYours]);
- } else if($typeYours==YellowWebinterfaceMerge::Same) {
- array_push($diff, $diffMine[$posMine]);
- } else if($typeMine==YellowWebinterfaceMerge::Add && $typeYours==YellowWebinterfaceMerge::Add) {
- $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
- } else if($typeMine==YellowWebinterfaceMerge::Modify && $typeYours==YellowWebinterfaceMerge::Modify) {
- $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
- } else if($typeMine==YellowWebinterfaceMerge::Remove && $typeYours==YellowWebinterfaceMerge::Remove) {
- array_push($diff, $diffMine[$posMine]);
- } else if($typeMine==YellowWebinterfaceMerge::Add) {
- array_push($diff, $diffMine[$posMine]);
- } else if($typeYours==YellowWebinterfaceMerge::Add) {
- array_push($diff, $diffYours[$posYours]);
- } else {
- $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], true);
- }
- if(defined("DEBUG") && DEBUG>=2) echo "YellowWebinterfaceMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
- if($typeMine==YellowWebinterfaceMerge::Add || $typeYours==YellowWebinterfaceMerge::Add)
- {
- if($typeMine==YellowWebinterfaceMerge::Add) ++$posMine;
- if($typeYours==YellowWebinterfaceMerge::Add) ++$posYours;
- } else {
- ++$posMine;
- ++$posYours;
- }
- }
- for(;$posMine<count($diffMine); ++$posMine)
- {
- array_push($diff, $diffMine[$posMine]);
- $typeMine = $diffMine[$posMine][0]; $typeYours = ' ';
- if(defined("DEBUG") && DEBUG>=2) echo "YellowWebinterfaceMerge::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 "YellowWebinterfaceMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
- }
- return $diff;
- }
-
- // Merge potential conflict
- function mergeConflict(&$diff, $diffMine, $diffYours, $conflict)
- {
- if(!$conflict && $diffMine[1]==$diffYours[1])
- {
- array_push($diff, $diffMine);
- } else {
- array_push($diff, array($diffMine[0], $diffMine[1], true));
- array_push($diff, array($diffYours[0], $diffYours[1], true));
- }
- }
-
- // Return merged text, NULL if not possible
- function getOutput($diff, $showDiff = false)
- {
- $output = "";
- if(!$showDiff)
- {
- for($i=0; $i<count($diff); ++$i)
- {
- if($diff[$i][0] != YellowWebinterfaceMerge::Remove) $output .= $diff[$i][1];
- $conflict |= $diff[$i][2];
- }
- } else {
- for($i=0; $i<count($diff); ++$i)
- {
- $output .= $diff[$i][2] ? "! " : $diff[$i][0].' ';
- $output .= $diff[$i][1];
- }
- }
- return !$conflict ? $output : NULL;
- }
-}
-
-$yellow->plugins->register("webinterface", "YellowWebinterface", YellowWebinterface::Version);
-?>
-\ No newline at end of file
diff --git a/system/core/core.php b/system/core/core.php
@@ -1,2869 +0,0 @@
-<?php
-// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
-// This file may be used and distributed under the terms of the public license.
-
-// Yellow main class
-class Yellow
-{
- const Version = "0.5.33";
- var $page; //current page
- var $pages; //pages from file system
- var $files; //files from file system
- var $plugins; //plugins
- var $config; //configuration
- var $text; //text strings
- var $lookup; //location and file lookup
- var $toolbox; //toolbox with helpers
-
- function __construct()
- {
- $this->page = new YellowPage($this);
- $this->pages = new YellowPages($this);
- $this->files = new YellowFiles($this);
- $this->plugins = new YellowPlugins($this);
- $this->config = new YellowConfig($this);
- $this->text = new YellowText($this);
- $this->lookup = new YellowLookup($this);
- $this->toolbox = new YellowToolbox();
- $this->config->setDefault("sitename", "Yellow");
- $this->config->setDefault("author", "Yellow");
- $this->config->setDefault("language", "en");
- $this->config->setDefault("theme", "default");
- $this->config->setDefault("timeZone", $this->toolbox->getTimeZone());
- $this->config->setDefault("serverScheme", $this->toolbox->getServerScheme());
- $this->config->setDefault("serverName", $this->toolbox->getServerName());
- $this->config->setDefault("serverBase", $this->toolbox->getServerBase());
- $this->config->setDefault("imageLocation", "/media/images/");
- $this->config->setDefault("pluginLocation", "/media/plugins/");
- $this->config->setDefault("themeLocation", "/media/themes/");
- $this->config->setDefault("systemDir", "system/");
- $this->config->setDefault("configDir", "system/config/");
- $this->config->setDefault("coreDir", "system/core/");
- $this->config->setDefault("pluginDir", "system/plugins/");
- $this->config->setDefault("themeDir", "system/themes/");
- $this->config->setDefault("snippetDir", "system/themes/snippets/");
- $this->config->setDefault("templateDir", "system/themes/templates/");
- $this->config->setDefault("mediaDir", "media/");
- $this->config->setDefault("imageDir", "media/images/");
- $this->config->setDefault("staticDir", "cache/");
- $this->config->setDefault("staticAccessFile", ".htaccess");
- $this->config->setDefault("staticDefaultFile", "index.html");
- $this->config->setDefault("staticErrorFile", "error.html");
- $this->config->setDefault("contentDir", "content/");
- $this->config->setDefault("contentRootDir", "default/");
- $this->config->setDefault("contentHomeDir", "home/");
- $this->config->setDefault("contentDefaultFile", "page.txt");
- $this->config->setDefault("contentPagination", "page");
- $this->config->setDefault("contentExtension", ".txt");
- $this->config->setDefault("configExtension", ".ini");
- $this->config->setDefault("configFile", "config.ini");
- $this->config->setDefault("textFile", "language-(.*).ini");
- $this->config->setDefault("errorFile", "page-error-(.*).txt");
- $this->config->setDefault("robotsFile", "robots.txt");
- $this->config->setDefault("iconFile", "icon.png");
- $this->config->setDefault("template", "default");
- $this->config->setDefault("navigation", "navigation");
- $this->config->setDefault("sidebar", "sidebar");
- $this->config->setDefault("parser", "markdown");
- $this->config->setDefault("parserSafeMode", "0");
- $this->config->setDefault("multiLanguageMode", "0");
- $this->load();
- }
-
- // Initialise configuration
- function load()
- {
- if(defined("DEBUG") && DEBUG>=3)
- {
- $serverSoftware = $this->toolbox->getServerSoftware();
- echo "Yellow ".Yellow::Version.", PHP ".PHP_VERSION.", $serverSoftware<br>\n";
- }
- $this->config->load($this->config->get("configDir").$this->config->get("configFile"));
- $this->text->load($this->config->get("configDir").$this->config->get("textFile"));
- date_default_timezone_set($this->config->get("timeZone"));
- list($pathRoot, $pathHome) = $this->lookup->getContentInformation();
- $this->config->set("contentRootDir", $pathRoot);
- $this->config->set("contentHomeDir", $pathHome);
- }
-
- // Handle request
- function request()
- {
- ob_start();
- $statusCode = 0;
- $this->toolbox->timerStart($time);
- list($serverScheme, $serverName, $base, $location, $fileName) = $this->getRequestInformation();
- $this->page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
- foreach($this->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onRequest"))
- {
- $this->pages->requestHandler = $key;
- $statusCode = $value["obj"]->onRequest($serverScheme, $serverName, $base, $location, $fileName);
- if($statusCode != 0) break;
- }
- }
- if($statusCode == 0)
- {
- $this->pages->requestHandler = "core";
- $statusCode = $this->processRequest($serverScheme, $serverName, $base, $location, $fileName, true);
- }
- if($this->page->isError()) $statusCode = $this->processRequestError();
- $this->toolbox->timerStop($time);
- ob_end_flush();
- if(defined("DEBUG") && DEBUG>=1) echo "Yellow::request status:$statusCode location:$location<br/>\n";
- if(defined("DEBUG") && DEBUG>=1) echo "Yellow::request time:$time ms<br/>\n";
- return $statusCode;
- }
-
- // Process request
- function processRequest($serverScheme, $serverName, $base, $location, $fileName, $cacheable)
- {
- $statusCode = 0;
- if(is_readable($fileName))
- {
- if($this->toolbox->isRequestCleanUrl($location))
- {
- $statusCode = 303;
- $locationArgs = $this->toolbox->getLocationArgsCleanUrl($this->config->get("contentPagination"));
- $location = $this->lookup->normaliseUrl($serverScheme, $serverName, $base, $location.$locationArgs);
- $this->sendStatus($statusCode, $location);
- }
- } else {
- if($this->isRequestContentDirectory($location))
- {
- $statusCode = 301;
- $location = $this->lookup->isFileLocation($location) ? "$location/" : "/".$this->getRequestLanguage()."/";
- $location = $this->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
- $this->sendStatus($statusCode, $location);
- }
- }
- if($statusCode == 0)
- {
- $statusCode = is_readable($fileName) ? 200 : 404;
- $fileName = $this->getStaticFileFromCache($location, $fileName, $cacheable, $statusCode);
- if($this->isStaticFile($fileName))
- {
- $statusCode = $this->sendFile($statusCode, $fileName, $cacheable);
- } else {
- $fileName = $this->readPage($serverScheme, $serverName, $base, $location, $fileName, $cacheable, $statusCode);
- $statusCode = $this->sendPage();
- }
- }
- if(defined("DEBUG") && DEBUG>=1)
- {
- $handler = $this->getRequestHandler();
- echo "Yellow::processRequest file:$fileName handler:$handler<br/>\n";
- }
- return $statusCode;
- }
-
- // Process request with error
- function processRequestError()
- {
- ob_clean();
- $fileName = $this->readPage($this->page->serverScheme, $this->page->serverName, $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)
- {
- $handler = $this->getRequestHandler();
- echo "Yellow::processRequestError file:$fileName handler:$handler<br/>\n";
- }
- return $statusCode;
- }
-
- // Read page
- function readPage($serverScheme, $serverName, $base, $location, $fileName, $cacheable, $statusCode, $pageError = "")
- {
- if($statusCode >= 400)
- {
- $fileName = $this->config->get("configDir").$this->config->get("errorFile");
- $fileName = strreplaceu("(.*)", $statusCode, $fileName);
- $cacheable = false;
- }
- $this->page = new YellowPage($this);
- $this->page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
- $this->page->parseData($this->toolbox->getFileData($fileName), $cacheable, $statusCode, $pageError);
- $this->text->setLanguage($this->page->get("language"));
- $this->page->parseContent();
- return $fileName;
- }
-
- // Send page response
- function sendPage()
- {
- $this->page->parsePage();
- $statusCode = $this->page->statusCode;
- $lastModifiedFormatted = $this->page->getHeader("Last-Modified");
- if($statusCode==200 && $this->page->isCacheable() && $this->toolbox->isRequestNotModified($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)
- {
- foreach($this->page->headerData as $key=>$value) echo "Yellow::sendPage $key: $value<br/>\n";
- $fileNameTheme = $this->config->get("themeDir").$this->page->get("theme").".css";
- $templateName = $this->page->get("template");
- $parserName = $this->page->get("parser");
- echo "Yellow::sendPage theme:$fileNameTheme template:$templateName parser:$parserName<br/>\n";
- }
- return $statusCode;
- }
-
- // Send file response
- function sendFile($statusCode, $fileName, $cacheable)
- {
- $lastModifiedFormatted = $this->toolbox->getHttpDateFormatted(filemtime($fileName));
- if($statusCode==200 && $cacheable && $this->toolbox->isRequestNotModified($lastModifiedFormatted))
- {
- $statusCode = 304;
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- } else {
- @header($this->toolbox->getHttpStatusFormatted($statusCode));
- if(!$cacheable) @header("Cache-Control: no-cache, must-revalidate");
- @header("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
- @header("Last-Modified: ".$lastModifiedFormatted);
- echo $this->toolbox->getFileData($fileName);
- }
- return $statusCode;
- }
-
- // Send status response
- function sendStatus($statusCode, $location = "")
- {
- if(!empty($location)) $this->page->clean($statusCode, $location);
- @header($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 "Yellow::sendStatus $key: $value<br/>\n";
- }
- }
-
- // Return request information
- function getRequestInformation($serverScheme = "", $serverName = "", $base = "")
- {
- $serverScheme = empty($serverScheme) ? $this->config->get("serverScheme") : $serverScheme;
- $serverName = empty($serverName) ? $this->config->get("serverName") : $serverName;
- $base = empty($base) ? $this->config->get("serverBase") : $base;
- $location = $this->toolbox->getLocationClean();
- $location = substru($location, strlenu($base));
- if(preg_match("/\.(css|js|jpg|png|txt|woff)$/", $location))
- {
- $pluginLocationLength = strlenu($this->config->get("pluginLocation"));
- $themeLocationLength = strlenu($this->config->get("themeLocation"));
- if(substru($location, 0, $pluginLocationLength+5) == $this->config->get("pluginLocation")."core-")
- {
- $fileName = $this->config->get("coreDir").substru($location, $pluginLocationLength);
- } else if(substru($location, 0, $pluginLocationLength) == $this->config->get("pluginLocation")) {
- $fileName = $this->config->get("pluginDir").substru($location, $pluginLocationLength);
- } else if(substru($location, 0, $themeLocationLength) == $this->config->get("themeLocation")) {
- $fileName = $this->config->get("themeDir").substru($location, $themeLocationLength);
- } else if($location == "/".$this->config->get("robotsFile")) {
- $fileName = $this->config->get("configDir").$this->config->get("robotsFile");
- }
- }
- if(empty($fileName)) $fileName = $this->lookup->findFileFromLocation($location);
- return array($serverScheme, $serverName, $base, $location, $fileName);
- }
-
- // Return request language
- function getRequestLanguage()
- {
- return $this->toolbox->detectBrowserLanguage($this->pages->getLanguages(), $this->config->get("language"));
- }
-
- // Return request handler
- function getRequestHandler()
- {
- return $this->pages->requestHandler;
- }
-
- // Return snippet arguments
- function getSnippetArgs()
- {
- return $this->pages->snippetArgs;
- }
-
- // Return static file from cache if available
- function getStaticFileFromCache($location, $fileName, $cacheable, $statusCode)
- {
- if(PHP_SAPI != "cli" && $cacheable)
- {
- if($statusCode == 200)
- {
- $location .= $this->toolbox->getLocationArgs();
- $fileNameStatic = rtrim($this->config->get("staticDir"), '/').$location;
- if(!$this->lookup->isFileLocation($location)) $fileNameStatic .= $this->config->get("staticDefaultFile");
- } else if($statusCode == 404) {
- $fileNameStatic = $this->config->get("staticDir").$this->config->get("staticErrorFile");
- }
- if(is_readable($fileNameStatic)) $fileName = $fileNameStatic;
- }
- return $fileName;
- }
-
- // Check if static file
- function isStaticFile($fileName)
- {
- $staticDirLength = strlenu($this->config->get("staticDir"));
- $systemDirLength = strlenu($this->config->get("systemDir"));
- return substru($fileName, 0, $staticDirLength) == $this->config->get("staticDir") ||
- substru($fileName, 0, $systemDirLength) == $this->config->get("systemDir");
- }
-
- // Check if request can be redirected into content directory
- function isRequestContentDirectory($location)
- {
- $ok = false;
- if($this->lookup->isFileLocation($location))
- {
- $path = $this->lookup->findFileFromLocation("$location/", true);
- $ok = is_dir($path);
- } else if($location=="/") {
- $ok = $this->config->get("multiLanguageMode");
- }
- return $ok;
- }
-
- // Execute command
- function command($name, $args = NULL)
- {
- $statusCode = 0;
- if($this->plugins->isExisting($name))
- {
- $plugin = $this->plugins->plugins[$name];
- if(method_exists($plugin["obj"], "onCommand")) $statusCode = $plugin["obj"]->onCommand(func_get_args());
- } else {
- $statusCode = 500;
- $this->page->error($statusCode, "Plugin '$name' does not exist!");
- }
- return $statusCode;
- }
-
- // Execute snippet
- function snippet($name, $args = NULL)
- {
- $this->pages->snippetArgs = func_get_args();
- $this->page->parseSnippet($name);
- }
-}
-
-// Yellow page
-class YellowPage
-{
- var $yellow; //access to API
- var $serverScheme; //server scheme
- var $serverName; //server name
- var $base; //base location
- var $location; //page location
- var $fileName; //content file name
- var $lastModified; //last modification date
- var $rawData; //raw data of page
- var $metaDataOffsetBytes; //meta data offset
- var $metaData; //meta data
- var $headerData; //response header
- var $outputData; //response output
- var $pages; //page collection
- var $pageRelations; //page relations
- var $parser; //content parser
- var $parserData; //content data of page
- var $parserSafeMode; //page is parsed in safe mode? (boolean)
- var $available; //page is available? (boolean)
- var $visible; //page is visible location? (boolean)
- var $active; //page is active location? (boolean)
- var $cacheable; //page is cacheable? (boolean)
- var $statusCode; //status code
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->metaData = array();
- $this->headerData = array();
- $this->pages = new YellowPageCollection($yellow);
- $this->pageRelations = array();
- }
-
- // Set request information
- function setRequestInformation($serverScheme, $serverName, $base, $location, $fileName)
- {
- $this->serverScheme = $serverScheme;
- $this->serverName = $serverName;
- $this->base = $base;
- $this->location = $location;
- $this->fileName = $fileName;
- }
-
- // Parse page data
- function parseData($rawData, $cacheable, $statusCode, $pageError = "")
- {
- $this->lastModified = 0;
- $this->rawData = $rawData;
- $this->parser = NULL;
- $this->parserData = "";
- $this->parserSafeMode = intval($this->yellow->config->get("parserSafeMode"));
- $this->available = true;
- $this->visible = $this->yellow->lookup->isVisibleLocation($this->location, $this->fileName);
- $this->active = $this->yellow->lookup->isActiveLocation($this->location, $this->yellow->page->location);
- $this->cacheable = $cacheable;
- $this->statusCode = $statusCode;
- $this->parseMeta($pageError);
- }
-
- // Parse page data update
- function parseDataUpdate()
- {
- if($this->statusCode == 0)
- {
- $fileHandle = @fopen($this->fileName, "r");
- if($fileHandle)
- {
- $this->statusCode = 200;
- $this->rawData = fread($fileHandle, filesize($this->fileName));
- fclose($fileHandle);
- $this->parseMeta();
- }
- }
- }
-
- // Parse page meta data
- function parseMeta($pageError = "")
- {
- $this->metaData = array();
- if(!is_null($this->rawData))
- {
- $this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
- $this->set("sitename", $this->yellow->config->get("sitename"));
- $this->set("author", $this->yellow->config->get("author"));
- $this->set("language", $this->yellow->lookup->findLanguageFromFile($this->fileName,
- $this->yellow->config->get("language")));
- $this->set("theme", $this->yellow->lookup->findNameFromFile($this->fileName,
- $this->yellow->config->get("themeDir"), $this->yellow->config->get("theme"), ".css"));
- $this->set("template", $this->yellow->lookup->findNameFromFile($this->fileName,
- $this->yellow->config->get("templateDir"), $this->yellow->config->get("template"), ".html"));
- $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
- $this->set("navigation", $this->yellow->config->get("navigation"));
- $this->set("sidebar", $this->yellow->config->get("sidebar"));
- $this->set("parser", $this->yellow->config->get("parser"));
-
- if(preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)[\r\n]+\-\-\-[\r\n]+/s", $this->rawData, $parts))
- {
- $this->metaDataOffsetBytes = strlenb($parts[0]);
- foreach(preg_split("/[\r\n]+/", $parts[2]) as $line)
- {
- preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
- if(!empty($matches[1]) && !strempty($matches[2])) $this->set(lcfirst($matches[1]), $matches[2]);
- }
- } else if(preg_match("/^(\xEF\xBB\xBF)?([^\r\n]+)[\r\n]+=+[\r\n]+/", $this->rawData, $parts)) {
- $this->metaDataOffsetBytes = strlenb($parts[0]);
- $this->set("title", $parts[2]);
- }
-
- $titleHeader = ($this->location == $this->yellow->pages->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("titleHeader")) $this->set("titleHeader", $titleHeader);
- if(!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
- if($this->get("titleContent") == "-") $this->set("titleContent", "");
- $this->set("pageRead", $this->yellow->lookup->normaliseUrl(
- $this->yellow->config->get("serverScheme"),
- $this->yellow->config->get("serverName"),
- $this->yellow->config->get("serverBase"), $this->location));
- $this->set("pageEdit", $this->yellow->lookup->normaliseUrl(
- $this->yellow->config->get("webinterfaceServerScheme"),
- $this->yellow->config->get("webinterfaceServerName"),
- $this->yellow->config->get("serverBase"),
- rtrim($this->yellow->config->get("webinterfaceLocation"), '/').$this->location));
- $this->set("pageFile", $this->yellow->lookup->normaliseFile($this->fileName));
- if($this->get("status") == "hidden") $this->available = false;
- } else {
- $this->set("type", $this->yellow->toolbox->getFileExtension($this->fileName));
- $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
- $this->set("pageFile", $this->yellow->lookup->normaliseFile($this->fileName, true));
- }
- if(!empty($pageError)) $this->set("pageError", $pageError);
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onParseMeta")) $value["obj"]->onParseMeta($this);
- }
- }
-
- // Parse page content on demand
- function parseContent()
- {
- if(!is_object($this->parser))
- {
- if($this->yellow->plugins->isExisting($this->get("parser")))
- {
- $plugin = $this->yellow->plugins->plugins[$this->get("parser")];
- if(method_exists($plugin["obj"], "onParseContentRaw"))
- {
- $this->parser = $plugin["obj"];
- $this->parserData = $this->parser->onParseContentRaw($this, $this->getContent(true));
- foreach($this->yellow->plugins->plugins 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);
- $this->parserData = preg_replace("/@pageError/i", $this->get("pageError"), $this->parserData);
- }
- if(!$this->isExisting("description"))
- {
- $this->set("description", $this->yellow->toolbox->createTextDescription($this->parserData, 150));
- }
- if(!$this->isExisting("keywords"))
- {
- $this->set("keywords", $this->yellow->toolbox->createTextKeywords($this->get("title"), 10));
- }
- if(defined("DEBUG") && DEBUG>=3) echo "YellowPage::parseContent location:".$this->location."<br/>\n";
- }
- }
-
- // Parse page content block
- function parseContentBlock($name, $text, $shortcut)
- {
- $output = NULL;
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onParseContentBlock"))
- {
- $output = $value["obj"]->onParseContentBlock($this, $name, $text, $shortcut);
- if(!is_null($output)) break;
- }
- }
- if(defined("DEBUG") && DEBUG>=3 && !empty($name)) echo "YellowPage::parseContentBlock name:$name shortcut:$shortcut<br/>\n";
- return $output;
- }
-
- // Parse page
- function parsePage()
- {
- $this->outputData = NULL;
- if(!$this->isError())
- {
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onParsePage")) $value["obj"]->onParsePage();
- }
- }
- if(is_null($this->outputData))
- {
- ob_start();
- $this->parseTemplate($this->get("template"));
- $this->outputData = ob_get_contents();
- ob_end_clean();
- }
- if(!$this->isCacheable()) $this->setHeader("Cache-Control", "no-cache, must-revalidate");
- 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));
- if(!$this->yellow->text->isLanguage($this->get("language")))
- {
- $this->error(500, "Language '".$this->get("language")."' does not exist!");
- }
- if(!is_file($this->yellow->config->get("themeDir").$this->get("theme").".css"))
- {
- $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->getRequestHandler()=="core" && $this->isExisting("redirect") && $this->statusCode==200)
- {
- $location = $this->yellow->lookup->normaliseLocation($this->get("redirect"), $this->base, $this->location);
- $location = $this->yellow->lookup->normaliseUrl($this->serverScheme, $this->serverName, "", $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;
- }
-
- // Parse template
- function parseTemplate($name)
- {
- $fileNameTemplate = $this->yellow->config->get("templateDir").$this->yellow->lookup->normaliseName($name).".html";
- if(is_file($fileNameTemplate))
- {
- $this->setLastModified(filemtime($fileNameTemplate));
- global $yellow;
- require($fileNameTemplate);
- } else {
- $this->error(500, "Template '$name' does not exist!");
- echo "Template error<br/>\n";
- }
- }
-
- // Parse snippet
- function parseSnippet($name)
- {
- $fileNameSnippet = $this->yellow->config->get("snippetDir").$this->yellow->lookup->normaliseName($name).".php";
- if(is_file($fileNameSnippet))
- {
- $this->setLastModified(filemtime($fileNameSnippet));
- global $yellow;
- require($fileNameSnippet);
- } else {
- $this->error(500, "Snippet '$name' does not exist!");
- echo "Snippet error<br/>\n";
- }
- }
-
- // Set page meta data
- function set($key, $value)
- {
- $this->metaData[$key] = $value;
- }
-
- // Return page meta data
- function get($key)
- {
- return $this->isExisting($key) ? $this->metaData[$key] : "";
- }
-
- // Return page meta data, HTML encoded
- function getHtml($key)
- {
- return htmlspecialchars($this->get($key));
- }
-
- // Return page meta data as language specific date
- function getDate($key, $dateFormat = "")
- {
- if(!empty($dateFormat))
- {
- $format = $this->yellow->text->get($dateFormat);
- } else {
- $format = $this->yellow->text->get("dateFormatMedium");
- }
- return $this->yellow->text->getDateFormatted(strtotime($this->get($key)), $format);
- }
-
- // Return page content, HTML encoded or raw format
- function getContent($rawFormat = false)
- {
- if($rawFormat)
- {
- $this->parseDataUpdate();
- $text = substrb($this->rawData, $this->metaDataOffsetBytes);
- } else {
- $this->parseContent();
- $text = $this->parserData;
- }
- return $text;
- }
-
- // Return parent page relative to current page, NULL if none
- function getParent()
- {
- $parentLocation = $this->yellow->pages->getParentLocation($this->location);
- return $this->yellow->pages->find($parentLocation);
- }
-
- // Return top-level page for current page, NULL if none
- function getParentTop($homeFailback = true)
- {
- $parentTopLocation = $this->yellow->pages->getParentTopLocation($this->location);
- if(!$this->yellow->pages->find($parentTopLocation) && $homeFailback)
- {
- $parentTopLocation = $this->yellow->pages->getHomeLocation($this->location);
- }
- return $this->yellow->pages->find($parentTopLocation);
- }
-
- // Return page collection with pages on the same level as current page
- function getSiblings($showInvisible = false)
- {
- $parentLocation = $this->yellow->pages->getParentLocation($this->location);
- return $this->yellow->pages->getChildren($parentLocation, $showInvisible);
- }
-
- // Return page collection with child pages relative to current page
- function getChildren($showInvisible = false)
- {
- return $this->yellow->pages->getChildren($this->location, $showInvisible);
- }
-
- // Return page collection with media files for current page
- function getFiles($showInvisible = false)
- {
- return $this->yellow->files->index($showInvisible, true)->filter("pageFile", $this->get("pageFile"));
- }
-
- // Set page collection with additional pages for current page
- function setPages($pages)
- {
- $this->pages = $pages;
- }
-
- // Return page collection with additional pages for current page
- function getPages()
- {
- return $this->pages;
- }
-
- // Set related page
- function setPage($key, $page)
- {
- $this->pageRelations[$key] = $page;
- }
-
- // Return related page
- function getPage($key)
- {
- return !is_null($this->pageRelations[$key]) ? $this->pageRelations[$key] : $this;
- }
-
- // Return absolute page location
- function getLocation()
- {
- return $this->base.$this->location;
- }
-
- // Return page URL with server scheme and server name
- function getUrl()
- {
- return $this->yellow->lookup->normaliseUrl($this->serverScheme, $this->serverName, $this->base, $this->location);
- }
-
- // Return page extra HTML data
- function getExtra($name)
- {
- $output = "";
- if($name == "header")
- {
- if(is_file($this->yellow->config->get("themeDir").$this->get("theme").".css"))
- {
- $location = $this->yellow->config->get("serverBase").
- $this->yellow->config->get("themeLocation").$this->get("theme").".css";
- $output .= "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".htmlspecialchars($location)."\" />\n";
- }
- if(is_file($this->yellow->config->get("imageDir").$this->yellow->config->get("iconFile")))
- {
- $location = $this->yellow->config->get("serverBase").
- $this->yellow->config->get("imageLocation").$this->yellow->config->get("iconFile");
- $contentType = $this->yellow->toolbox->getMimeContentType($this->yellow->config->get("iconFile"));
- $output .= "<link rel=\"shortcut icon\" type=\"$contentType\" href=\"".htmlspecialchars($location)."\" />\n";
- }
- }
- foreach($this->yellow->plugins->plugins as $key=>$value)
- {
- if(method_exists($value["obj"], "onExtra"))
- {
- $outputPlugin = $value["obj"]->onExtra($name);
- if(!is_null($outputPlugin)) $output .= $outputPlugin;
- }
- }
- return $this->normaliseExtra($output);
- }
-
- // Normalise page extra HTML data
- function normaliseExtra($text)
- {
- $outputScript = $outputStylesheet = $outputOther = $locations = array();
- foreach($this->yellow->toolbox->getTextLines($text) as $line)
- {
- if(preg_match("/^<script (.*?)src=\"([^\"]+)\"(.*?)><\/script>$/i", $line, $matches))
- {
- if(is_null($locations[$matches[2]]))
- {
- $locations[$matches[2]] = $matches[2];
- array_push($outputScript, $line);
- }
- } else if(preg_match("/^<link rel=\"stylesheet\"(.*?)href=\"([^\"]+)\"(.*?)>$/i", $line, $matches)) {
- if(is_null($locations[$matches[2]]))
- {
- $locations[$matches[2]] = $matches[2];
- array_push($outputStylesheet, $line);
- }
- } else {
- array_push($outputOther, $line);
- }
- }
- return implode($outputScript).implode($outputStylesheet).implode($outputOther);
- }
-
- // Set page response output
- function setOutput($output)
- {
- $this->outputData = $output;
- }
-
- // Set page response header
- function setHeader($key, $value)
- {
- $this->headerData[$key] = $value;
- }
-
- // Return page response header
- function getHeader($key)
- {
- return $this->isHeader($key) ? $this->headerData[$key] : "";
- }
-
- // Return page content modification date, Unix time or HTTP format
- function getModified($httpFormat = false)
- {
- $modified = strtotime($this->get("modified"));
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
- }
-
- // Set last modification date, Unix time
- function setLastModified($modified)
- {
- $this->lastModified = max($this->lastModified, $modified);
- }
-
- // Return last modification date, Unix time or HTTP format
- function getLastModified($httpFormat = false)
- {
- $modified = max($this->lastModified, $this->getModified(), $this->yellow->config->getModified(),
- $this->yellow->text->getModified(), $this->yellow->plugins->getModified());
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
- }
-
- // Return page status code, number or HTTP format
- function getStatusCode($httpFormat = false)
- {
- $statusCode = $this->statusCode;
- if($httpFormat)
- {
- $statusCode = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
- if($this->isExisting("pageError")) $statusCode .= ": ".$this->get("pageError");
- }
- return $statusCode;
- }
-
- // Respond with error page
- function error($statusCode, $pageError = "")
- {
- if(!$this->isExisting("pageError") && $statusCode>0)
- {
- $this->statusCode = $statusCode;
- $this->set("pageError", empty($pageError) ? "Template/snippet error!" : $pageError);
- }
- }
-
- // Respond with status code, no page content
- 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, must-revalidate");
- }
- $this->set("pageClean", (string)$statusCode);
- }
- }
-
- // Check if page is available
- function isAvailable()
- {
- return $this->available;
- }
-
- // Check if page is visible
- function isVisible()
- {
- return $this->visible;
- }
-
- // Check if page is within current request
- function isActive()
- {
- return $this->active;
- }
-
- // Check if page is cacheable
- function isCacheable()
- {
- return $this->cacheable;
- }
-
- // Check if page with error
- function isError()
- {
- return $this->isExisting("pageError");
- }
-
- // Check if response header exists
- function isHeader($key)
- {
- return !is_null($this->headerData[$key]);
- }
-
- // Check if page meta data exists
- function isExisting($key)
- {
- return !is_null($this->metaData[$key]);
- }
-
- // Check if related page exists
- function isPage($key)
- {
- return !is_null($this->pageRelations[$key]);
- }
-}
-
-// Yellow page collection as array
-class YellowPageCollection extends ArrayObject
-{
- var $yellow; //access to API
- var $filterValue; //current page filter value
- var $paginationNumber; //current page number in pagination
- var $paginationCount; //highest page number in pagination
-
- function __construct($yellow)
- {
- parent::__construct(array());
- $this->yellow = $yellow;
- }
-
- // Filter page collection by meta data
- function filter($key, $value, $exactMatch = true)
- {
- if(!empty($key))
- {
- $array = array();
- $value = strreplaceu(' ', '-', strtoloweru($value));
- $valueLength = strlenu($value);
- foreach($this->getArrayCopy() as $page)
- {
- if($page->isExisting($key))
- {
- foreach(preg_split("/,\s*/", $page->get($key)) as $pageValue)
- {
- $pageValueLength = $exactMatch ? strlenu($pageValue) : $valueLength;
- if($value == substru(strreplaceu(' ', '-', strtoloweru($pageValue)), 0, $pageValueLength))
- {
- $this->filterValue = substru($pageValue, 0, $pageValueLength);
- array_push($array, $page);
- }
- }
- }
- }
- $this->exchangeArray($array);
- }
- return $this;
- }
-
- // Filter page collection by file name
- 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 meta data
- function sort($key, $ascendingOrder = true)
- {
- $callback = function($a, $b) use ($key, $ascendingOrder)
- {
- return $ascendingOrder ?
- strnatcasecmp($a->get($key), $b->get($key)) :
- strnatcasecmp($b->get($key), $a->get($key));
- };
- $array = $this->getArrayCopy();
- usort($array, $callback);
- $this->exchangeArray($array);
- return $this;
- }
-
- // Sort page collection by meta data similarity
- function similar($page, $ascendingOrder = false)
- {
- $location = $page->location;
- $keywords = $this->yellow->toolbox->createTextKeywords($page->get("title"));
- $keywords .= ",".$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("searchscore", $ascendingOrder);
- }
- return $this;
- }
-
- // Merge page collection
- function merge($input)
- {
- $this->exchangeArray(array_merge($this->getArrayCopy(), (array)$input));
- return $this;
- }
-
- // Append to end of page collection
- function append($page)
- {
- parent::append($page);
- return $this;
- }
-
- // Prepend to start of page collection
- function prepend($page)
- {
- $array = $this->getArrayCopy();
- array_unshift($array, $page);
- $this->exchangeArray($array);
- return $this;
- }
-
- // Limit the number of pages in page collection
- function limit($pagesMax)
- {
- $this->exchangeArray(array_slice($this->getArrayCopy(), 0, $pagesMax));
- return $this;
- }
-
- // Reverse page collection
- function reverse()
- {
- $this->exchangeArray(array_reverse($this->getArrayCopy()));
- return $this;
- }
-
- // Randomize page collection
- function shuffle()
- {
- $array = $this->getArrayCopy();
- shuffle($array);
- $this->exchangeArray($array);
- return $this;
- }
-
- // Paginate page collection
- function pagination($limit, $reverse = true)
- {
- $this->paginationNumber = 1;
- $this->paginationCount = ceil($this->count() / $limit);
- $pagination = $this->yellow->config->get("contentPagination");
- if(isset($_REQUEST[$pagination])) $this->paginationNumber = intval($_REQUEST[$pagination]);
- 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 $this;
- }
-
- // Return current page number in pagination
- function getPaginationNumber()
- {
- return $this->paginationNumber;
- }
-
- // Return highest page number in pagination
- function getPaginationCount()
- {
- return $this->paginationCount;
- }
-
- // Return absolute location for a page in pagination
- function getPaginationLocation($pageNumber)
- {
- if($pageNumber>=1 && $pageNumber<=$this->paginationCount)
- {
- $pagination = $this->yellow->config->get("contentPagination");
- $location = $this->yellow->page->getLocation();
- $locationArgs = $this->yellow->toolbox->getLocationArgsNew(
- $pageNumber>1 ? "$pagination:$pageNumber" : "$pagination:", $pagination);
- }
- return $location.$locationArgs;
- }
-
- // Return absolute location for previous page in pagination
- function getPaginationPrevious()
- {
- $pageNumber = $this->paginationNumber;
- $pageNumber = ($pageNumber>1 && $pageNumber<=$this->paginationCount) ? $pageNumber-1 : 0;
- return $this->getPaginationLocation($pageNumber);
- }
-
- // Return absolute location for next page in pagination
- function getPaginationNext()
- {
- $pageNumber = $this->paginationNumber;
- $pageNumber = ($pageNumber>=1 && $pageNumber<$this->paginationCount) ? $pageNumber+1 : 0;
- return $this->getPaginationLocation($pageNumber);
- }
-
- // Return current page filter
- function getFilter()
- {
- return $this->filterValue;
- }
-
- // Return page collection modification date, Unix time or HTTP format
- 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 there is a pagination
- function isPagination()
- {
- return $this->paginationCount > 1;
- }
-}
-
-// Yellow pages
-class YellowPages
-{
- var $yellow; //access to API
- var $pages; //scanned pages
- var $requestHandler; //request handler name
- var $snippetArgs; //requested snippet arguments
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->pages = array();
- }
-
- // Scan file system on demand
- function scanLocation($location)
- {
- if(is_null($this->pages[$location]))
- {
- if(defined("DEBUG") && DEBUG>=2) echo "YellowPages::scanLocation location:$location<br/>\n";
- $this->pages[$location] = array();
- $serverScheme = $this->yellow->page->serverScheme;
- $serverName = $this->yellow->page->serverName;
- $base = $this->yellow->page->base;
- if(empty($location))
- {
- $rootLocations = $this->yellow->lookup->findRootLocations();
- foreach($rootLocations as $rootLocation)
- {
- list($rootLocation, $fileName) = explode(' ', $rootLocation, 2);
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($serverScheme, $serverName, $base, $rootLocation, $fileName);
- $page->parseData("", false, 0);
- array_push($this->pages[$location], $page);
- }
- } else {
- $fileNames = $this->yellow->lookup->findChildrenFromLocation($location);
- foreach($fileNames as $fileName)
- {
- $fileHandle = @fopen($fileName, "r");
- if($fileHandle)
- {
- $fileData = fread($fileHandle, 4096);
- $statusCode = filesize($fileName) <= 4096 ? 200 : 0;
- fclose($fileHandle);
- } else {
- $fileData = "";
- $statusCode = 0;
- }
- $page = new YellowPage($this->yellow);
- $page->setRequestInformation($serverScheme, $serverName, $base,
- $this->yellow->lookup->findLocationFromFile($fileName), $fileName);
- $page->parseData($fileData, false, $statusCode);
- array_push($this->pages[$location], $page);
- }
- }
- }
- return $this->pages[$location];
- }
-
- // Return page from file system, NULL if not found
- function find($location, $absoluteLocation = 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;
- }
-
- // Return page collection with all pages
- function index($showInvisible = false, $multiLanguage = false, $levelMax = 0)
- {
- $rootLocation = $multiLanguage ? "" : $this->getRootLocation($this->yellow->page->location);
- return $this->getChildrenRecursive($rootLocation, $showInvisible, $levelMax);
- }
-
- // Return page collection with top-level navigation
- function top($showInvisible = false)
- {
- $rootLocation = $this->getRootLocation($this->yellow->page->location);
- return $this->getChildren($rootLocation, $showInvisible);
- }
-
- // Return page collection with path ancestry
- 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);
- }
- return $pages;
- }
-
- // Return page collection with multiple languages
- 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);
- }
- }
- }
- return $pages;
- }
-
- // Return page collection that's empty
- function clean()
- {
- return new YellowPageCollection($this->yellow);
- }
-
- // Return available languages
- function getLanguages($showInvisible = false)
- {
- $languages = array();
- foreach($this->scanLocation("") as $page)
- {
- if($page->isAvailable() && ($page->isVisible() || $showInvisible))
- {
- array_push($languages, $page->get("language"));
- }
- }
- return $languages;
- }
-
- // Return child pages
- 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)) $pages->append($page);
- }
- }
- return $pages;
- }
-
- // Return child pages recursively
- 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)) $pages->append($page);
- if(!$this->yellow->lookup->isFileLocation($page->location) && $levelMax!=0)
- {
- $pages->merge($this->getChildrenRecursive($page->location, $showInvisible, $levelMax));
- }
- }
- }
- return $pages;
- }
-
- // Return root location
- function getRootLocation($location)
- {
- $rootLocation = "root/";
- if($this->yellow->config->get("multiLanguageMode"))
- {
- 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 home location
- function getHomeLocation($location)
- {
- return substru($this->getRootLocation($location), 4);
- }
-
- // Return parent location
- 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 top-level location
- 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;
- }
-}
-
-// Yellow files
-class YellowFiles
-{
- var $yellow; //access to API
- var $files; //scanned files
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->files = array();
- }
-
- // Scan file system on demand
- function scanLocation($location)
- {
- if(is_null($this->files[$location]))
- {
- if(defined("DEBUG") && DEBUG>=2) echo "YellowFiles::scanLocation location:$location<br/>\n";
- $this->files[$location] = array();
- $serverScheme = $this->yellow->page->serverScheme;
- $serverName = $this->yellow->page->serverName;
- $base = $this->yellow->config->get("serverBase");
- if(empty($location))
- {
- $fileNames = array($this->yellow->config->get("mediaDir"));
- } 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);
- }
- }
- foreach($fileNames as $fileName)
- {
- $file = new YellowPage($this->yellow);
- $file->setRequestInformation($serverScheme, $serverName, $base, "/".$fileName, $fileName);
- $file->parseData(NULL, false, 0);
- array_push($this->files[$location], $file);
- }
- }
- return $this->files[$location];
- }
-
- // Return page with media file information, NULL if not found
- function find($location, $absoluteLocation = false)
- {
- if($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
- foreach($this->scanLocation($this->getParentLocation($location)) as $file)
- {
- if($file->location == $location) { $found = true; break; }
- }
- return $found ? $file : NULL;
- }
-
- // Return page collection with all media files
- function index($showInvisible = false, $multiPass = false, $levelMax = 0)
- {
- return $this->getChildrenRecursive("", $showInvisible, $levelMax);
- }
-
- // Return page collection that's empty
- function clean()
- {
- return new YellowPageCollection($this->yellow);
- }
-
- // Return child files
- function getChildren($location, $showInvisible = false)
- {
- $files = new YellowPageCollection($this->yellow);
- foreach($this->scanLocation($location) as $file)
- {
- if($file->isAvailable() && ($file->isVisible() || $showInvisible))
- {
- $files->append($file);
- }
- }
- return $files;
- }
-
- // Return child files recursively
- 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))
- {
- $files->append($file);
- if(!$this->yellow->lookup->isFileLocation($file->location) && $levelMax!=0)
- {
- $files->merge($this->getChildrenRecursive($file->location, $showInvisible, $levelMax));
- }
- }
- }
- return $files;
- }
-
- // Return home location
- function getHomeLocation($location)
- {
- return "/".$this->yellow->config->get("mediaDir");
- }
-
- // Return parent location
- function getParentLocation($location)
- {
- $token = rtrim("/".$this->yellow->config->get("mediaDir"), '/');
- if(preg_match("#^($token.*\/).+?$#", $location, $matches))
- {
- if($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
- }
- if(empty($parentLocation)) $parentLocation = "";
- return $parentLocation;
- }
-
- // Return top-level location
- function getParentTopLocation($location)
- {
- $token = rtrim("/".$this->yellow->config->get("mediaDir"), '/');
- if(preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
- if(empty($parentTopLocation)) $parentTopLocation = "$token/";
- return $parentTopLocation;
- }
-}
-
-// Yellow plugins
-class YellowPlugins
-{
- var $yellow; //access to API
- var $plugins; //registered plugins
- var $modified; //plugin modification date
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->plugins = array();
- $this->modified = 0;
- }
-
- // Load plugins
- function load()
- {
- $path = $this->yellow->config->get("coreDir");
- foreach($this->yellow->toolbox->getDirectoryEntries($path, "/^core-.*\.php$/", true, false) as $entry)
- {
- $this->modified = max($this->modified, filemtime($entry));
- global $yellow;
- require_once($entry);
- }
- $path = $this->yellow->config->get("pluginDir");
- foreach($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.php$/", true, false) as $entry)
- {
- $this->modified = max($this->modified, filemtime($entry));
- global $yellow;
- require_once($entry);
- }
- foreach($this->plugins as $key=>$value)
- {
- $this->plugins[$key]["obj"] = new $value["class"];
- if(defined("DEBUG") && DEBUG>=3) echo "YellowPlugins::load class:$value[class] $value[version]<br/>\n";
- if(method_exists($this->plugins[$key]["obj"], "onLoad")) $this->plugins[$key]["obj"]->onLoad($yellow);
- }
- }
-
- // Register plugin
- function register($name, $class, $version)
- {
- if(!$this->isExisting($name))
- {
- $this->plugins[$name] = array();
- $this->plugins[$name]["class"] = $class;
- $this->plugins[$name]["version"] = $version;
- }
- }
-
- // Return plugin
- function get($name)
- {
- return $this->plugins[$name]["obj"];
- }
-
- // Return plugins modification date, Unix time or HTTP format
- function getModified($httpFormat = false)
- {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
- }
-
- // Check if plugin exists
- function isExisting($name)
- {
- return !is_null($this->plugins[$name]);
- }
-}
-
-// Yellow configuration
-class YellowConfig
-{
- var $yellow; //access to API
- var $modified; //configuration modification date
- var $config; //configuration
- var $configDefaults; //configuration defaults
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->modified = 0;
- $this->config = array();
- $this->configDefaults = array();
- }
-
- // Load configuration from file
- function load($fileName)
- {
- $fileData = @file($fileName);
- if($fileData)
- {
- if(defined("DEBUG") && DEBUG>=2) echo "YellowConfig::load file:$fileName<br/>\n";
- $this->modified = filemtime($fileName);
- foreach($fileData as $line)
- {
- if(preg_match("/^\//", $line)) continue;
- 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 "YellowConfig::load key:$matches[1] $matches[2]<br/>\n";
- }
- }
- }
- }
-
- // Set default configuration
- function setDefault($key, $value)
- {
- $this->configDefaults[$key] = $value;
- }
-
- // Set configuration
- function set($key, $value)
- {
- $this->config[$key] = $value;
- }
-
- // Return configuration
- function get($key)
- {
- if(!is_null($this->config[$key]))
- {
- $value = $this->config[$key];
- } else {
- $value = !is_null($this->configDefaults[$key]) ? $this->configDefaults[$key] : "";
- }
- return $value;
- }
-
- // Return configuration, HTML encoded
- function getHtml($key)
- {
- return htmlspecialchars($this->get($key));
- }
-
- // Return configuration strings
- function getData($filterStart = "", $filterEnd = "")
- {
- $config = array();
- if(empty($filterStart) && empty($filterEnd))
- {
- $config = array_merge($this->configDefaults, $this->config);
- } else {
- foreach(array_merge($this->configDefaults, $this->config) as $key=>$value)
- {
- if(!empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $config[$key] = $value;
- if(!empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $config[$key] = $value;
- }
- }
- return $config;
- }
-
- // Return configuration modification date, Unix time or HTTP format
- function getModified($httpFormat = false)
- {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
- }
-
- // Check if configuration exists
- function isExisting($key)
- {
- return !is_null($this->config[$key]);
- }
-}
-
-// Yellow text strings
-class YellowText
-{
- var $yellow; //access to API
- var $modified; //text modification date
- var $text; //text strings
- var $language; //current language
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- $this->modified = 0;
- $this->text = array();
- }
-
- // Load text strings from file
- function load($fileName)
- {
- $path = dirname($fileName);
- $regex = "/^".basename($fileName)."$/";
- foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false) as $entry)
- {
- $fileData = @file($entry);
- if($fileData)
- {
- if(defined("DEBUG") && DEBUG>=2) echo "YellowText::load file:$entry<br/>\n";
- $this->modified = max($this->modified, filemtime($entry));
- $language = "";
- foreach($fileData as $line)
- {
- preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches);
- if($matches[1]=="language" && !strempty($matches[2])) { $language = $matches[2]; break; }
- }
- foreach($fileData as $line)
- {
- if(preg_match("/^\//", $line)) continue;
- preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches);
- if(!empty($language) && !empty($matches[1]) && !strempty($matches[2]))
- {
- $this->setText($matches[1], $matches[2], $language);
- if(defined("DEBUG") && DEBUG>=3) echo "YellowText::load key:$matches[1] $matches[2]<br/>\n";
- }
- }
- }
- }
- }
-
- // Set current language
- function setLanguage($language)
- {
- $this->language = $language;
- }
-
- // Set text string for specific language
- function setText($key, $value, $language)
- {
- if(is_null($this->text[$language])) $this->text[$language] = array();
- $this->text[$language][$key] = $value;
- }
-
- // Return text string for specific language
- function getText($key, $language)
- {
- return $this->isExisting($key, $language) ? $this->text[$language][$key] : "[$key]";
- }
-
- // Return text string for specific language, HTML encoded
- function getTextHtml($key, $language)
- {
- return htmlspecialchars($this->getText($key, $language));
- }
-
- // Return text string
- function get($key)
- {
- return $this->getText($key, $this->language);
- }
-
- // Return text string, HTML encoded
- function getHtml($key)
- {
- return htmlspecialchars($this->getText($key, $this->language));
- }
-
- // Return text strings
- 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("language")) == "language") $text[$key] = $value;
- if(substru($key, 0, strlenu($filterStart)) == $filterStart) $text[$key] = $value;
- }
- }
- }
- return $text;
- }
-
- // Return text string with human readable date, custom date format
- function getDateFormatted($timestamp, $format)
- {
- $dateMonths = preg_split("/,\s*/", $this->get("dateMonths"));
- $dateWeekdays = preg_split("/,\s*/", $this->get("dateWeekdays"));
- $month = $dateMonths[date('n', $timestamp) - 1];
- $weekday = $dateWeekdays[date('N', $timestamp) - 1];
- $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);
- return date($format, $timestamp);
- }
-
- // Return text modification date, Unix time or HTTP format
- function getModified($httpFormat = false)
- {
- return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
- }
-
- // Normalise date into known format
- function normaliseDate($text)
- {
- if(preg_match("/^\d+\-\d+$/", $text))
- {
- $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatShort"));
- } else if(preg_match("/^\d+\-\d+\-\d+$/", $text)) {
- $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatMedium"));
- } else if(preg_match("/^\d+\-\d+\-\d+ \d+\:\d+$/", $text)) {
- $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatLong"));
- } else {
- $output = $text;
- }
- return $output;
- }
-
- // Check if language exists
- function isLanguage($language)
- {
- return !is_null($this->text[$language]);
- }
-
- // Check if text string exists
- function isExisting($key, $language = "")
- {
- if(empty($language)) $language = $this->language;
- return !is_null($this->text[$language]) && !is_null($this->text[$language][$key]);
- }
-}
-
-// Yellow location and file lookup
-class YellowLookup
-{
- var $yellow; //access to API
-
- function __construct($yellow)
- {
- $this->yellow = $yellow;
- }
-
- // Return root locations
- function findRootLocations($includePath = true)
- {
- $locations = array();
- $pathBase = $this->yellow->config->get("contentDir");
- $pathRoot = $this->yellow->config->get("contentRootDir");
- if(!empty($pathRoot))
- {
- foreach($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry)
- {
- $token = $this->normaliseName($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";
- }
- } else {
- array_push($locations, $includePath ? "root/ $pathBase" : "root/");
- }
- return $locations;
- }
-
- // Return location from file path
- function findLocationFromFile($fileName)
- {
- $location = "/";
- $pathBase = $this->yellow->config->get("contentDir");
- $pathRoot = $this->yellow->config->get("contentRootDir");
- $pathHome = $this->yellow->config->get("contentHomeDir");
- $fileDefault = $this->yellow->config->get("contentDefaultFile");
- $fileExtension = $this->yellow->config->get("contentExtension");
- if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
- {
- $fileName = substru($fileName, strlenu($pathBase));
- $tokens = explode('/', $fileName);
- if(!empty($pathRoot))
- {
- $token = $this->normaliseName($tokens[0]).'/';
- if($token!=$pathRoot) $location .= $token;
- array_shift($tokens);
- }
- for($i=0; $i<count($tokens)-1; ++$i)
- {
- $token = $this->normaliseName($tokens[$i]).'/';
- if($i || $token!=$pathHome) $location .= $token;
- }
- $token = $this->normaliseName($tokens[$i]);
- $fileFolder = $this->normaliseName($tokens[$i-1]).$fileExtension;
- if($token!=$fileDefault && $token!=$fileFolder) $location .= $this->normaliseName($tokens[$i], true, true);
- $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";
- }
- return $invalid ? "" : $location;
- }
-
- // Return file path from location
- function findFileFromLocation($location, $directory = false)
- {
- $path = $this->yellow->config->get("contentDir");
- $pathRoot = $this->yellow->config->get("contentRootDir");
- $pathHome = $this->yellow->config->get("contentHomeDir");
- $fileDefault = $this->yellow->config->get("contentDefaultFile");
- $fileExtension = $this->yellow->config->get("contentExtension");
- $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->normaliseName($tokens[1]) == $this->normaliseName($pathRoot)) $invalid = true;
- $path .= $this->findFileDirectory($path, $tokens[1], false, true, $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->normaliseName($tokens[1]) == $this->normaliseName($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)
- {
- $fileFolder = $tokens[$i-1].$fileExtension;
- if(!empty($tokens[$i]))
- {
- $token = $tokens[$i].$fileExtension;
- if($token==$fileDefault || $token==$fileFolder) $invalid = true;
- $path .= $this->findFileDirectory($path, $token, true, false, $found, $invalid);
- } else {
- $path .= $this->findFileDefault($path, $fileDefault, $fileFolder);
- }
- if(defined("DEBUG") && DEBUG>=2)
- {
- $debug = "$location -> ".($invalid ? "INVALID" : $path);
- echo "YellowLookup::findFileFromLocation $debug<br/>\n";
- }
- }
- }
- return $invalid ? "" : $path;
- }
-
- // Return file or directory that matches token
- function findFileDirectory($path, $token, $tokenFailback, $directory, &$found, &$invalid)
- {
- if($this->normaliseName($token) != $token) $invalid = true;
- if(!$invalid)
- {
- $regex = "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
- foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, $directory, false) as $entry)
- {
- if($this->normaliseName($entry) == $token) { $token = $entry; $found = true; break; }
- }
- }
- if($directory) $token .= '/';
- return ($tokenFailback || $found) ? $token : "";
- }
-
- // Return default file in directory
- function findFileDefault($path, $fileDefault, $fileFolder)
- {
- $token = $fileDefault;
- if(!is_file($path."/".$fileDefault))
- {
- $regex = "/^[\d\-\_\.]*($fileDefault|$fileFolder)$/";
- foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry)
- {
- if($this->normaliseName($entry) == $fileDefault) { $token = $entry; break; }
- if($this->normaliseName($entry) == $fileFolder) { $token = $entry; break; }
- }
- }
- return $token;
- }
-
- // Return children from location
- function findChildrenFromLocation($location)
- {
- $fileNames = array();
- $fileDefault = $this->yellow->config->get("contentDefaultFile");
- $fileExtension = $this->yellow->config->get("contentExtension");
- if(!$this->isFileLocation($location))
- {
- $path = $this->findFileFromLocation($location, true);
- foreach($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
- {
- $fileFolder = $this->normaliseName($entry).$fileExtension;
- $token = $this->findFileDefault($path.$entry, $fileDefault, $fileFolder);
- array_push($fileNames, $path.$entry."/".$token);
- }
- if(!$this->isRootLocation($location))
- {
- $fileFolder = $this->normaliseName(basename($path)).$fileExtension;
- $regex = "/^.*\\".$fileExtension."$/";
- foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry)
- {
- if($this->normaliseName($entry) == $fileDefault) continue;
- if($this->normaliseName($entry) == $fileFolder) continue;
- if($this->normaliseName($entry, true, true) == "") continue;
- array_push($fileNames, $path.$entry);
- }
- }
- }
- return $fileNames;
- }
-
- // Return language from file path
- function findLanguageFromFile($fileName, $languageDefault)
- {
- $language = $languageDefault;
- $pathBase = $this->yellow->config->get("contentDir");
- $pathRoot = $this->yellow->config->get("contentRootDir");
- if(!empty($pathRoot))
- {
- $fileName = substru($fileName, strlenu($pathBase));
- if(preg_match("/^(.+?)\//", $fileName, $matches)) $name = $this->normaliseName($matches[1]);
- if(strlenu($name) == 2) $language = $name;
- }
- return $language;
- }
-
- // Return theme/template name from file path
- function findNameFromFile($fileName, $pathBase, $nameDefault, $fileExtension)
- {
- $name = "";
- if(preg_match("/^.*\/(.+?)$/", dirname($fileName), $matches)) $name = $this->normaliseName($matches[1]);
- if(!is_file("$pathBase$name$fileExtension")) $name = $this->normaliseName($nameDefault);
- return $name;
- }
-
- // Return file path for new page
- function findFileNew($fileName, $fileNew, $pathBase, $nameDefault)
- {
- if(preg_match("/^.*\/(.+?)$/", dirname($fileName), $matches)) $name = $this->normaliseName($matches[1]);
- $fileName = strreplaceu("(.*)", $name, $pathBase.$fileNew);
- if(!is_file($fileName))
- {
- $name = $this->normaliseName($nameDefault);
- $fileName = strreplaceu("(.*)", $name, $pathBase.$fileNew);
- }
- return $fileName;
- }
-
- // Return file path from title
- function findFileFromTitle($titlePrefix, $titleText, $fileName, $fileDefault, $fileExtension)
- {
- preg_match("/^([\d\-\_\.]*)(.*)$/", $titlePrefix, $matches);
- if(preg_match("/\d$/", $matches[1])) $matches[1] .= '-';
- $titleText = $this->normaliseName($titleText, false, false, true);
- preg_match("/^([\d\-\_\.]*)(.*)$/", $matches[1].$titleText, $matches);
- $fileNamePrefix = $matches[1];
- $fileNameText = empty($matches[2]) ? $fileDefault : $matches[2].$fileExtension;
- return dirname($fileName)."/".$fileNamePrefix.$fileNameText;
- }
-
- // Normalise file/directory/other name
- function normaliseName($text, $removePrefix = true, $removeExtension = false, $filterStrict = false)
- {
- if($removeExtension) $text = ($pos = strrposu($text, '.')) ? substru($text, 0, $pos) : $text;
- if($removePrefix) if(preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches)) $text = $matches[1];
- if($filterStrict) $text = strreplaceu('.', '-', strtoloweru($text));
- return preg_replace("/[^\pL\d\-\_\.]/u", "-", rtrim($text, '/'));
- }
-
- // Normalise content/media file name
- function normaliseFile($fileName, $convertExtension = false)
- {
- $fileName = basename($fileName);
- if($convertExtension)
- {
- $fileName = ($pos = strposu($fileName, '.')) ? substru($fileName, 0, $pos) : $fileName;
- $fileName .= $this->yellow->config->get("contentExtension");
- }
- return $fileName;
- }
-
- // Normalise location, make absolute location
- function normaliseLocation($location, $pageBase, $pageLocation, $staticLocation = "", $filterStrict = true)
- {
- if(!preg_match("/^\w+:/", trim(html_entity_decode($location, ENT_QUOTES, "UTF-8"))))
- {
- if(empty($staticLocation) || !preg_match("#^$staticLocation#", $location))
- {
- if(preg_match("/^\#/", $location))
- {
- $location = $pageBase.$pageLocation.$location;
- } else if(!preg_match("/^\//", $location)) {
- $location = $this->getDirectoryLocation($pageBase.$pageLocation).$location;
- } else if(!preg_match("#^$pageBase#", $location)) {
- $location = $pageBase.$location;
- }
- }
- } else {
- if($filterStrict && !preg_match("/^(http|https|ftp|mailto):/", $location)) $location = "error-xss-filter";
- }
- return $location;
- }
-
- // Normalise URL, make absolute URL
- function normaliseUrl($serverScheme, $serverName, $base, $location)
- {
- if(!preg_match("/^\w+:/", $location))
- {
- $url = "$serverScheme://$serverName$base$location";
- } else {
- $url = $location;
- }
- return $url;
- }
-
- // Return content information
- function getContentInformation()
- {
- $path = $this->yellow->config->get("contentDir");
- $pathRoot = $this->yellow->config->get("contentRootDir");
- $pathHome = $this->yellow->config->get("contentHomeDir");
- if(!$this->yellow->config->get("multiLanguageMode")) $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->normaliseName($entry) == $root) { $token = $entry; break; }
- }
- $pathRoot = $this->normaliseName($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->normaliseName($entry) == $home) { $token = $entry; break; }
- }
- $pathHome = $this->normaliseName($token)."/";
- }
- return array($pathRoot, $pathHome);
- }
-
- // Return directory location
- function getDirectoryLocation($location)
- {
- return ($pos = strrposu($location, '/')) ? substru($location, 0, $pos+1) : "/";
- }
-
- // Check if location is specifying root
- function isRootLocation($location)
- {
- return $location[0] != "/";
- }
-
- // Check if location is specifying file or directory
- function isFileLocation($location)
- {
- return substru($location, -1, 1) != "/";
- }
-
- // Check if location is visible
- function isVisibleLocation($location, $fileName)
- {
- $visible = true;
- $pathBase = $this->yellow->config->get("contentDir");
- if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
- {
- $fileName = substru($fileName, strlenu($pathBase));
- $tokens = explode('/', $fileName);
- for($i=0; $i<count($tokens)-1; ++$i)
- {
- if(!preg_match("/^[\d\-\_\.]+(.*)$/", $tokens[$i])) { $visible = false; break; }
- }
- } else {
- $visible = false;
- }
- return $visible;
- }
-
- // Check if location is within current request
- function isActiveLocation($location, $currentLocation)
- {
- if($this->isFileLocation($location))
- {
- $active = $currentLocation==$location;
- } else {
- if($location == $this->yellow->pages->getHomeLocation($location))
- {
- $active = $this->getDirectoryLocation($currentLocation)==$location;
- } else {
- $active = substru($currentLocation, 0, strlenu($location))==$location;
- }
- }
- return $active;
- }
-
- // Check if location is valid
- function isValidLocation($location)
- {
- $string = "";
- $tokens = explode('/', $location);
- for($i=1; $i<count($tokens); ++$i) $string .= '/'.$this->normaliseName($tokens[$i]);
- return $location == $string;
- }
-}
-
-// Yellow toolbox with helpers
-class YellowToolbox
-{
- // Return server software from current HTTP request
- function getServerSoftware()
- {
- $serverSoftware = PHP_SAPI;
- if(preg_match("/^(\S+)/", $_SERVER["SERVER_SOFTWARE"], $matches)) $serverSoftware = $matches[1];
- return $serverSoftware." ".PHP_OS;
- }
-
- // Return server scheme from current HTTP request
- function getServerScheme()
- {
- $serverScheme = "";
- if(preg_match("/^HTTP\//", $_SERVER["SERVER_PROTOCOL"]))
- {
- $secure = isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"]!="off";
- $serverScheme = $secure ? "https" : "http";
- }
- return $serverScheme;
- }
-
- // Return server name from current HTTP request
- function getServerName()
- {
- return $_SERVER["SERVER_NAME"];
- }
-
- // Return server base from current HTTP request
- function getServerBase()
- {
- $serverBase = "";
- if(preg_match("/^(.*)\//", $_SERVER["SCRIPT_NAME"], $matches)) $serverBase = $matches[1];
- return $serverBase;
- }
-
- // Return location from current HTTP request
- function getLocation()
- {
- $uri = $_SERVER["REQUEST_URI"];
- return rawurldecode(($pos = strposu($uri, '?')) ? substru($uri, 0, $pos) : $uri);
- }
-
- // Return location from current HTTP request, remove unwanted path tokens
- function getLocationClean()
- {
- $string = $this->getLocation();
- $location = ($string[0]=='/') ? '' : '/';
- for($pos=0; $pos<strlenb($string); ++$pos)
- {
- if($string[$pos] == '/')
- {
- if($string[$pos+1] == '/') continue;
- if($string[$pos+1] == '.')
- {
- $posNew = $pos+1; while($string[$posNew] == '.') ++$posNew;
- if($string[$posNew]=='/' || $string[$posNew]=='')
- {
- $pos = $posNew-1;
- continue;
- }
- }
- }
- $location .= $string[$pos];
- }
- if(preg_match("/^(.*?\/)([^\/]+:.*)$/", $location, $matches))
- {
- $_SERVER["LOCATION"] = $location = $matches[1];
- $_SERVER["LOCATION_ARGS"] = $matches[2];
- foreach(explode('/', $matches[2]) as $token)
- {
- preg_match("/^(.*?):(.*)$/", $token, $matches);
- if(!empty($matches[1]) && !strempty($matches[2]))
- {
- $matches[1] = strreplaceu(array("\x1c", "\x1d"), array('/', ':'), $matches[1]);
- $matches[2] = strreplaceu(array("\x1c", "\x1d"), array('/', ':'), $matches[2]);
- $_REQUEST[$matches[1]] = $matches[2];
- }
- }
- }
- return $location;
- }
-
- // Return location arguments from current HTTP request
- function getLocationArgs()
- {
- return $_SERVER["LOCATION_ARGS"];
- }
-
- // Return location arguments from current HTTP request, modify an argument
- function getLocationArgsNew($arg, $pagination)
- {
- preg_match("/^(.*?):(.*)$/", $arg, $args);
- foreach(explode('/', $_SERVER["LOCATION_ARGS"]) as $token)
- {
- preg_match("/^(.*?):(.*)$/", $token, $matches);
- if($matches[1] == $args[1]) { $matches[2] = $args[2]; $found = true; }
- if(!empty($matches[1]) && !strempty($matches[2]))
- {
- if(!empty($locationArgs)) $locationArgs .= '/';
- $locationArgs .= "$matches[1]:$matches[2]";
- }
- }
- if(!$found && !empty($args[1]) && !strempty($args[2]))
- {
- if(!empty($locationArgs)) $locationArgs .= '/';
- $locationArgs .= "$args[1]:$args[2]";
- }
- if(!empty($locationArgs))
- {
- if(!$this->isLocationArgsPagination($locationArgs, $pagination)) $locationArgs .= '/';
- $locationArgs = $this->normaliseArgs($locationArgs, false, false);
- }
- return $locationArgs;
- }
-
- // Return location arguments from current HTTP request, convert form into clean URL
- function getLocationArgsCleanUrl($pagination)
- {
- foreach(array_merge($_GET, $_POST) as $key=>$value)
- {
- if(!empty($key) && !strempty($value))
- {
- if(!empty($locationArgs)) $locationArgs .= '/';
- $key = strreplaceu(array('/', ':'), array("\x1c", "\x1d"), $key);
- $value = strreplaceu(array('/', ':'), array("\x1c", "\x1d"), $value);
- $locationArgs .= "$key:$value";
- }
- }
- if(!empty($locationArgs))
- {
- if(!$this->isLocationArgsPagination($locationArgs, $pagination)) $locationArgs .= '/';
- $locationArgs = $this->normaliseArgs($locationArgs, false, false);
- }
- return $locationArgs;
- }
-
- // Check if location contains location arguments
- function isLocationArgs($location)
- {
- return preg_match("/[^\/]+:.*$/", $location);
- }
-
- // Check if location contains pagination arguments
- function isLocationArgsPagination($location, $pagination)
- {
- return preg_match("/^(.*\/)?$pagination:.*$/", $location);
- }
-
- // Check if clean URL is requested
- function isRequestCleanUrl($location)
- {
- return (isset($_GET["clean-url"]) || isset($_POST["clean-url"])) && substru($location, -1, 1)=="/";
- }
-
- // Check if unmodified since last HTTP request
- function isRequestNotModified($lastModifiedFormatted)
- {
- return isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) && $_SERVER["HTTP_IF_MODIFIED_SINCE"]==$lastModifiedFormatted;
- }
-
- // Normalise location arguments
- function normaliseArgs($text, $appendSlash = true, $filterStrict = true)
- {
- if($appendSlash) $text .= '/';
- if($filterStrict) $text = strreplaceu(' ', '-', strtoloweru($text));
- return strreplaceu(array('%3A','%2F'), array(':','/'), rawurlencode($text));
- }
-
- // Normalise text into UTF-8 NFC
- 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 $text;
- }
-
- // Return time zone
- function getTimeZone()
- {
- $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 human readable HTTP server status
- function getHttpStatusFormatted($statusCode)
- {
- $serverProtocol = $_SERVER["SERVER_PROTOCOL"];
- if(!preg_match("/^HTTP\//", $serverProtocol)) $serverProtocol = "HTTP/1.1";
- switch($statusCode)
- {
- case 0: $text = "$serverProtocol $statusCode No data"; break;
- case 200: $text = "$serverProtocol $statusCode OK"; break;
- case 301: $text = "$serverProtocol $statusCode Moved permanently"; break;
- case 302: $text = "$serverProtocol $statusCode Moved temporarily"; break;
- case 303: $text = "$serverProtocol $statusCode Reload please"; break;
- case 304: $text = "$serverProtocol $statusCode Not modified"; break;
- case 400: $text = "$serverProtocol $statusCode Bad request"; break;
- case 401: $text = "$serverProtocol $statusCode Unauthorised"; break;
- case 404: $text = "$serverProtocol $statusCode Not found"; break;
- case 424: $text = "$serverProtocol $statusCode Not existing"; break;
- case 500: $text = "$serverProtocol $statusCode Server error"; break;
- default: $text = "$serverProtocol $statusCode Unknown status";
- }
- return $text;
- }
-
- // Return human readable HTTP date
- function getHttpDateFormatted($timestamp)
- {
- return gmdate("D, d M Y H:i:s", $timestamp)." GMT";
- }
-
- // Return MIME content type
- function getMimeContentType($fileName)
- {
- $mimeTypes = array(
- "css" => "text/css",
- "ico" => "image/x-icon",
- "js" => "application/javascript",
- "jpg" => "image/jpeg",
- "png" => "image/png",
- "txt" => "text/plain",
- "woff" => "application/font-woff",
- "xml" => "text/xml; charset=utf-8");
- $contentType = "text/html; charset=utf-8";
- $extension = $this->getFileExtension($fileName);
- if(array_key_exists(strtoloweru($extension), $mimeTypes)) $contentType = $mimeTypes[$extension];
- return $contentType;
- }
-
- // Return files and directories
- function getDirectoryEntries($path, $regex = "/.*/", $sort = true, $directories = true, $includePath = true)
- {
- $entries = array();
- $dirHandle = @opendir($path);
- if($dirHandle)
- {
- $path = rtrim($path, '/');
- while(($entry = readdir($dirHandle)) !== 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);
- }
- }
- }
- if($sort) natsort($entries);
- closedir($dirHandle);
- }
- return $entries;
- }
-
- // Return files and directories recursively
- 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));
- }
- }
- return $entries;
- }
-
- // Delete directory
- function deleteDirectory($path, $recursive = false)
- {
- if($recursive)
- {
- $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
- $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST);
- foreach($files as $file)
- {
- if($file->isDir())
- {
- @rmdir($file->getRealPath());
- } else {
- @unlink($file->getRealPath());
- }
- }
- }
- return @rmdir($path);
- }
-
- // Return file data, empty string if not found
- function getFileData($fileName)
- {
- return is_readable($fileName) ? file_get_contents($fileName) : "";
- }
-
- // Return file extension
- function getFileExtension($fileName)
- {
- return strtoloweru(($pos = strrposu($fileName, '.')) ? substru($fileName, $pos+1) : "");
- }
-
- // Return file modification date, Unix time
- function getFileModified($fileName)
- {
- $modified = is_readable($fileName) ? filemtime($fileName) : 0;
- if($modified == 0)
- {
- $path = dirname($fileName);
- $modified = is_readable($path) ? filemtime($path) : 0;
- }
- return $modified;
- }
-
- // Create file
- 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, "w");
- if($fileHandle)
- {
- fwrite($fileHandle, $fileData);
- fclose($fileHandle);
- $ok = true;
- }
- return $ok;
- }
-
- // Copy file
- function copyFile($fileNameSource, $fileNameDest, $mkdir = false)
- {
- if($mkdir)
- {
- $path = dirname($fileNameDest);
- if(!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
- }
- return @copy($fileNameSource, $fileNameDest);
- }
-
- // Rename file
- function renameFile($fileNameSource, $fileNameDest, $mkdir = false)
- {
- if($mkdir)
- {
- $path = dirname($fileNameDest);
- if(!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
- }
- return @rename($fileNameSource, $fileNameDest);
- }
-
- // Set file modification date, Unix time
- function modifyFile($fileName, $modified)
- {
- return @touch($fileName, $modified);
- }
-
- // Delete file
- function deleteFile($fileName)
- {
- return @unlink($fileName);
- }
-
- // Return lines from text string
- function getTextLines($text)
- {
- $lines = array();
- $split = preg_split("/(\R)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
- for($i=0; $i<count($split)-1; $i+=2) array_push($lines, $split[$i].$split[$i+1]);
- if($split[$i] != '') array_push($lines, $split[$i]);
- return $lines;
- }
-
- // Return arguments from text string
- function getTextArgs($text, $optional = "-")
- {
- $text = preg_replace("/\s+/s", " ", trim($text));
- $tokens = str_getcsv($text, ' ', '"');
- foreach($tokens as $key=>$value) if($value == $optional) $tokens[$key] = "";
- return $tokens;
- }
-
- // Create description from text string
- function createTextDescription($text, $lengthMax, $removeHtml = true, $endMarker = "", $endMarkerText = "")
- {
- if(preg_match("/^<h1>.*?<\/h1>(.*)$/si", $text, $matches)) $text = $matches[1];
- if($removeHtml)
- {
- while(true)
- {
- $elementFound = preg_match("/<\s*?([\/!]?\w*)(.*?)\s*?\>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
- $element = $matches[0][0];
- $elementName = $matches[1][0];
- $elementText = $matches[2][0];
- $elementOffsetBytes = $elementFound ? $matches[0][1] : strlenb($text);
- $string = html_entity_decode(substrb($text, $offsetBytes, $elementOffsetBytes - $offsetBytes), ENT_QUOTES, "UTF-8");
- if(preg_match("/^(blockquote|br|div|h\d|hr|li|ol|p|pre|ul)/i", $elementName)) $string .= ' ';
- if(preg_match("/^\/(code|pre)/i", $elementName)) $string = preg_replace("/^(\d+\n){2,}$/", "", $string);
- $string = preg_replace("/\s+/s", " ", $string);
- if(substru($string, 0, 1)==" " && (empty($output) || substru($output, -1)==' ')) $string = substru($string, 1);
- $length = strlenu($string);
- $output .= substru($string, 0, $length < $lengthMax ? $length : $lengthMax-1);
- $lengthMax -= $length;
- if(!empty($element) && $element==$endMarker) { $lengthMax = 0; $endMarkerFound = true; }
- if($lengthMax<=0 || !$elementFound) break;
- $offsetBytes = $elementOffsetBytes + strlenb($element);
- }
- $output = rtrim($output);
- if($lengthMax <= 0) $output .= $endMarkerFound ? $endMarkerText : "…";
- } else {
- $elementsOpen = array();
- while(true)
- {
- $elementFound = preg_match("/&.*?\;|<\s*?([\/!]?\w*)(.*?)\s*?\>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
- $element = $matches[0][0];
- $elementName = $matches[1][0];
- $elementText = $matches[2][0];
- $elementOffsetBytes = $elementFound ? $matches[0][1] : strlenb($text);
- $string = substrb($text, $offsetBytes, $elementOffsetBytes - $offsetBytes);
- $length = strlenu($string);
- $output .= substru($string, 0, $length < $lengthMax ? $length : $lengthMax-1);
- $lengthMax -= $length + ($element[0]=='&' ? 1 : 0);
- if(!empty($element) && $element==$endMarker) { $lengthMax = 0; $endMarkerFound = true; }
- if($lengthMax<=0 || !$elementFound) break;
- if(!empty($elementName) && substru($elementText, -1)!='/' &&
- !preg_match("/^(area|br|col|hr|img|input|col|param|!)/i", $elementName))
- {
- if($elementName[0] != '/')
- {
- array_push($elementsOpen, $elementName);
- } else {
- array_pop($elementsOpen);
- }
- }
- $output .= $element;
- $offsetBytes = $elementOffsetBytes + strlenb($element);
- }
- $output = rtrim($output);
- for($i=count($elementsOpen)-1; $i>=0; --$i)
- {
- if(!preg_match("/^(dl|ol|ul|table|tbody|thead|tfoot|tr)/i", $elementsOpen[$i])) break;
- $output .= "</".$elementsOpen[$i].">";
- }
- if($lengthMax <= 0) $output .= $endMarkerFound ? $endMarkerText : "…";
- for(; $i>=0; --$i) $output .= "</".$elementsOpen[$i].">";
- }
- return $output;
- }
-
- // Create keywords from text string
- function createTextKeywords($text, $keywordsMax = 0)
- {
- $tokens = array_unique(preg_split("/[,\s\(\)\+\-]/", strtoloweru($text)));
- foreach($tokens as $key=>$value) if(strlenu($value) < 3) unset($tokens[$key]);
- if($keywordsMax) $tokens = array_slice($tokens, 0, $keywordsMax);
- return implode(", ", $tokens);
- }
-
- // Create title from text string
- function createTextTitle($text)
- {
- if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = strreplaceu('-', ' ', ucfirst($matches[1]));
- return $text;
- }
-
- // Create random text for cryptography
- function createSalt($length, $bcryptFormat = false)
- {
- $dataBuffer = $salt = "";
- $dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
- if(empty($dataBuffer) && function_exists("mcrypt_create_iv"))
- {
- $dataBuffer = @mcrypt_create_iv($dataBufferSize, MCRYPT_DEV_URANDOM);
- }
- if(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 {
- $salt = substrb(bin2hex($dataBuffer), 0, $length);
- }
- }
- return $salt;
- }
-
- // Create hash with random salt, bcrypt or sha256
- 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;
- }
- return $hash;
- }
-
- // Verify that text matches hash
- 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 = substrb($hash, 0, 4);
- $salt = substrb($hash, 4, 32);
- $hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
- }
- break;
- }
- $ok = !empty($hashCalculated) && strlenb($hashCalculated)==strlenb($hash);
- if($ok) for($i=0; $i<strlenb($hashCalculated); ++$i) $ok &= $hashCalculated[$i] == $hash[$i];
- return $ok;
- }
-
- // Detect web browser language
- function detectBrowserLanguage($languages, $languageDefault)
- {
- $language = $languageDefault;
- if(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
- {
- foreach(preg_split("/,\s*/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $string)
- {
- $tokens = explode(';', $string);
- if(in_array($tokens[0], $languages)) { $language = $tokens[0]; break; }
- }
- }
- return $language;
- }
-
- // Detect image dimensions and type, png or jpg
- function detectImageInfo($fileName)
- {
- $width = $height = 0;
- $type = "";
- $fileHandle = @fopen($fileName, "rb");
- if($fileHandle)
- {
- if(substru(strtoloweru($fileName), -3) == "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 = "png";
- }
- } else if(substru(strtoloweru($fileName), -3) == "jpg") {
- $dataBufferSizeMax = filesize($fileName);
- $dataBufferSize = min($dataBufferSizeMax, 4096);
- $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 = "jpg";
- 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;
- $dataBuffer .= fread($fileHandle, $dataBufferDiff);
- if(feof($fileHandle)) { $dataBufferSize = 0; break; }
- }
- }
- }
- }
- fclose($fileHandle);
- }
- return array($width, $height, $type);
- }
-
- // Start timer
- function timerStart(&$time)
- {
- $time = microtime(true);
- }
-
- // Stop timer and calculate elapsed time in milliseconds
- function timerStop(&$time)
- {
- $time = intval((microtime(true)-$time) * 1000);
- }
-}
-
-// Unicode support for PHP
-mb_internal_encoding("UTF-8");
-function strempty($string) { return is_null($string) || $string===""; }
-function strreplaceu() { return call_user_func_array("str_replace", func_get_args()); }
-function strtoloweru() { return call_user_func_array("mb_strtolower", func_get_args()); }
-function strtoupperu() { return call_user_func_array("mb_strtoupper", func_get_args()); }
-function strlenu() { return call_user_func_array("mb_strlen", func_get_args()); }
-function strlenb() { return call_user_func_array("strlen", func_get_args()); }
-function strposu() { return call_user_func_array("mb_strpos", func_get_args()); }
-function strposb() { return call_user_func_array("strpos", func_get_args()); }
-function strrposu() { return call_user_func_array("mb_strrpos", func_get_args()); }
-function strrposb() { return call_user_func_array("strrpos", func_get_args()); }
-function substru() { return call_user_func_array("mb_substr", func_get_args()); }
-function substrb() { return call_user_func_array("substr", func_get_args()); }
-
-// Error reporting for PHP
-error_reporting(E_ALL ^ E_NOTICE);
-?>
-\ No newline at end of file
diff --git a/system/plugins/commandline.php b/system/plugins/commandline.php
@@ -0,0 +1,552 @@
+<?php
+// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
+// This file may be used and distributed under the terms of the public license.
+
+// Command line plugin
+class YellowCommandline
+{
+ const Version = "0.6.1";
+ var $yellow; //access to API
+ var $content; //number of content pages
+ var $media; //number of media files
+ var $system; //number of system files
+ var $error; //number of build errors
+ var $locationsArgs; //locations with location arguments detected
+ var $locationsArgsPagination; //locations with pagination arguments detected
+
+ // Handle initialisation
+ function onLoad($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->yellow->config->setDefault("commandlineVersionUrl", "https://github.com/datenstrom/yellow-extensions");
+ }
+
+ // Handle command
+ function onCommand($args)
+ {
+ list($name, $command) = $args;
+ switch($command)
+ {
+ case "": $statusCode = $this->helpCommand(); break;
+ case "version": $statusCode = $this->versionCommand($args); break;
+ case "build": $statusCode = $this->buildCommand($args); break;
+ case "clean": $statusCode = $this->cleanCommand($args); break;
+ default: $statusCode = $this->pluginCommand($args);
+ }
+ if($statusCode == 0)
+ {
+ $statusCode = 400;
+ echo "Yellow $command: Command not found\n";
+ }
+ return $statusCode;
+ }
+
+ // Handle command help
+ function onCommandHelp()
+ {
+ $help .= "version\n";
+ $help .= "build [DIRECTORY LOCATION]\n";
+ $help .= "clean [DIRECTORY LOCATION]\n";
+ return $help;
+ }
+
+ // Show available commands
+ function helpCommand()
+ {
+ echo "Yellow ".YellowCore::Version."\n";
+ foreach($this->getCommandHelp() as $line) echo (++$lineCounter>1 ? " " : "Syntax: ")."yellow.php $line\n";
+ return 200;
+ }
+
+ // Show software version
+ function versionCommand($args)
+ {
+ $statusCode = 0;
+ echo "Yellow ".YellowCore::Version."\n";
+ $url = $this->yellow->config->get("commandlineVersionUrl");
+ list($dummy, $command) = $args;
+ list($statusCode, $versionCurrent) = $this->getPluginVersion();
+ list($statusCode, $versionLatest) = $this->getPluginVersion($url);
+ foreach($versionCurrent as $key=>$value)
+ {
+ if($versionCurrent[$key] >= $versionLatest[$key])
+ {
+ echo "$key $value\n";
+ } else {
+ echo "$key $value - Update available\n";
+ ++$updates;
+ }
+ }
+ if($statusCode != 200) echo "ERROR checking updates at $url, $versionLatest[error]\n";
+ if(!$this->yellow->config->isExisting("sitename"))
+ {
+ $fileNames = $this->yellow->toolbox->getDirectoryEntries(
+ $this->yellow->config->get("configDir"), "/^.*\.ini$/", true, false);
+ foreach($fileNames as $fileName) $statusCode = max($statusCode, $this->updateConfigFile($fileName));
+ }
+ if($updates) echo "Yellow $command: $updates update".($updates==1 ? "":"s")." available at $url\n";
+ return $statusCode;
+ }
+
+ // Build static pages
+ function buildCommand($args)
+ {
+ $statusCode = 0;
+ list($dummy, $command, $path, $location) = $args;
+ if(empty($location) || $location[0]=='/')
+ {
+ if($this->checkStaticConfig())
+ {
+ $statusCode = $this->buildStatic($path, $location);
+ } else {
+ $statusCode = 500;
+ list($this->content, $this->media, $this->system, $this->error) = array(0, 0, 0, 1);
+ $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("configFile");
+ echo "ERROR bulding pages: Please configure ServerScheme, ServerName, ServerBase, ServerTime in file '$fileName'!\n";
+ }
+ echo "Yellow $command: $this->content content, $this->media media, $this->system system";
+ echo ", $this->error error".($this->error!=1 ? 's' : '');
+ echo ", status $statusCode\n";
+ } else {
+ $statusCode = 400;
+ echo "Yellow $command: Invalid arguments\n";
+ }
+ return $statusCode;
+ }
+
+ // Build static pages and files
+ function buildStatic($path, $location)
+ {
+ $this->yellow->toolbox->timerStart($time);
+ $path = rtrim(empty($path) ? $this->yellow->config->get("staticDir") : $path, '/');
+ $this->content = $this->media = $this->system = $this->error = $statusCode = 0;
+ $this->locationsArgs = $this->locationsArgsPagination = array();
+ if(empty($location))
+ {
+ $statusCode = $this->cleanStatic($path, $location);
+ foreach($this->getStaticLocations() as $location)
+ {
+ $statusCode = max($statusCode, $this->buildStaticPage($path, $location, true));
+ }
+ foreach($this->locationsArgs as $location)
+ {
+ $statusCode = max($statusCode, $this->buildStaticPage($path, $location, true));
+ }
+ foreach($this->locationsArgsPagination as $location)
+ {
+ if(substru($location, -1) != ':')
+ {
+ $statusCode = max($statusCode, $this->buildStaticPage($path, $location, false, true));
+ }
+ for($pageNumber=2; $pageNumber<=999; ++$pageNumber)
+ {
+ $statusCodeLocation = $this->buildStaticPage($path, $location.$pageNumber, false, true);
+ $statusCode = max($statusCode, $statusCodeLocation);
+ if($statusCodeLocation == 0) break;
+ }
+ }
+ $statusCode = max($statusCode, $this->buildStaticPage($path, "/error", false, false, true));
+ foreach($this->getStaticFilesMedia($path) as $fileNameSource=>$fileNameDest)
+ {
+ $statusCode = max($statusCode, $this->buildStaticFile($fileNameSource, $fileNameDest, true));
+ }
+ foreach($this->getStaticFilesSystem($path) as $fileNameSource=>$fileNameDest)
+ {
+ $statusCode = max($statusCode, $this->buildStaticFile($fileNameSource, $fileNameDest, false));
+ }
+ } else {
+ $statusCode = $this->buildStaticPage($path, $location);
+ }
+ $this->yellow->toolbox->timerStop($time);
+ if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStatic time:$time ms\n";
+ return $statusCode;
+ }
+
+ // Build static page
+ function buildStaticPage($path, $location, $analyse = false, $probe = false, $error = false)
+ {
+ ob_start();
+ $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1";
+ $_SERVER["SERVER_NAME"] = $this->yellow->config->get("serverName");
+ $_SERVER["REQUEST_URI"] = $this->yellow->config->get("serverBase").$location;
+ $_SERVER["SCRIPT_NAME"] = $this->yellow->config->get("serverBase")."/yellow.php";
+ $_REQUEST = array();
+ $statusCode = $this->yellow->request();
+ if($statusCode<400 || $error)
+ {
+ $fileData = ob_get_contents();
+ $modified = strtotime($this->yellow->page->getHeader("Last-Modified"));
+ if($statusCode>=301 && $statusCode<=303)
+ {
+ $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location"));
+ $modified = time();
+ }
+ $fileName = $this->getStaticFile($path, $location, $statusCode);
+ 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'!");
+ }
+ }
+ ob_end_clean();
+ if($statusCode==200 && $analyse) $this->analyseStaticPage($fileData);
+ if($statusCode==404 && $error) $statusCode = 200;
+ if($statusCode==404 && $probe) $statusCode = 0;
+ if($statusCode != 0) ++$this->content;
+ if($statusCode >= 400)
+ {
+ ++$this->error;
+ echo "ERROR building content location '$location', ".$this->yellow->page->getStatusCode(true)."\n";
+ }
+ if(defined("DEBUG") && DEBUG>=3) echo $fileData;
+ if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStaticPage status:$statusCode location:$location\n";
+ return $statusCode;
+ }
+
+ // Build static file
+ function buildStaticFile($fileNameSource, $fileNameDest, $fileTypeMedia)
+ {
+ $statusCode = $this->yellow->toolbox->copyFile($fileNameSource, $fileNameDest, true) &&
+ $this->yellow->toolbox->modifyFile($fileNameDest, filemtime($fileNameSource)) ? 200 : 500;
+ if($fileTypeMedia) { ++$this->media; } else { ++$this->system; }
+ if($statusCode >= 400)
+ {
+ ++$this->error;
+ $fileType = $fileTypeMedia ? "media file" : "system file";
+ $fileError = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
+ echo "ERROR building $fileType, $fileError: Can't write file '$fileNameDest'!\n";
+ }
+ if(defined("DEBUG") && DEBUG>=1) echo "YellowCommandline::buildStaticFile status:$statusCode file:$fileNameDest\n";
+ return $statusCode;
+ }
+
+ // Analyse static page, detect locations with arguments
+ function analyseStaticPage($text)
+ {
+ $serverName = $this->yellow->config->get("serverName");
+ $serverBase = $this->yellow->config->get("serverBase");
+ $pagination = $this->yellow->config->get("contentPagination");
+ preg_match_all("/<a(.*?)href=\"([^\"]+)\"(.*?)>/i", $text, $matches);
+ foreach($matches[2] as $match)
+ {
+ if(preg_match("/^\w+:\/+(.*?)(\/.*)$/", $match, $tokens))
+ {
+ if($tokens[1] != $serverName) continue;
+ $match = $tokens[2];
+ }
+ if(!$this->yellow->toolbox->isLocationArgs($match)) continue;
+ if(substru($match, 0, strlenu($serverBase)) != $serverBase) continue;
+ $location = rawurldecode(substru($match, strlenu($serverBase)));
+ if(!$this->yellow->toolbox->isLocationArgsPagination($location, $pagination))
+ {
+ $location = rtrim($location, '/').'/';
+ if(is_null($this->locationsArgs[$location]))
+ {
+ $this->locationsArgs[$location] = $location;
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowCommandline::analyseStaticPage detected location:$location\n";
+ }
+ } else {
+ $location = rtrim($location, "0..9");
+ if(is_null($this->locationsArgsPagination[$location]))
+ {
+ $this->locationsArgsPagination[$location] = $location;
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowCommandline::analyseStaticPage detected location:$location\n";
+ }
+ }
+ }
+ }
+
+ // Clean static pages
+ function cleanCommand($args)
+ {
+ $statusCode = 0;
+ list($dummy, $command, $path, $location) = $args;
+ if(empty($location) || $location[0]=='/')
+ {
+ $statusCode = $this->cleanStatic($path, $location);
+ echo "Yellow $command: Static page".(empty($location) ? "s" : "")." ".($statusCode!=200 ? "not " : "")."cleaned\n";
+ } else {
+ $statusCode = 400;
+ echo "Yellow $command: Invalid arguments\n";
+ }
+ return $statusCode;
+ }
+
+ // Clean static directories and files
+ function cleanStatic($path, $location)
+ {
+ $statusCode = 200;
+ $path = rtrim(empty($path) ? $this->yellow->config->get("staticDir") : $path, '/');
+ if(empty($location))
+ {
+ $statusCode = max($statusCode, $this->pluginCommand(array("all", "clean")));
+ $statusCode = max($statusCode, $this->cleanStaticDirectory($path));
+ } else {
+ $statusCode = $this->cleanStaticFile($path, $location);
+ }
+ return $statusCode;
+ }
+
+ // Clean static directory
+ function cleanStaticDirectory($path)
+ {
+ $statusCode = 200;
+ if(is_dir($path))
+ {
+ if(!$this->checkStaticDirectory($path) || !$this->yellow->toolbox->deleteDirectory($path, true))
+ {
+ $statusCode = 500;
+ echo "ERROR cleaning pages: Can't delete directory '$path'!\n";
+ }
+ }
+ return $statusCode;
+ }
+
+ // Clean static file
+ function cleanStaticFile($path, $location)
+ {
+ $statusCode = 200;
+ $fileName = $this->getStaticFile($path, $location, $statusCode);
+ if(is_file($fileName))
+ {
+ if(!$this->checkStaticDirectory($path) || !$this->yellow->toolbox->deleteFile($fileName))
+ {
+ $statusCode = 500;
+ echo "ERROR cleaning pages: Can't delete file '$fileName'!\n";
+ }
+ }
+ return $statusCode;
+ }
+
+ // Update configuration file if necessary
+ function updateConfigFile($fileName)
+ {
+ $statusCode = 200;
+ $fileData = @file($fileName);
+ if($fileData)
+ {
+ foreach($fileData as $line)
+ {
+ if(preg_match("/^\/\/(.*)$/", $line, $matches))
+ {
+ if(!$found) $matches[1] .= " (updated)";
+ $line = "#$matches[1]\n";
+ $found = true;
+ }
+ if(preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches))
+ {
+ $line = ucfirst($matches[1]).": $matches[2]\n";
+ }
+ if(preg_match("/^([^,:]+),([^,]+),([^,]+),([^,]+),([^,\n]+)$/", $line, $matches))
+ {
+ $line = "$matches[1]: $matches[2],$matches[3],$matches[4],active,$matches[5]\n";
+ }
+ $fileDataNew .= $line;
+ }
+ }
+ if($found)
+ {
+ if(!$this->yellow->toolbox->createFile($fileName, $fileDataNew))
+ {
+ $statusCode = 500;
+ echo "ERROR updating configuration: Can't write file '$fileName'!\n";
+ }
+ }
+ return $statusCode;
+ }
+
+ // Forward plugin command
+ function pluginCommand($args)
+ {
+ $statusCode = 0;
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if($key == "commandline") continue;
+ if(method_exists($value["obj"], "onCommand"))
+ {
+ $statusCode = $value["obj"]->onCommand($args);
+ if($statusCode != 0) break;
+ }
+ }
+ return $statusCode;
+ }
+
+ // Check static configuration
+ function checkStaticConfig()
+ {
+ $serverScheme = $this->yellow->config->get("serverScheme");
+ $serverName = $this->yellow->config->get("serverName");
+ $serverBase = $this->yellow->config->get("serverBase");
+ return !empty($serverScheme) && !empty($serverName) &&
+ $this->yellow->lookup->isValidLocation($serverBase) && $serverBase!="/";
+ }
+
+ // Check static directory
+ function checkStaticDirectory($path)
+ {
+ $ok = false;
+ if(!empty($path))
+ {
+ if($path == rtrim($this->yellow->config->get("staticDir"), '/')) $ok = true;
+ if(is_file("$path/".$this->yellow->config->get("staticAccessFile"))) $ok = true;
+ if(is_file("$path/yellow.php")) $ok = false;
+ }
+ return $ok;
+ }
+
+ // Return static locations from file system
+ function getStaticLocations()
+ {
+ $locations = array();
+ $serverScheme = $this->yellow->config->get("serverScheme");
+ $serverName = $this->yellow->config->get("serverName");
+ $serverBase = $this->yellow->config->get("serverBase");
+ $this->yellow->page->setRequestInformation($serverScheme, $serverName, $serverBase, "", "");
+ foreach($this->yellow->pages->index(true, true) as $page)
+ {
+ if($page->get("status")!="ignore" && $page->get("status")!="draft")
+ {
+ array_push($locations, $page->location);
+ }
+ }
+ if(!$this->yellow->pages->find("/") && $this->yellow->config->get("multiLanguageMode")) array_unshift($locations, "/");
+ return $locations;
+ }
+
+ // Return static media files
+ function getStaticFilesMedia($path)
+ {
+ $files = array();
+ $fileNames = $this->yellow->toolbox->getDirectoryEntriesRecursive(
+ $this->yellow->config->get("mediaDir"), "/.*/", false, false);
+ foreach($fileNames as $fileName) $files[$fileName] = "$path/$fileName";
+ return $files;
+ }
+
+ // Return static system files
+ function getStaticFilesSystem($path)
+ {
+ $files = array();
+ $fileNames = $this->yellow->toolbox->getDirectoryEntries(
+ $this->yellow->config->get("pluginDir"), "/\.(css|js|jpg|png|txt|woff)/", false, false);
+ foreach($fileNames as $fileName)
+ {
+ $files[$fileName] = $path.$this->yellow->config->get("pluginLocation").basename($fileName);
+ }
+ $fileNames = $this->yellow->toolbox->getDirectoryEntries(
+ $this->yellow->config->get("themeDir"), "/\.(css|js|jpg|png|txt|woff)/", false, false);
+ foreach($fileNames as $fileName)
+ {
+ $files[$fileName] = $path.$this->yellow->config->get("themeLocation").basename($fileName);
+ }
+ $fileNames = array();
+ array_push($fileNames, $this->yellow->config->get("staticAccessFile"));
+ array_push($fileNames, $this->yellow->config->get("configDir").$this->yellow->config->get("robotsFile"));
+ foreach($fileNames as $fileName) $files[$fileName] = "$path/".basename($fileName);
+ return $files;
+ }
+
+ // Return static file
+ function getStaticFile($path, $location, $statusCode)
+ {
+ if($statusCode < 400)
+ {
+ $fileName = $path.$location;
+ if(!$this->yellow->lookup->isFileLocation($location)) $fileName .= $this->yellow->config->get("staticDefaultFile");
+ } else if($statusCode == 404) {
+ $fileName = $path."/".$this->yellow->config->get("staticErrorFile");
+ }
+ return $fileName;
+ }
+
+ // Return static redirect
+ 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 plugin version
+ function getPluginVersion($url = "")
+ {
+ $version = array();
+ if(empty($url))
+ {
+ $statusCode = 200;
+ $version["YellowCore"] = YellowCore::Version;
+ foreach($this->yellow->plugins->plugins as $key=>$value) $version[$value["class"]] = $value[version];
+ } else {
+ if(extension_loaded("curl"))
+ {
+ $pluginVersionUrl = $this->getPluginVersionUrl($url);
+ $curlHandle = curl_init();
+ curl_setopt($curlHandle, CURLOPT_URL, $pluginVersionUrl);
+ curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; YellowCore/".YellowCore::Version).")";
+ curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 30);
+ $rawData = curl_exec($curlHandle);
+ $statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
+ curl_close($curlHandle);
+ if($statusCode == 200)
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowCommandline::getPluginVersion file:$pluginVersionUrl\n";
+ foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
+ {
+ if(preg_match("/^(\w+)\s*:\s*([0-9\.]+)/", $line, $matches))
+ {
+ $version[$matches[1]] = $matches[2];
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowCommandline::getPluginVersion $matches[1]:$matches[2]\n";
+ }
+ }
+ }
+ if($statusCode == 0) $statusCode = 444;
+ $version["error"] = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
+ } else {
+ $statusCode = 500;
+ $version["error"] = "Plugin 'commandline' requires cURL library!";
+ }
+ }
+ uksort($version, strnatcasecmp);
+ return array($statusCode, $version);
+ }
+
+ // Return plugin version URL from repository
+ function getPluginVersionUrl($url)
+ {
+ if(!$this->yellow->lookup->isFileLocation($url))
+ {
+ if(preg_match("#^https://github.com/(.+)$#", $url, $matches))
+ {
+ $url = "https://raw.githubusercontent.com/".$matches[1]."/master/version.ini";
+ }
+ }
+ return $url;
+ }
+
+ // Return command help
+ function getCommandHelp()
+ {
+ $data = array();
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onCommandHelp"))
+ {
+ foreach(preg_split("/[\r\n]+/", $value["obj"]->onCommandHelp()) as $line)
+ {
+ list($command, $text) = explode(' ', $line, 2);
+ if(!empty($command) && is_null($data[$command])) $data[$command] = $line;
+ }
+ }
+ }
+ uksort($data, strnatcasecmp);
+ return $data;
+ }
+}
+
+$yellow->plugins->register("commandline", "YellowCommandline", YellowCommandline::Version);
+?>
+\ No newline at end of file
diff --git a/system/plugins/core.php b/system/plugins/core.php
@@ -0,0 +1,2858 @@
+<?php
+// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
+// This file may be used and distributed under the terms of the public license.
+
+// Yellow core
+class YellowCore
+{
+ const Version = "0.6.1";
+ var $page; //current page
+ var $pages; //pages from file system
+ var $files; //files from file system
+ var $plugins; //plugins
+ var $config; //configuration
+ var $text; //text strings
+ var $lookup; //location and file lookup
+ var $toolbox; //toolbox with helpers
+
+ function __construct()
+ {
+ $this->page = new YellowPage($this);
+ $this->pages = new YellowPages($this);
+ $this->files = new YellowFiles($this);
+ $this->plugins = new YellowPlugins($this);
+ $this->config = new YellowConfig($this);
+ $this->text = new YellowText($this);
+ $this->lookup = new YellowLookup($this);
+ $this->toolbox = new YellowToolbox();
+ $this->config->setDefault("sitename", "Yellow");
+ $this->config->setDefault("author", "Yellow");
+ $this->config->setDefault("language", "en");
+ $this->config->setDefault("theme", "default");
+ $this->config->setDefault("serverScheme", $this->toolbox->getServerScheme());
+ $this->config->setDefault("serverName", $this->toolbox->getServerName());
+ $this->config->setDefault("serverBase", $this->toolbox->getServerBase());
+ $this->config->setDefault("serverTime", $this->toolbox->getServerTime());
+ $this->config->setDefault("imageLocation", "/media/images/");
+ $this->config->setDefault("pluginLocation", "/media/plugins/");
+ $this->config->setDefault("themeLocation", "/media/themes/");
+ $this->config->setDefault("systemDir", "system/");
+ $this->config->setDefault("configDir", "system/config/");
+ $this->config->setDefault("pluginDir", "system/plugins/");
+ $this->config->setDefault("themeDir", "system/themes/");
+ $this->config->setDefault("snippetDir", "system/themes/snippets/");
+ $this->config->setDefault("templateDir", "system/themes/templates/");
+ $this->config->setDefault("mediaDir", "media/");
+ $this->config->setDefault("imageDir", "media/images/");
+ $this->config->setDefault("staticDir", "cache/");
+ $this->config->setDefault("staticAccessFile", ".htaccess");
+ $this->config->setDefault("staticDefaultFile", "index.html");
+ $this->config->setDefault("staticErrorFile", "error.html");
+ $this->config->setDefault("contentDir", "content/");
+ $this->config->setDefault("contentRootDir", "default/");
+ $this->config->setDefault("contentHomeDir", "home/");
+ $this->config->setDefault("contentDefaultFile", "page.txt");
+ $this->config->setDefault("contentPagination", "page");
+ $this->config->setDefault("contentExtension", ".txt");
+ $this->config->setDefault("configExtension", ".ini");
+ $this->config->setDefault("configFile", "config.ini");
+ $this->config->setDefault("textFile", "language-(.*).ini");
+ $this->config->setDefault("errorFile", "page-error-(.*).txt");
+ $this->config->setDefault("robotsFile", "robots.txt");
+ $this->config->setDefault("iconFile", "icon.png");
+ $this->config->setDefault("template", "default");
+ $this->config->setDefault("navigation", "navigation");
+ $this->config->setDefault("sidebar", "sidebar");
+ $this->config->setDefault("parser", "markdown");
+ $this->config->setDefault("parserSafeMode", "0");
+ $this->config->setDefault("multiLanguageMode", "0");
+ $this->load();
+ }
+
+ // Initialise configuration
+ function load()
+ {
+ if(defined("DEBUG") && DEBUG>=3)
+ {
+ $serverSoftware = $this->toolbox->getServerSoftware();
+ echo "Yellow ".YellowCore::Version.", PHP ".PHP_VERSION.", $serverSoftware<br>\n";
+ }
+ $this->config->load($this->config->get("configDir").$this->config->get("configFile"));
+ $this->text->load($this->config->get("configDir").$this->config->get("textFile"));
+ date_default_timezone_set($this->config->get("serverTime"));
+ list($pathRoot, $pathHome) = $this->lookup->getContentInformation();
+ $this->config->set("contentRootDir", $pathRoot);
+ $this->config->set("contentHomeDir", $pathHome);
+ }
+
+ // Handle request
+ function request()
+ {
+ ob_start();
+ $statusCode = 0;
+ $this->toolbox->timerStart($time);
+ list($serverScheme, $serverName, $base, $location, $fileName) = $this->getRequestInformation();
+ $this->page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
+ foreach($this->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onRequest"))
+ {
+ $this->pages->requestHandler = $key;
+ $statusCode = $value["obj"]->onRequest($serverScheme, $serverName, $base, $location, $fileName);
+ if($statusCode != 0) break;
+ }
+ }
+ if($statusCode == 0)
+ {
+ $this->pages->requestHandler = "core";
+ $statusCode = $this->processRequest($serverScheme, $serverName, $base, $location, $fileName, true);
+ }
+ if($this->page->isError()) $statusCode = $this->processRequestError();
+ $this->toolbox->timerStop($time);
+ ob_end_flush();
+ if(defined("DEBUG") && DEBUG>=1) echo "YellowCore::request status:$statusCode location:$location<br/>\n";
+ if(defined("DEBUG") && DEBUG>=1) echo "YellowCore::request time:$time ms<br/>\n";
+ return $statusCode;
+ }
+
+ // Process request
+ function processRequest($serverScheme, $serverName, $base, $location, $fileName, $cacheable)
+ {
+ $statusCode = 0;
+ if(is_readable($fileName))
+ {
+ if($this->toolbox->isRequestCleanUrl($location))
+ {
+ $statusCode = 303;
+ $locationArgs = $this->toolbox->getLocationArgsCleanUrl($this->config->get("contentPagination"));
+ $location = $this->lookup->normaliseUrl($serverScheme, $serverName, $base, $location.$locationArgs);
+ $this->sendStatus($statusCode, $location);
+ }
+ } else {
+ if($this->isRequestContentDirectory($location))
+ {
+ $statusCode = 301;
+ $location = $this->lookup->isFileLocation($location) ? "$location/" : "/".$this->getRequestLanguage()."/";
+ $location = $this->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
+ $this->sendStatus($statusCode, $location);
+ }
+ }
+ if($statusCode == 0)
+ {
+ $statusCode = is_readable($fileName) ? 200 : 404;
+ $fileName = $this->getStaticFileFromCache($location, $fileName, $cacheable, $statusCode);
+ if($this->isStaticFile($fileName))
+ {
+ $statusCode = $this->sendFile($statusCode, $fileName, $cacheable);
+ } else {
+ $fileName = $this->readPage($serverScheme, $serverName, $base, $location, $fileName, $cacheable, $statusCode);
+ $statusCode = $this->sendPage();
+ }
+ }
+ if(defined("DEBUG") && DEBUG>=1)
+ {
+ $handler = $this->getRequestHandler();
+ echo "YellowCore::processRequest file:$fileName handler:$handler<br/>\n";
+ }
+ return $statusCode;
+ }
+
+ // Process request with error
+ function processRequestError()
+ {
+ ob_clean();
+ $fileName = $this->readPage($this->page->serverScheme, $this->page->serverName, $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)
+ {
+ $handler = $this->getRequestHandler();
+ echo "YellowCore::processRequestError file:$fileName handler:$handler<br/>\n";
+ }
+ return $statusCode;
+ }
+
+ // Read page
+ function readPage($serverScheme, $serverName, $base, $location, $fileName, $cacheable, $statusCode, $pageError = "")
+ {
+ if($statusCode >= 400)
+ {
+ $fileName = $this->config->get("configDir").$this->config->get("errorFile");
+ $fileName = strreplaceu("(.*)", $statusCode, $fileName);
+ $cacheable = false;
+ }
+ $this->page = new YellowPage($this);
+ $this->page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
+ $this->page->parseData($this->toolbox->getFileData($fileName), $cacheable, $statusCode, $pageError);
+ $this->text->setLanguage($this->page->get("language"));
+ $this->page->parseContent();
+ return $fileName;
+ }
+
+ // Send page response
+ function sendPage()
+ {
+ $this->page->parsePage();
+ $statusCode = $this->page->statusCode;
+ $lastModifiedFormatted = $this->page->getHeader("Last-Modified");
+ if($statusCode==200 && $this->page->isCacheable() && $this->toolbox->isRequestNotModified($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)
+ {
+ foreach($this->page->headerData as $key=>$value) echo "YellowCore::sendPage $key: $value<br/>\n";
+ $fileNameTheme = $this->config->get("themeDir").$this->page->get("theme").".css";
+ $templateName = $this->page->get("template");
+ $parserName = $this->page->get("parser");
+ echo "YellowCore::sendPage theme:$fileNameTheme template:$templateName parser:$parserName<br/>\n";
+ }
+ return $statusCode;
+ }
+
+ // Send file response
+ function sendFile($statusCode, $fileName, $cacheable)
+ {
+ $lastModifiedFormatted = $this->toolbox->getHttpDateFormatted(filemtime($fileName));
+ if($statusCode==200 && $cacheable && $this->toolbox->isRequestNotModified($lastModifiedFormatted))
+ {
+ $statusCode = 304;
+ @header($this->toolbox->getHttpStatusFormatted($statusCode));
+ } else {
+ @header($this->toolbox->getHttpStatusFormatted($statusCode));
+ if(!$cacheable) @header("Cache-Control: no-cache, must-revalidate");
+ @header("Content-Type: ".$this->toolbox->getMimeContentType($fileName));
+ @header("Last-Modified: ".$lastModifiedFormatted);
+ echo $this->toolbox->getFileData($fileName);
+ }
+ return $statusCode;
+ }
+
+ // Send status response
+ function sendStatus($statusCode, $location = "")
+ {
+ if(!empty($location)) $this->page->clean($statusCode, $location);
+ @header($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";
+ }
+ }
+
+ // Return request information
+ function getRequestInformation($serverScheme = "", $serverName = "", $base = "")
+ {
+ $serverScheme = empty($serverScheme) ? $this->config->get("serverScheme") : $serverScheme;
+ $serverName = empty($serverName) ? $this->config->get("serverName") : $serverName;
+ $base = empty($base) ? $this->config->get("serverBase") : $base;
+ $location = $this->toolbox->getLocationClean();
+ $location = substru($location, strlenu($base));
+ if(preg_match("/\.(css|js|jpg|png|txt|woff)$/", $location))
+ {
+ $pluginLocationLength = strlenu($this->config->get("pluginLocation"));
+ $themeLocationLength = strlenu($this->config->get("themeLocation"));
+ if(substru($location, 0, $pluginLocationLength) == $this->config->get("pluginLocation")) {
+ $fileName = $this->config->get("pluginDir").substru($location, $pluginLocationLength);
+ } else if(substru($location, 0, $themeLocationLength) == $this->config->get("themeLocation")) {
+ $fileName = $this->config->get("themeDir").substru($location, $themeLocationLength);
+ } else if($location == "/".$this->config->get("robotsFile")) {
+ $fileName = $this->config->get("configDir").$this->config->get("robotsFile");
+ }
+ }
+ if(empty($fileName)) $fileName = $this->lookup->findFileFromLocation($location);
+ return array($serverScheme, $serverName, $base, $location, $fileName);
+ }
+
+ // Return request language
+ function getRequestLanguage()
+ {
+ return $this->toolbox->detectBrowserLanguage($this->pages->getLanguages(), $this->config->get("language"));
+ }
+
+ // Return request handler
+ function getRequestHandler()
+ {
+ return $this->pages->requestHandler;
+ }
+
+ // Return snippet arguments
+ function getSnippetArgs()
+ {
+ return $this->pages->snippetArgs;
+ }
+
+ // Return static file from cache if available
+ function getStaticFileFromCache($location, $fileName, $cacheable, $statusCode)
+ {
+ if(PHP_SAPI != "cli" && $cacheable)
+ {
+ if($statusCode == 200)
+ {
+ $location .= $this->toolbox->getLocationArgs();
+ $fileNameStatic = rtrim($this->config->get("staticDir"), '/').$location;
+ if(!$this->lookup->isFileLocation($location)) $fileNameStatic .= $this->config->get("staticDefaultFile");
+ } else if($statusCode == 404) {
+ $fileNameStatic = $this->config->get("staticDir").$this->config->get("staticErrorFile");
+ }
+ if(is_readable($fileNameStatic)) $fileName = $fileNameStatic;
+ }
+ return $fileName;
+ }
+
+ // Check if static file
+ function isStaticFile($fileName)
+ {
+ $staticDirLength = strlenu($this->config->get("staticDir"));
+ $systemDirLength = strlenu($this->config->get("systemDir"));
+ return substru($fileName, 0, $staticDirLength) == $this->config->get("staticDir") ||
+ substru($fileName, 0, $systemDirLength) == $this->config->get("systemDir");
+ }
+
+ // Check if request can be redirected into content directory
+ function isRequestContentDirectory($location)
+ {
+ $ok = false;
+ if($this->lookup->isFileLocation($location))
+ {
+ $path = $this->lookup->findFileFromLocation("$location/", true);
+ $ok = is_dir($path);
+ } else if($location=="/") {
+ $ok = $this->config->get("multiLanguageMode");
+ }
+ return $ok;
+ }
+
+ // Execute command
+ function command($name, $args = NULL)
+ {
+ $statusCode = 0;
+ if($this->plugins->isExisting($name))
+ {
+ $plugin = $this->plugins->plugins[$name];
+ if(method_exists($plugin["obj"], "onCommand")) $statusCode = $plugin["obj"]->onCommand(func_get_args());
+ } else {
+ $statusCode = 500;
+ $this->page->error($statusCode, "Plugin '$name' does not exist!");
+ }
+ return $statusCode;
+ }
+
+ // Execute snippet
+ function snippet($name, $args = NULL)
+ {
+ $this->pages->snippetArgs = func_get_args();
+ $this->page->parseSnippet($name);
+ }
+}
+
+// Yellow page
+class YellowPage
+{
+ var $yellow; //access to API
+ var $serverScheme; //server scheme
+ var $serverName; //server name
+ var $base; //base location
+ var $location; //page location
+ var $fileName; //content file name
+ var $lastModified; //last modification date
+ var $rawData; //raw data of page
+ var $metaDataOffsetBytes; //meta data offset
+ var $metaData; //meta data
+ var $headerData; //response header
+ var $outputData; //response output
+ var $pages; //page collection
+ var $pageRelations; //page relations
+ var $parser; //content parser
+ var $parserData; //content data of page
+ var $parserSafeMode; //page is parsed in safe mode? (boolean)
+ var $available; //page is available? (boolean)
+ var $visible; //page is visible location? (boolean)
+ var $active; //page is active location? (boolean)
+ var $cacheable; //page is cacheable? (boolean)
+ var $statusCode; //status code
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->metaData = array();
+ $this->headerData = array();
+ $this->pages = new YellowPageCollection($yellow);
+ $this->pageRelations = array();
+ }
+
+ // Set request information
+ function setRequestInformation($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $this->serverScheme = $serverScheme;
+ $this->serverName = $serverName;
+ $this->base = $base;
+ $this->location = $location;
+ $this->fileName = $fileName;
+ }
+
+ // Parse page data
+ function parseData($rawData, $cacheable, $statusCode, $pageError = "")
+ {
+ $this->lastModified = 0;
+ $this->rawData = $rawData;
+ $this->parser = NULL;
+ $this->parserData = "";
+ $this->parserSafeMode = intval($this->yellow->config->get("parserSafeMode"));
+ $this->available = true;
+ $this->visible = $this->yellow->lookup->isVisibleLocation($this->location, $this->fileName);
+ $this->active = $this->yellow->lookup->isActiveLocation($this->location, $this->yellow->page->location);
+ $this->cacheable = $cacheable;
+ $this->statusCode = $statusCode;
+ $this->parseMeta($pageError);
+ }
+
+ // Parse page data update
+ function parseDataUpdate()
+ {
+ if($this->statusCode == 0)
+ {
+ $fileHandle = @fopen($this->fileName, "r");
+ if($fileHandle)
+ {
+ $this->statusCode = 200;
+ $this->rawData = fread($fileHandle, filesize($this->fileName));
+ fclose($fileHandle);
+ $this->parseMeta();
+ }
+ }
+ }
+
+ // Parse page meta data
+ function parseMeta($pageError = "")
+ {
+ $this->metaData = array();
+ if(!is_null($this->rawData))
+ {
+ $this->set("title", $this->yellow->toolbox->createTextTitle($this->location));
+ $this->set("sitename", $this->yellow->config->get("sitename"));
+ $this->set("author", $this->yellow->config->get("author"));
+ $this->set("language", $this->yellow->lookup->findLanguageFromFile($this->fileName,
+ $this->yellow->config->get("language")));
+ $this->set("theme", $this->yellow->lookup->findNameFromFile($this->fileName,
+ $this->yellow->config->get("themeDir"), $this->yellow->config->get("theme"), ".css"));
+ $this->set("template", $this->yellow->lookup->findNameFromFile($this->fileName,
+ $this->yellow->config->get("templateDir"), $this->yellow->config->get("template"), ".html"));
+ $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
+ $this->set("navigation", $this->yellow->config->get("navigation"));
+ $this->set("sidebar", $this->yellow->config->get("sidebar"));
+ $this->set("parser", $this->yellow->config->get("parser"));
+
+ if(preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)[\r\n]+\-\-\-[\r\n]+/s", $this->rawData, $parts))
+ {
+ $this->metaDataOffsetBytes = strlenb($parts[0]);
+ foreach(preg_split("/[\r\n]+/", $parts[2]) as $line)
+ {
+ preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
+ if(!empty($matches[1]) && !strempty($matches[2])) $this->set(lcfirst($matches[1]), $matches[2]);
+ }
+ } else if(preg_match("/^(\xEF\xBB\xBF)?([^\r\n]+)[\r\n]+=+[\r\n]+/", $this->rawData, $parts)) {
+ $this->metaDataOffsetBytes = strlenb($parts[0]);
+ $this->set("title", $parts[2]);
+ }
+
+ $titleHeader = ($this->location == $this->yellow->pages->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("titleHeader")) $this->set("titleHeader", $titleHeader);
+ if(!$this->isExisting("titleNavigation")) $this->set("titleNavigation", $this->get("title"));
+ if($this->get("titleContent") == "-") $this->set("titleContent", "");
+ $this->set("pageRead", $this->yellow->lookup->normaliseUrl(
+ $this->yellow->config->get("serverScheme"),
+ $this->yellow->config->get("serverName"),
+ $this->yellow->config->get("serverBase"), $this->location));
+ $this->set("pageEdit", $this->yellow->lookup->normaliseUrl(
+ $this->yellow->config->get("webinterfaceServerScheme"),
+ $this->yellow->config->get("webinterfaceServerName"),
+ $this->yellow->config->get("serverBase"),
+ rtrim($this->yellow->config->get("webinterfaceLocation"), '/').$this->location));
+ $this->set("pageFile", $this->yellow->lookup->normaliseFile($this->fileName));
+ if($this->get("status") == "hidden") $this->available = false;
+ } else {
+ $this->set("type", $this->yellow->toolbox->getFileExtension($this->fileName));
+ $this->set("modified", date("Y-m-d H:i:s", $this->yellow->toolbox->getFileModified($this->fileName)));
+ $this->set("pageFile", $this->yellow->lookup->normaliseFile($this->fileName, true));
+ }
+ if(!empty($pageError)) $this->set("pageError", $pageError);
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onParseMeta")) $value["obj"]->onParseMeta($this);
+ }
+ }
+
+ // Parse page content on demand
+ function parseContent()
+ {
+ if(!is_object($this->parser))
+ {
+ if($this->yellow->plugins->isExisting($this->get("parser")))
+ {
+ $plugin = $this->yellow->plugins->plugins[$this->get("parser")];
+ if(method_exists($plugin["obj"], "onParseContentRaw"))
+ {
+ $this->parser = $plugin["obj"];
+ $this->parserData = $this->parser->onParseContentRaw($this, $this->getContent(true));
+ foreach($this->yellow->plugins->plugins 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);
+ $this->parserData = preg_replace("/@pageError/i", $this->get("pageError"), $this->parserData);
+ }
+ if(!$this->isExisting("description"))
+ {
+ $this->set("description", $this->yellow->toolbox->createTextDescription($this->parserData, 150));
+ }
+ if(!$this->isExisting("keywords"))
+ {
+ $this->set("keywords", $this->yellow->toolbox->createTextKeywords($this->get("title"), 10));
+ }
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowPage::parseContent location:".$this->location."<br/>\n";
+ }
+ }
+
+ // Parse page content block
+ function parseContentBlock($name, $text, $shortcut)
+ {
+ $output = NULL;
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onParseContentBlock"))
+ {
+ $output = $value["obj"]->onParseContentBlock($this, $name, $text, $shortcut);
+ if(!is_null($output)) break;
+ }
+ }
+ if(defined("DEBUG") && DEBUG>=3 && !empty($name)) echo "YellowPage::parseContentBlock name:$name shortcut:$shortcut<br/>\n";
+ return $output;
+ }
+
+ // Parse page
+ function parsePage()
+ {
+ $this->outputData = NULL;
+ if(!$this->isError())
+ {
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onParsePage")) $value["obj"]->onParsePage();
+ }
+ }
+ if(is_null($this->outputData))
+ {
+ ob_start();
+ $this->parseTemplate($this->get("template"));
+ $this->outputData = ob_get_contents();
+ ob_end_clean();
+ }
+ if(!$this->isCacheable()) $this->setHeader("Cache-Control", "no-cache, must-revalidate");
+ 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));
+ if(!$this->yellow->text->isLanguage($this->get("language")))
+ {
+ $this->error(500, "Language '".$this->get("language")."' does not exist!");
+ }
+ if(!is_file($this->yellow->config->get("themeDir").$this->get("theme").".css"))
+ {
+ $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->getRequestHandler()=="core" && $this->isExisting("redirect") && $this->statusCode==200)
+ {
+ $location = $this->yellow->lookup->normaliseLocation($this->get("redirect"), $this->base, $this->location);
+ $location = $this->yellow->lookup->normaliseUrl($this->serverScheme, $this->serverName, "", $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;
+ }
+
+ // Parse template
+ function parseTemplate($name)
+ {
+ $fileNameTemplate = $this->yellow->config->get("templateDir").$this->yellow->lookup->normaliseName($name).".html";
+ if(is_file($fileNameTemplate))
+ {
+ $this->setLastModified(filemtime($fileNameTemplate));
+ global $yellow;
+ require($fileNameTemplate);
+ } else {
+ $this->error(500, "Template '$name' does not exist!");
+ echo "Template error<br/>\n";
+ }
+ }
+
+ // Parse snippet
+ function parseSnippet($name)
+ {
+ $fileNameSnippet = $this->yellow->config->get("snippetDir").$this->yellow->lookup->normaliseName($name).".php";
+ if(is_file($fileNameSnippet))
+ {
+ $this->setLastModified(filemtime($fileNameSnippet));
+ global $yellow;
+ require($fileNameSnippet);
+ } else {
+ $this->error(500, "Snippet '$name' does not exist!");
+ echo "Snippet error<br/>\n";
+ }
+ }
+
+ // Set page meta data
+ function set($key, $value)
+ {
+ $this->metaData[$key] = $value;
+ }
+
+ // Return page meta data
+ function get($key)
+ {
+ return $this->isExisting($key) ? $this->metaData[$key] : "";
+ }
+
+ // Return page meta data, HTML encoded
+ function getHtml($key)
+ {
+ return htmlspecialchars($this->get($key));
+ }
+
+ // Return page meta data as language specific date
+ function getDate($key, $dateFormat = "")
+ {
+ if(!empty($dateFormat))
+ {
+ $format = $this->yellow->text->get($dateFormat);
+ } else {
+ $format = $this->yellow->text->get("dateFormatMedium");
+ }
+ return $this->yellow->text->getDateFormatted(strtotime($this->get($key)), $format);
+ }
+
+ // Return page content, HTML encoded or raw format
+ function getContent($rawFormat = false)
+ {
+ if($rawFormat)
+ {
+ $this->parseDataUpdate();
+ $text = substrb($this->rawData, $this->metaDataOffsetBytes);
+ } else {
+ $this->parseContent();
+ $text = $this->parserData;
+ }
+ return $text;
+ }
+
+ // Return parent page relative to current page, NULL if none
+ function getParent()
+ {
+ $parentLocation = $this->yellow->pages->getParentLocation($this->location);
+ return $this->yellow->pages->find($parentLocation);
+ }
+
+ // Return top-level page for current page, NULL if none
+ function getParentTop($homeFailback = true)
+ {
+ $parentTopLocation = $this->yellow->pages->getParentTopLocation($this->location);
+ if(!$this->yellow->pages->find($parentTopLocation) && $homeFailback)
+ {
+ $parentTopLocation = $this->yellow->pages->getHomeLocation($this->location);
+ }
+ return $this->yellow->pages->find($parentTopLocation);
+ }
+
+ // Return page collection with pages on the same level as current page
+ function getSiblings($showInvisible = false)
+ {
+ $parentLocation = $this->yellow->pages->getParentLocation($this->location);
+ return $this->yellow->pages->getChildren($parentLocation, $showInvisible);
+ }
+
+ // Return page collection with child pages relative to current page
+ function getChildren($showInvisible = false)
+ {
+ return $this->yellow->pages->getChildren($this->location, $showInvisible);
+ }
+
+ // Return page collection with media files for current page
+ function getFiles($showInvisible = false)
+ {
+ return $this->yellow->files->index($showInvisible, true)->filter("pageFile", $this->get("pageFile"));
+ }
+
+ // Set page collection with additional pages for current page
+ function setPages($pages)
+ {
+ $this->pages = $pages;
+ }
+
+ // Return page collection with additional pages for current page
+ function getPages()
+ {
+ return $this->pages;
+ }
+
+ // Set related page
+ function setPage($key, $page)
+ {
+ $this->pageRelations[$key] = $page;
+ }
+
+ // Return related page
+ function getPage($key)
+ {
+ return !is_null($this->pageRelations[$key]) ? $this->pageRelations[$key] : $this;
+ }
+
+ // Return absolute page location
+ function getLocation()
+ {
+ return $this->base.$this->location;
+ }
+
+ // Return page URL with server scheme and server name
+ function getUrl()
+ {
+ return $this->yellow->lookup->normaliseUrl($this->serverScheme, $this->serverName, $this->base, $this->location);
+ }
+
+ // Return page extra HTML data
+ function getExtra($name)
+ {
+ $output = "";
+ if($name == "header")
+ {
+ if(is_file($this->yellow->config->get("themeDir").$this->get("theme").".css"))
+ {
+ $location = $this->yellow->config->get("serverBase").
+ $this->yellow->config->get("themeLocation").$this->get("theme").".css";
+ $output .= "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".htmlspecialchars($location)."\" />\n";
+ }
+ if(is_file($this->yellow->config->get("imageDir").$this->yellow->config->get("iconFile")))
+ {
+ $location = $this->yellow->config->get("serverBase").
+ $this->yellow->config->get("imageLocation").$this->yellow->config->get("iconFile");
+ $contentType = $this->yellow->toolbox->getMimeContentType($this->yellow->config->get("iconFile"));
+ $output .= "<link rel=\"shortcut icon\" type=\"$contentType\" href=\"".htmlspecialchars($location)."\" />\n";
+ }
+ }
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onExtra"))
+ {
+ $outputPlugin = $value["obj"]->onExtra($name);
+ if(!is_null($outputPlugin)) $output .= $outputPlugin;
+ }
+ }
+ return $this->normaliseExtra($output);
+ }
+
+ // Normalise page extra HTML data
+ function normaliseExtra($text)
+ {
+ $outputScript = $outputStylesheet = $outputOther = $locations = array();
+ foreach($this->yellow->toolbox->getTextLines($text) as $line)
+ {
+ if(preg_match("/^<script (.*?)src=\"([^\"]+)\"(.*?)><\/script>$/i", $line, $matches))
+ {
+ if(is_null($locations[$matches[2]]))
+ {
+ $locations[$matches[2]] = $matches[2];
+ array_push($outputScript, $line);
+ }
+ } else if(preg_match("/^<link rel=\"stylesheet\"(.*?)href=\"([^\"]+)\"(.*?)>$/i", $line, $matches)) {
+ if(is_null($locations[$matches[2]]))
+ {
+ $locations[$matches[2]] = $matches[2];
+ array_push($outputStylesheet, $line);
+ }
+ } else {
+ array_push($outputOther, $line);
+ }
+ }
+ return implode($outputScript).implode($outputStylesheet).implode($outputOther);
+ }
+
+ // Set page response output
+ function setOutput($output)
+ {
+ $this->outputData = $output;
+ }
+
+ // Set page response header
+ function setHeader($key, $value)
+ {
+ $this->headerData[$key] = $value;
+ }
+
+ // Return page response header
+ function getHeader($key)
+ {
+ return $this->isHeader($key) ? $this->headerData[$key] : "";
+ }
+
+ // Return page content modification date, Unix time or HTTP format
+ function getModified($httpFormat = false)
+ {
+ $modified = strtotime($this->get("modified"));
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
+ }
+
+ // Set last modification date, Unix time
+ function setLastModified($modified)
+ {
+ $this->lastModified = max($this->lastModified, $modified);
+ }
+
+ // Return last modification date, Unix time or HTTP format
+ function getLastModified($httpFormat = false)
+ {
+ $modified = max($this->lastModified, $this->getModified(), $this->yellow->config->getModified(),
+ $this->yellow->text->getModified(), $this->yellow->plugins->getModified());
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($modified) : $modified;
+ }
+
+ // Return page status code, number or HTTP format
+ function getStatusCode($httpFormat = false)
+ {
+ $statusCode = $this->statusCode;
+ if($httpFormat)
+ {
+ $statusCode = $this->yellow->toolbox->getHttpStatusFormatted($statusCode);
+ if($this->isExisting("pageError")) $statusCode .= ": ".$this->get("pageError");
+ }
+ return $statusCode;
+ }
+
+ // Respond with error page
+ function error($statusCode, $pageError = "")
+ {
+ if(!$this->isExisting("pageError") && $statusCode>0)
+ {
+ $this->statusCode = $statusCode;
+ $this->set("pageError", empty($pageError) ? "Template/snippet error!" : $pageError);
+ }
+ }
+
+ // Respond with status code, no page content
+ 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, must-revalidate");
+ }
+ $this->set("pageClean", (string)$statusCode);
+ }
+ }
+
+ // Check if page is available
+ function isAvailable()
+ {
+ return $this->available;
+ }
+
+ // Check if page is visible
+ function isVisible()
+ {
+ return $this->visible;
+ }
+
+ // Check if page is within current request
+ function isActive()
+ {
+ return $this->active;
+ }
+
+ // Check if page is cacheable
+ function isCacheable()
+ {
+ return $this->cacheable;
+ }
+
+ // Check if page with error
+ function isError()
+ {
+ return $this->isExisting("pageError");
+ }
+
+ // Check if response header exists
+ function isHeader($key)
+ {
+ return !is_null($this->headerData[$key]);
+ }
+
+ // Check if page meta data exists
+ function isExisting($key)
+ {
+ return !is_null($this->metaData[$key]);
+ }
+
+ // Check if related page exists
+ function isPage($key)
+ {
+ return !is_null($this->pageRelations[$key]);
+ }
+}
+
+// Yellow page collection as array
+class YellowPageCollection extends ArrayObject
+{
+ var $yellow; //access to API
+ var $filterValue; //current page filter value
+ var $paginationNumber; //current page number in pagination
+ var $paginationCount; //highest page number in pagination
+
+ function __construct($yellow)
+ {
+ parent::__construct(array());
+ $this->yellow = $yellow;
+ }
+
+ // Filter page collection by meta data
+ function filter($key, $value, $exactMatch = true)
+ {
+ if(!empty($key))
+ {
+ $array = array();
+ $value = strreplaceu(' ', '-', strtoloweru($value));
+ $valueLength = strlenu($value);
+ foreach($this->getArrayCopy() as $page)
+ {
+ if($page->isExisting($key))
+ {
+ foreach(preg_split("/,\s*/", $page->get($key)) as $pageValue)
+ {
+ $pageValueLength = $exactMatch ? strlenu($pageValue) : $valueLength;
+ if($value == substru(strreplaceu(' ', '-', strtoloweru($pageValue)), 0, $pageValueLength))
+ {
+ $this->filterValue = substru($pageValue, 0, $pageValueLength);
+ array_push($array, $page);
+ }
+ }
+ }
+ }
+ $this->exchangeArray($array);
+ }
+ return $this;
+ }
+
+ // Filter page collection by file name
+ 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 meta data
+ function sort($key, $ascendingOrder = true)
+ {
+ $callback = function($a, $b) use ($key, $ascendingOrder)
+ {
+ return $ascendingOrder ?
+ strnatcasecmp($a->get($key), $b->get($key)) :
+ strnatcasecmp($b->get($key), $a->get($key));
+ };
+ $array = $this->getArrayCopy();
+ usort($array, $callback);
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Sort page collection by meta data similarity
+ function similar($page, $ascendingOrder = false)
+ {
+ $location = $page->location;
+ $keywords = $this->yellow->toolbox->createTextKeywords($page->get("title"));
+ $keywords .= ",".$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("searchscore", $ascendingOrder);
+ }
+ return $this;
+ }
+
+ // Merge page collection
+ function merge($input)
+ {
+ $this->exchangeArray(array_merge($this->getArrayCopy(), (array)$input));
+ return $this;
+ }
+
+ // Append to end of page collection
+ function append($page)
+ {
+ parent::append($page);
+ return $this;
+ }
+
+ // Prepend to start of page collection
+ function prepend($page)
+ {
+ $array = $this->getArrayCopy();
+ array_unshift($array, $page);
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Limit the number of pages in page collection
+ function limit($pagesMax)
+ {
+ $this->exchangeArray(array_slice($this->getArrayCopy(), 0, $pagesMax));
+ return $this;
+ }
+
+ // Reverse page collection
+ function reverse()
+ {
+ $this->exchangeArray(array_reverse($this->getArrayCopy()));
+ return $this;
+ }
+
+ // Randomize page collection
+ function shuffle()
+ {
+ $array = $this->getArrayCopy();
+ shuffle($array);
+ $this->exchangeArray($array);
+ return $this;
+ }
+
+ // Paginate page collection
+ function pagination($limit, $reverse = true)
+ {
+ $this->paginationNumber = 1;
+ $this->paginationCount = ceil($this->count() / $limit);
+ $pagination = $this->yellow->config->get("contentPagination");
+ if(isset($_REQUEST[$pagination])) $this->paginationNumber = intval($_REQUEST[$pagination]);
+ 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 $this;
+ }
+
+ // Return current page number in pagination
+ function getPaginationNumber()
+ {
+ return $this->paginationNumber;
+ }
+
+ // Return highest page number in pagination
+ function getPaginationCount()
+ {
+ return $this->paginationCount;
+ }
+
+ // Return absolute location for a page in pagination
+ function getPaginationLocation($pageNumber)
+ {
+ if($pageNumber>=1 && $pageNumber<=$this->paginationCount)
+ {
+ $pagination = $this->yellow->config->get("contentPagination");
+ $location = $this->yellow->page->getLocation();
+ $locationArgs = $this->yellow->toolbox->getLocationArgsNew(
+ $pageNumber>1 ? "$pagination:$pageNumber" : "$pagination:", $pagination);
+ }
+ return $location.$locationArgs;
+ }
+
+ // Return absolute location for previous page in pagination
+ function getPaginationPrevious()
+ {
+ $pageNumber = $this->paginationNumber;
+ $pageNumber = ($pageNumber>1 && $pageNumber<=$this->paginationCount) ? $pageNumber-1 : 0;
+ return $this->getPaginationLocation($pageNumber);
+ }
+
+ // Return absolute location for next page in pagination
+ function getPaginationNext()
+ {
+ $pageNumber = $this->paginationNumber;
+ $pageNumber = ($pageNumber>=1 && $pageNumber<$this->paginationCount) ? $pageNumber+1 : 0;
+ return $this->getPaginationLocation($pageNumber);
+ }
+
+ // Return current page filter
+ function getFilter()
+ {
+ return $this->filterValue;
+ }
+
+ // Return page collection modification date, Unix time or HTTP format
+ 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 there is a pagination
+ function isPagination()
+ {
+ return $this->paginationCount > 1;
+ }
+}
+
+// Yellow pages
+class YellowPages
+{
+ var $yellow; //access to API
+ var $pages; //scanned pages
+ var $requestHandler; //request handler name
+ var $snippetArgs; //requested snippet arguments
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->pages = array();
+ }
+
+ // Scan file system on demand
+ function scanLocation($location)
+ {
+ if(is_null($this->pages[$location]))
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowPages::scanLocation location:$location<br/>\n";
+ $this->pages[$location] = array();
+ $serverScheme = $this->yellow->page->serverScheme;
+ $serverName = $this->yellow->page->serverName;
+ $base = $this->yellow->page->base;
+ if(empty($location))
+ {
+ $rootLocations = $this->yellow->lookup->findRootLocations();
+ foreach($rootLocations as $rootLocation)
+ {
+ list($rootLocation, $fileName) = explode(' ', $rootLocation, 2);
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($serverScheme, $serverName, $base, $rootLocation, $fileName);
+ $page->parseData("", false, 0);
+ array_push($this->pages[$location], $page);
+ }
+ } else {
+ $fileNames = $this->yellow->lookup->findChildrenFromLocation($location);
+ foreach($fileNames as $fileName)
+ {
+ $fileHandle = @fopen($fileName, "r");
+ if($fileHandle)
+ {
+ $fileData = fread($fileHandle, 4096);
+ $statusCode = filesize($fileName) <= 4096 ? 200 : 0;
+ fclose($fileHandle);
+ } else {
+ $fileData = "";
+ $statusCode = 0;
+ }
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($serverScheme, $serverName, $base,
+ $this->yellow->lookup->findLocationFromFile($fileName), $fileName);
+ $page->parseData($fileData, false, $statusCode);
+ array_push($this->pages[$location], $page);
+ }
+ }
+ }
+ return $this->pages[$location];
+ }
+
+ // Return page from file system, NULL if not found
+ function find($location, $absoluteLocation = 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;
+ }
+
+ // Return page collection with all pages
+ function index($showInvisible = false, $multiLanguage = false, $levelMax = 0)
+ {
+ $rootLocation = $multiLanguage ? "" : $this->getRootLocation($this->yellow->page->location);
+ return $this->getChildrenRecursive($rootLocation, $showInvisible, $levelMax);
+ }
+
+ // Return page collection with top-level navigation
+ function top($showInvisible = false)
+ {
+ $rootLocation = $this->getRootLocation($this->yellow->page->location);
+ return $this->getChildren($rootLocation, $showInvisible);
+ }
+
+ // Return page collection with path ancestry
+ 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);
+ }
+ return $pages;
+ }
+
+ // Return page collection with multiple languages
+ 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);
+ }
+ }
+ }
+ return $pages;
+ }
+
+ // Return page collection that's empty
+ function clean()
+ {
+ return new YellowPageCollection($this->yellow);
+ }
+
+ // Return available languages
+ function getLanguages($showInvisible = false)
+ {
+ $languages = array();
+ foreach($this->scanLocation("") as $page)
+ {
+ if($page->isAvailable() && ($page->isVisible() || $showInvisible))
+ {
+ array_push($languages, $page->get("language"));
+ }
+ }
+ return $languages;
+ }
+
+ // Return child pages
+ 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)) $pages->append($page);
+ }
+ }
+ return $pages;
+ }
+
+ // Return child pages recursively
+ 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)) $pages->append($page);
+ if(!$this->yellow->lookup->isFileLocation($page->location) && $levelMax!=0)
+ {
+ $pages->merge($this->getChildrenRecursive($page->location, $showInvisible, $levelMax));
+ }
+ }
+ }
+ return $pages;
+ }
+
+ // Return root location
+ function getRootLocation($location)
+ {
+ $rootLocation = "root/";
+ if($this->yellow->config->get("multiLanguageMode"))
+ {
+ 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 home location
+ function getHomeLocation($location)
+ {
+ return substru($this->getRootLocation($location), 4);
+ }
+
+ // Return parent location
+ 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 top-level location
+ 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;
+ }
+}
+
+// Yellow files
+class YellowFiles
+{
+ var $yellow; //access to API
+ var $files; //scanned files
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->files = array();
+ }
+
+ // Scan file system on demand
+ function scanLocation($location)
+ {
+ if(is_null($this->files[$location]))
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowFiles::scanLocation location:$location<br/>\n";
+ $this->files[$location] = array();
+ $serverScheme = $this->yellow->page->serverScheme;
+ $serverName = $this->yellow->page->serverName;
+ $base = $this->yellow->config->get("serverBase");
+ if(empty($location))
+ {
+ $fileNames = array($this->yellow->config->get("mediaDir"));
+ } 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);
+ }
+ }
+ foreach($fileNames as $fileName)
+ {
+ $file = new YellowPage($this->yellow);
+ $file->setRequestInformation($serverScheme, $serverName, $base, "/".$fileName, $fileName);
+ $file->parseData(NULL, false, 0);
+ array_push($this->files[$location], $file);
+ }
+ }
+ return $this->files[$location];
+ }
+
+ // Return page with media file information, NULL if not found
+ function find($location, $absoluteLocation = false)
+ {
+ if($absoluteLocation) $location = substru($location, strlenu($this->yellow->page->base));
+ foreach($this->scanLocation($this->getParentLocation($location)) as $file)
+ {
+ if($file->location == $location) { $found = true; break; }
+ }
+ return $found ? $file : NULL;
+ }
+
+ // Return page collection with all media files
+ function index($showInvisible = false, $multiPass = false, $levelMax = 0)
+ {
+ return $this->getChildrenRecursive("", $showInvisible, $levelMax);
+ }
+
+ // Return page collection that's empty
+ function clean()
+ {
+ return new YellowPageCollection($this->yellow);
+ }
+
+ // Return child files
+ function getChildren($location, $showInvisible = false)
+ {
+ $files = new YellowPageCollection($this->yellow);
+ foreach($this->scanLocation($location) as $file)
+ {
+ if($file->isAvailable() && ($file->isVisible() || $showInvisible))
+ {
+ $files->append($file);
+ }
+ }
+ return $files;
+ }
+
+ // Return child files recursively
+ 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))
+ {
+ $files->append($file);
+ if(!$this->yellow->lookup->isFileLocation($file->location) && $levelMax!=0)
+ {
+ $files->merge($this->getChildrenRecursive($file->location, $showInvisible, $levelMax));
+ }
+ }
+ }
+ return $files;
+ }
+
+ // Return home location
+ function getHomeLocation($location)
+ {
+ return "/".$this->yellow->config->get("mediaDir");
+ }
+
+ // Return parent location
+ function getParentLocation($location)
+ {
+ $token = rtrim("/".$this->yellow->config->get("mediaDir"), '/');
+ if(preg_match("#^($token.*\/).+?$#", $location, $matches))
+ {
+ if($matches[1]!="$token/" || $this->yellow->lookup->isFileLocation($location)) $parentLocation = $matches[1];
+ }
+ if(empty($parentLocation)) $parentLocation = "";
+ return $parentLocation;
+ }
+
+ // Return top-level location
+ function getParentTopLocation($location)
+ {
+ $token = rtrim("/".$this->yellow->config->get("mediaDir"), '/');
+ if(preg_match("#^($token.+?\/)#", $location, $matches)) $parentTopLocation = $matches[1];
+ if(empty($parentTopLocation)) $parentTopLocation = "$token/";
+ return $parentTopLocation;
+ }
+}
+
+// Yellow plugins
+class YellowPlugins
+{
+ var $yellow; //access to API
+ var $plugins; //registered plugins
+ var $modified; //plugin modification date
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->plugins = array();
+ $this->modified = 0;
+ }
+
+ // Load plugins
+ function load()
+ {
+ $path = $this->yellow->config->get("pluginDir");
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, "/^.*\.php$/", true, false) as $entry)
+ {
+ $this->modified = max($this->modified, filemtime($entry));
+ global $yellow;
+ require_once($entry);
+ }
+ foreach($this->plugins as $key=>$value)
+ {
+ $this->plugins[$key]["obj"] = new $value["class"];
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowPlugins::load class:$value[class] $value[version]<br/>\n";
+ if(method_exists($this->plugins[$key]["obj"], "onLoad")) $this->plugins[$key]["obj"]->onLoad($yellow);
+ }
+ }
+
+ // Register plugin
+ function register($name, $class, $version)
+ {
+ if(!$this->isExisting($name))
+ {
+ $this->plugins[$name] = array();
+ $this->plugins[$name]["class"] = $class;
+ $this->plugins[$name]["version"] = $version;
+ }
+ }
+
+ // Return plugin
+ function get($name)
+ {
+ return $this->plugins[$name]["obj"];
+ }
+
+ // Return plugins modification date, Unix time or HTTP format
+ function getModified($httpFormat = false)
+ {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
+ }
+
+ // Check if plugin exists
+ function isExisting($name)
+ {
+ return !is_null($this->plugins[$name]);
+ }
+}
+
+// Yellow configuration
+class YellowConfig
+{
+ var $yellow; //access to API
+ var $modified; //configuration modification date
+ var $config; //configuration
+ var $configDefaults; //configuration defaults
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->modified = 0;
+ $this->config = array();
+ $this->configDefaults = array();
+ }
+
+ // Load configuration from file
+ function load($fileName)
+ {
+ $fileData = @file($fileName);
+ if($fileData)
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowConfig::load file:$fileName<br/>\n";
+ $this->modified = filemtime($fileName);
+ foreach($fileData as $line)
+ {
+ if(preg_match("/^\#/", $line)) continue;
+ preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
+ if(!empty($matches[1]) && !strempty($matches[2]))
+ {
+ $this->set(lcfirst($matches[1]), $matches[2]);
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowConfig::load ".lcfirst($matches[1]).":$matches[2]<br/>\n";
+ }
+ }
+ }
+ }
+
+ // Set default configuration
+ function setDefault($key, $value)
+ {
+ $this->configDefaults[$key] = $value;
+ }
+
+ // Set configuration
+ function set($key, $value)
+ {
+ $this->config[$key] = $value;
+ }
+
+ // Return configuration
+ function get($key)
+ {
+ if(!is_null($this->config[$key]))
+ {
+ $value = $this->config[$key];
+ } else {
+ $value = !is_null($this->configDefaults[$key]) ? $this->configDefaults[$key] : "";
+ }
+ return $value;
+ }
+
+ // Return configuration, HTML encoded
+ function getHtml($key)
+ {
+ return htmlspecialchars($this->get($key));
+ }
+
+ // Return configuration strings
+ function getData($filterStart = "", $filterEnd = "")
+ {
+ $config = array();
+ if(empty($filterStart) && empty($filterEnd))
+ {
+ $config = array_merge($this->configDefaults, $this->config);
+ } else {
+ foreach(array_merge($this->configDefaults, $this->config) as $key=>$value)
+ {
+ if(!empty($filterStart) && substru($key, 0, strlenu($filterStart))==$filterStart) $config[$key] = $value;
+ if(!empty($filterEnd) && substru($key, -strlenu($filterEnd))==$filterEnd) $config[$key] = $value;
+ }
+ }
+ return $config;
+ }
+
+ // Return configuration modification date, Unix time or HTTP format
+ function getModified($httpFormat = false)
+ {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
+ }
+
+ // Check if configuration exists
+ function isExisting($key)
+ {
+ return !is_null($this->config[$key]);
+ }
+}
+
+// Yellow text strings
+class YellowText
+{
+ var $yellow; //access to API
+ var $modified; //text modification date
+ var $text; //text strings
+ var $language; //current language
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->modified = 0;
+ $this->text = array();
+ }
+
+ // Load text strings from file
+ function load($fileName)
+ {
+ $path = dirname($fileName);
+ $regex = "/^".basename($fileName)."$/";
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false) as $entry)
+ {
+ $fileData = @file($entry);
+ if($fileData)
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowText::load file:$entry<br/>\n";
+ $this->modified = max($this->modified, filemtime($entry));
+ $language = "";
+ foreach($fileData as $line)
+ {
+ preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
+ if(lcfirst($matches[1])=="language" && !strempty($matches[2])) { $language = $matches[2]; break; }
+ }
+ foreach($fileData as $line)
+ {
+ if(preg_match("/^\#/", $line)) continue;
+ preg_match("/^\s*(.*?)\s*:\s*(.*?)\s*$/", $line, $matches);
+ if(!empty($language) && !empty($matches[1]) && !strempty($matches[2]))
+ {
+ $this->setText(lcfirst($matches[1]), $matches[2], $language);
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowText::load ".lcfirst($matches[1]).":$matches[2]<br/>\n";
+ }
+ }
+ }
+ }
+ }
+
+ // Set current language
+ function setLanguage($language)
+ {
+ $this->language = $language;
+ }
+
+ // Set text string for specific language
+ function setText($key, $value, $language)
+ {
+ if(is_null($this->text[$language])) $this->text[$language] = array();
+ $this->text[$language][$key] = $value;
+ }
+
+ // Return text string for specific language
+ function getText($key, $language)
+ {
+ return $this->isExisting($key, $language) ? $this->text[$language][$key] : "[$key]";
+ }
+
+ // Return text string for specific language, HTML encoded
+ function getTextHtml($key, $language)
+ {
+ return htmlspecialchars($this->getText($key, $language));
+ }
+
+ // Return text string
+ function get($key)
+ {
+ return $this->getText($key, $this->language);
+ }
+
+ // Return text string, HTML encoded
+ function getHtml($key)
+ {
+ return htmlspecialchars($this->getText($key, $this->language));
+ }
+
+ // Return text strings
+ 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("language")) == "language") $text[$key] = $value;
+ if(substru($key, 0, strlenu($filterStart)) == $filterStart) $text[$key] = $value;
+ }
+ }
+ }
+ return $text;
+ }
+
+ // Return text string with human readable date, custom date format
+ function getDateFormatted($timestamp, $format)
+ {
+ $dateMonths = preg_split("/,\s*/", $this->get("dateMonths"));
+ $dateWeekdays = preg_split("/,\s*/", $this->get("dateWeekdays"));
+ $month = $dateMonths[date('n', $timestamp) - 1];
+ $weekday = $dateWeekdays[date('N', $timestamp) - 1];
+ $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);
+ return date($format, $timestamp);
+ }
+
+ // Return text modification date, Unix time or HTTP format
+ function getModified($httpFormat = false)
+ {
+ return $httpFormat ? $this->yellow->toolbox->getHttpDateFormatted($this->modified) : $this->modified;
+ }
+
+ // Normalise date into known format
+ function normaliseDate($text)
+ {
+ if(preg_match("/^\d+\-\d+$/", $text))
+ {
+ $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatShort"));
+ } else if(preg_match("/^\d+\-\d+\-\d+$/", $text)) {
+ $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatMedium"));
+ } else if(preg_match("/^\d+\-\d+\-\d+ \d+\:\d+$/", $text)) {
+ $output = $this->getDateFormatted(strtotime($text), $this->get("dateFormatLong"));
+ } else {
+ $output = $text;
+ }
+ return $output;
+ }
+
+ // Check if language exists
+ function isLanguage($language)
+ {
+ return !is_null($this->text[$language]);
+ }
+
+ // Check if text string exists
+ function isExisting($key, $language = "")
+ {
+ if(empty($language)) $language = $this->language;
+ return !is_null($this->text[$language]) && !is_null($this->text[$language][$key]);
+ }
+}
+
+// Yellow location and file lookup
+class YellowLookup
+{
+ var $yellow; //access to API
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ }
+
+ // Return root locations
+ function findRootLocations($includePath = true)
+ {
+ $locations = array();
+ $pathBase = $this->yellow->config->get("contentDir");
+ $pathRoot = $this->yellow->config->get("contentRootDir");
+ if(!empty($pathRoot))
+ {
+ foreach($this->yellow->toolbox->getDirectoryEntries($pathBase, "/.*/", true, true, false) as $entry)
+ {
+ $token = $this->normaliseName($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";
+ }
+ } else {
+ array_push($locations, $includePath ? "root/ $pathBase" : "root/");
+ }
+ return $locations;
+ }
+
+ // Return location from file path
+ function findLocationFromFile($fileName)
+ {
+ $location = "/";
+ $pathBase = $this->yellow->config->get("contentDir");
+ $pathRoot = $this->yellow->config->get("contentRootDir");
+ $pathHome = $this->yellow->config->get("contentHomeDir");
+ $fileDefault = $this->yellow->config->get("contentDefaultFile");
+ $fileExtension = $this->yellow->config->get("contentExtension");
+ if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
+ {
+ $fileName = substru($fileName, strlenu($pathBase));
+ $tokens = explode('/', $fileName);
+ if(!empty($pathRoot))
+ {
+ $token = $this->normaliseName($tokens[0]).'/';
+ if($token!=$pathRoot) $location .= $token;
+ array_shift($tokens);
+ }
+ for($i=0; $i<count($tokens)-1; ++$i)
+ {
+ $token = $this->normaliseName($tokens[$i]).'/';
+ if($i || $token!=$pathHome) $location .= $token;
+ }
+ $token = $this->normaliseName($tokens[$i]);
+ $fileFolder = $this->normaliseName($tokens[$i-1]).$fileExtension;
+ if($token!=$fileDefault && $token!=$fileFolder) $location .= $this->normaliseName($tokens[$i], true, true);
+ $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";
+ }
+ return $invalid ? "" : $location;
+ }
+
+ // Return file path from location
+ function findFileFromLocation($location, $directory = false)
+ {
+ $path = $this->yellow->config->get("contentDir");
+ $pathRoot = $this->yellow->config->get("contentRootDir");
+ $pathHome = $this->yellow->config->get("contentHomeDir");
+ $fileDefault = $this->yellow->config->get("contentDefaultFile");
+ $fileExtension = $this->yellow->config->get("contentExtension");
+ $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->normaliseName($tokens[1]) == $this->normaliseName($pathRoot)) $invalid = true;
+ $path .= $this->findFileDirectory($path, $tokens[1], false, true, $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->normaliseName($tokens[1]) == $this->normaliseName($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)
+ {
+ $fileFolder = $tokens[$i-1].$fileExtension;
+ if(!empty($tokens[$i]))
+ {
+ $token = $tokens[$i].$fileExtension;
+ if($token==$fileDefault || $token==$fileFolder) $invalid = true;
+ $path .= $this->findFileDirectory($path, $token, true, false, $found, $invalid);
+ } else {
+ $path .= $this->findFileDefault($path, $fileDefault, $fileFolder);
+ }
+ if(defined("DEBUG") && DEBUG>=2)
+ {
+ $debug = "$location -> ".($invalid ? "INVALID" : $path);
+ echo "YellowLookup::findFileFromLocation $debug<br/>\n";
+ }
+ }
+ }
+ return $invalid ? "" : $path;
+ }
+
+ // Return file or directory that matches token
+ function findFileDirectory($path, $token, $tokenFailback, $directory, &$found, &$invalid)
+ {
+ if($this->normaliseName($token) != $token) $invalid = true;
+ if(!$invalid)
+ {
+ $regex = "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, false, $directory, false) as $entry)
+ {
+ if($this->normaliseName($entry) == $token) { $token = $entry; $found = true; break; }
+ }
+ }
+ if($directory) $token .= '/';
+ return ($tokenFailback || $found) ? $token : "";
+ }
+
+ // Return default file in directory
+ function findFileDefault($path, $fileDefault, $fileFolder)
+ {
+ $token = $fileDefault;
+ if(!is_file($path."/".$fileDefault))
+ {
+ $regex = "/^[\d\-\_\.]*($fileDefault|$fileFolder)$/";
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry)
+ {
+ if($this->normaliseName($entry) == $fileDefault) { $token = $entry; break; }
+ if($this->normaliseName($entry) == $fileFolder) { $token = $entry; break; }
+ }
+ }
+ return $token;
+ }
+
+ // Return children from location
+ function findChildrenFromLocation($location)
+ {
+ $fileNames = array();
+ $fileDefault = $this->yellow->config->get("contentDefaultFile");
+ $fileExtension = $this->yellow->config->get("contentExtension");
+ if(!$this->isFileLocation($location))
+ {
+ $path = $this->findFileFromLocation($location, true);
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, "/.*/", true, true, false) as $entry)
+ {
+ $fileFolder = $this->normaliseName($entry).$fileExtension;
+ $token = $this->findFileDefault($path.$entry, $fileDefault, $fileFolder);
+ array_push($fileNames, $path.$entry."/".$token);
+ }
+ if(!$this->isRootLocation($location))
+ {
+ $fileFolder = $this->normaliseName(basename($path)).$fileExtension;
+ $regex = "/^.*\\".$fileExtension."$/";
+ foreach($this->yellow->toolbox->getDirectoryEntries($path, $regex, true, false, false) as $entry)
+ {
+ if($this->normaliseName($entry) == $fileDefault) continue;
+ if($this->normaliseName($entry) == $fileFolder) continue;
+ if($this->normaliseName($entry, true, true) == "") continue;
+ array_push($fileNames, $path.$entry);
+ }
+ }
+ }
+ return $fileNames;
+ }
+
+ // Return language from file path
+ function findLanguageFromFile($fileName, $languageDefault)
+ {
+ $language = $languageDefault;
+ $pathBase = $this->yellow->config->get("contentDir");
+ $pathRoot = $this->yellow->config->get("contentRootDir");
+ if(!empty($pathRoot))
+ {
+ $fileName = substru($fileName, strlenu($pathBase));
+ if(preg_match("/^(.+?)\//", $fileName, $matches)) $name = $this->normaliseName($matches[1]);
+ if(strlenu($name) == 2) $language = $name;
+ }
+ return $language;
+ }
+
+ // Return theme/template name from file path
+ function findNameFromFile($fileName, $pathBase, $nameDefault, $fileExtension)
+ {
+ $name = "";
+ if(preg_match("/^.*\/(.+?)$/", dirname($fileName), $matches)) $name = $this->normaliseName($matches[1]);
+ if(!is_file("$pathBase$name$fileExtension")) $name = $this->normaliseName($nameDefault);
+ return $name;
+ }
+
+ // Return file path for new page
+ function findFileNew($fileName, $fileNew, $pathBase, $nameDefault)
+ {
+ if(preg_match("/^.*\/(.+?)$/", dirname($fileName), $matches)) $name = $this->normaliseName($matches[1]);
+ $fileName = strreplaceu("(.*)", $name, $pathBase.$fileNew);
+ if(!is_file($fileName))
+ {
+ $name = $this->normaliseName($nameDefault);
+ $fileName = strreplaceu("(.*)", $name, $pathBase.$fileNew);
+ }
+ return $fileName;
+ }
+
+ // Return file path from title
+ function findFileFromTitle($titlePrefix, $titleText, $fileName, $fileDefault, $fileExtension)
+ {
+ preg_match("/^([\d\-\_\.]*)(.*)$/", $titlePrefix, $matches);
+ if(preg_match("/\d$/", $matches[1])) $matches[1] .= '-';
+ $titleText = $this->normaliseName($titleText, false, false, true);
+ preg_match("/^([\d\-\_\.]*)(.*)$/", $matches[1].$titleText, $matches);
+ $fileNamePrefix = $matches[1];
+ $fileNameText = empty($matches[2]) ? $fileDefault : $matches[2].$fileExtension;
+ return dirname($fileName)."/".$fileNamePrefix.$fileNameText;
+ }
+
+ // Normalise file/directory/other name
+ function normaliseName($text, $removePrefix = true, $removeExtension = false, $filterStrict = false)
+ {
+ if($removeExtension) $text = ($pos = strrposu($text, '.')) ? substru($text, 0, $pos) : $text;
+ if($removePrefix) if(preg_match("/^[\d\-\_\.]+(.*)$/", $text, $matches)) $text = $matches[1];
+ if($filterStrict) $text = strreplaceu('.', '-', strtoloweru($text));
+ return preg_replace("/[^\pL\d\-\_\.]/u", "-", rtrim($text, '/'));
+ }
+
+ // Normalise content/media file name
+ function normaliseFile($fileName, $convertExtension = false)
+ {
+ $fileName = basename($fileName);
+ if($convertExtension)
+ {
+ $fileName = ($pos = strposu($fileName, '.')) ? substru($fileName, 0, $pos) : $fileName;
+ $fileName .= $this->yellow->config->get("contentExtension");
+ }
+ return $fileName;
+ }
+
+ // Normalise location, make absolute location
+ function normaliseLocation($location, $pageBase, $pageLocation, $staticLocation = "", $filterStrict = true)
+ {
+ if(!preg_match("/^\w+:/", trim(html_entity_decode($location, ENT_QUOTES, "UTF-8"))))
+ {
+ if(empty($staticLocation) || !preg_match("#^$staticLocation#", $location))
+ {
+ if(preg_match("/^\#/", $location))
+ {
+ $location = $pageBase.$pageLocation.$location;
+ } else if(!preg_match("/^\//", $location)) {
+ $location = $this->getDirectoryLocation($pageBase.$pageLocation).$location;
+ } else if(!preg_match("#^$pageBase#", $location)) {
+ $location = $pageBase.$location;
+ }
+ }
+ } else {
+ if($filterStrict && !preg_match("/^(http|https|ftp|mailto):/", $location)) $location = "error-xss-filter";
+ }
+ return $location;
+ }
+
+ // Normalise URL, make absolute URL
+ function normaliseUrl($serverScheme, $serverName, $base, $location)
+ {
+ if(!preg_match("/^\w+:/", $location))
+ {
+ $url = "$serverScheme://$serverName$base$location";
+ } else {
+ $url = $location;
+ }
+ return $url;
+ }
+
+ // Return content information
+ function getContentInformation()
+ {
+ $path = $this->yellow->config->get("contentDir");
+ $pathRoot = $this->yellow->config->get("contentRootDir");
+ $pathHome = $this->yellow->config->get("contentHomeDir");
+ if(!$this->yellow->config->get("multiLanguageMode")) $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->normaliseName($entry) == $root) { $token = $entry; break; }
+ }
+ $pathRoot = $this->normaliseName($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->normaliseName($entry) == $home) { $token = $entry; break; }
+ }
+ $pathHome = $this->normaliseName($token)."/";
+ }
+ return array($pathRoot, $pathHome);
+ }
+
+ // Return directory location
+ function getDirectoryLocation($location)
+ {
+ return ($pos = strrposu($location, '/')) ? substru($location, 0, $pos+1) : "/";
+ }
+
+ // Check if location is specifying root
+ function isRootLocation($location)
+ {
+ return $location[0] != "/";
+ }
+
+ // Check if location is specifying file or directory
+ function isFileLocation($location)
+ {
+ return substru($location, -1, 1) != "/";
+ }
+
+ // Check if location is visible
+ function isVisibleLocation($location, $fileName)
+ {
+ $visible = true;
+ $pathBase = $this->yellow->config->get("contentDir");
+ if(substru($fileName, 0, strlenu($pathBase)) == $pathBase)
+ {
+ $fileName = substru($fileName, strlenu($pathBase));
+ $tokens = explode('/', $fileName);
+ for($i=0; $i<count($tokens)-1; ++$i)
+ {
+ if(!preg_match("/^[\d\-\_\.]+(.*)$/", $tokens[$i])) { $visible = false; break; }
+ }
+ } else {
+ $visible = false;
+ }
+ return $visible;
+ }
+
+ // Check if location is within current request
+ function isActiveLocation($location, $currentLocation)
+ {
+ if($this->isFileLocation($location))
+ {
+ $active = $currentLocation==$location;
+ } else {
+ if($location == $this->yellow->pages->getHomeLocation($location))
+ {
+ $active = $this->getDirectoryLocation($currentLocation)==$location;
+ } else {
+ $active = substru($currentLocation, 0, strlenu($location))==$location;
+ }
+ }
+ return $active;
+ }
+
+ // Check if location is valid
+ function isValidLocation($location)
+ {
+ $string = "";
+ $tokens = explode('/', $location);
+ for($i=1; $i<count($tokens); ++$i) $string .= '/'.$this->normaliseName($tokens[$i]);
+ return $location == $string;
+ }
+}
+
+// Yellow toolbox with helpers
+class YellowToolbox
+{
+ // Return server software from current HTTP request
+ function getServerSoftware()
+ {
+ $serverSoftware = PHP_SAPI;
+ if(preg_match("/^(\S+)/", $_SERVER["SERVER_SOFTWARE"], $matches)) $serverSoftware = $matches[1];
+ return $serverSoftware." ".PHP_OS;
+ }
+
+ // Return server scheme from current HTTP request
+ function getServerScheme()
+ {
+ $serverScheme = "";
+ if(preg_match("/^HTTP\//", $_SERVER["SERVER_PROTOCOL"]))
+ {
+ $secure = isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"]!="off";
+ $serverScheme = $secure ? "https" : "http";
+ }
+ return $serverScheme;
+ }
+
+ // Return server name from current HTTP request
+ function getServerName()
+ {
+ return $_SERVER["SERVER_NAME"];
+ }
+
+ // Return server base from current HTTP request
+ function getServerBase()
+ {
+ $serverBase = "";
+ if(preg_match("/^(.*)\//", $_SERVER["SCRIPT_NAME"], $matches)) $serverBase = $matches[1];
+ return $serverBase;
+ }
+
+ // Return location from current HTTP request
+ function getLocation()
+ {
+ $uri = $_SERVER["REQUEST_URI"];
+ return rawurldecode(($pos = strposu($uri, '?')) ? substru($uri, 0, $pos) : $uri);
+ }
+
+ // Return location from current HTTP request, remove unwanted path tokens
+ function getLocationClean()
+ {
+ $string = $this->getLocation();
+ $location = ($string[0]=='/') ? '' : '/';
+ for($pos=0; $pos<strlenb($string); ++$pos)
+ {
+ if($string[$pos] == '/')
+ {
+ if($string[$pos+1] == '/') continue;
+ if($string[$pos+1] == '.')
+ {
+ $posNew = $pos+1; while($string[$posNew] == '.') ++$posNew;
+ if($string[$posNew]=='/' || $string[$posNew]=='')
+ {
+ $pos = $posNew-1;
+ continue;
+ }
+ }
+ }
+ $location .= $string[$pos];
+ }
+ if(preg_match("/^(.*?\/)([^\/]+:.*)$/", $location, $matches))
+ {
+ $_SERVER["LOCATION"] = $location = $matches[1];
+ $_SERVER["LOCATION_ARGS"] = $matches[2];
+ foreach(explode('/', $matches[2]) as $token)
+ {
+ preg_match("/^(.*?):(.*)$/", $token, $matches);
+ if(!empty($matches[1]) && !strempty($matches[2]))
+ {
+ $matches[1] = strreplaceu(array("\x1c", "\x1d"), array('/', ':'), $matches[1]);
+ $matches[2] = strreplaceu(array("\x1c", "\x1d"), array('/', ':'), $matches[2]);
+ $_REQUEST[$matches[1]] = $matches[2];
+ }
+ }
+ }
+ return $location;
+ }
+
+ // Return location arguments from current HTTP request
+ function getLocationArgs()
+ {
+ return $_SERVER["LOCATION_ARGS"];
+ }
+
+ // Return location arguments from current HTTP request, modify an argument
+ function getLocationArgsNew($arg, $pagination)
+ {
+ preg_match("/^(.*?):(.*)$/", $arg, $args);
+ foreach(explode('/', $_SERVER["LOCATION_ARGS"]) as $token)
+ {
+ preg_match("/^(.*?):(.*)$/", $token, $matches);
+ if($matches[1] == $args[1]) { $matches[2] = $args[2]; $found = true; }
+ if(!empty($matches[1]) && !strempty($matches[2]))
+ {
+ if(!empty($locationArgs)) $locationArgs .= '/';
+ $locationArgs .= "$matches[1]:$matches[2]";
+ }
+ }
+ if(!$found && !empty($args[1]) && !strempty($args[2]))
+ {
+ if(!empty($locationArgs)) $locationArgs .= '/';
+ $locationArgs .= "$args[1]:$args[2]";
+ }
+ if(!empty($locationArgs))
+ {
+ if(!$this->isLocationArgsPagination($locationArgs, $pagination)) $locationArgs .= '/';
+ $locationArgs = $this->normaliseArgs($locationArgs, false, false);
+ }
+ return $locationArgs;
+ }
+
+ // Return location arguments from current HTTP request, convert form into clean URL
+ function getLocationArgsCleanUrl($pagination)
+ {
+ foreach(array_merge($_GET, $_POST) as $key=>$value)
+ {
+ if(!empty($key) && !strempty($value))
+ {
+ if(!empty($locationArgs)) $locationArgs .= '/';
+ $key = strreplaceu(array('/', ':'), array("\x1c", "\x1d"), $key);
+ $value = strreplaceu(array('/', ':'), array("\x1c", "\x1d"), $value);
+ $locationArgs .= "$key:$value";
+ }
+ }
+ if(!empty($locationArgs))
+ {
+ if(!$this->isLocationArgsPagination($locationArgs, $pagination)) $locationArgs .= '/';
+ $locationArgs = $this->normaliseArgs($locationArgs, false, false);
+ }
+ return $locationArgs;
+ }
+
+ // Check if location contains location arguments
+ function isLocationArgs($location)
+ {
+ return preg_match("/[^\/]+:.*$/", $location);
+ }
+
+ // Check if location contains pagination arguments
+ function isLocationArgsPagination($location, $pagination)
+ {
+ return preg_match("/^(.*\/)?$pagination:.*$/", $location);
+ }
+
+ // Check if clean URL is requested
+ function isRequestCleanUrl($location)
+ {
+ return (isset($_GET["clean-url"]) || isset($_POST["clean-url"])) && substru($location, -1, 1)=="/";
+ }
+
+ // Check if unmodified since last HTTP request
+ function isRequestNotModified($lastModifiedFormatted)
+ {
+ return isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) && $_SERVER["HTTP_IF_MODIFIED_SINCE"]==$lastModifiedFormatted;
+ }
+
+ // Normalise location arguments
+ function normaliseArgs($text, $appendSlash = true, $filterStrict = true)
+ {
+ if($appendSlash) $text .= '/';
+ if($filterStrict) $text = strreplaceu(' ', '-', strtoloweru($text));
+ return strreplaceu(array('%3A','%2F'), array(':','/'), rawurlencode($text));
+ }
+
+ // Normalise text into UTF-8 NFC
+ 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 $text;
+ }
+
+ // Return server time zone
+ function getServerTime()
+ {
+ $serverTime = @date_default_timezone_get();
+ if(PHP_OS=="Darwin" && $serverTime=="UTC")
+ {
+ if(preg_match("#zoneinfo/(.*)#", @readlink("/etc/localtime"), $matches)) $serverTime = $matches[1];
+ }
+ return $serverTime;
+ }
+
+ // Return human readable HTTP server status
+ function getHttpStatusFormatted($statusCode)
+ {
+ $serverProtocol = $_SERVER["SERVER_PROTOCOL"];
+ if(!preg_match("/^HTTP\//", $serverProtocol)) $serverProtocol = "HTTP/1.1";
+ switch($statusCode)
+ {
+ case 0: $text = "$serverProtocol $statusCode No data"; break;
+ case 200: $text = "$serverProtocol $statusCode OK"; break;
+ case 301: $text = "$serverProtocol $statusCode Moved permanently"; break;
+ case 302: $text = "$serverProtocol $statusCode Moved temporarily"; break;
+ case 303: $text = "$serverProtocol $statusCode Reload please"; break;
+ case 304: $text = "$serverProtocol $statusCode Not modified"; break;
+ case 400: $text = "$serverProtocol $statusCode Bad request"; break;
+ case 404: $text = "$serverProtocol $statusCode Not found"; break;
+ case 424: $text = "$serverProtocol $statusCode Not existing"; break;
+ case 444: $text = "$serverProtocol $statusCode No response"; break;
+ case 500: $text = "$serverProtocol $statusCode Server error"; break;
+ default: $text = "$serverProtocol $statusCode Unknown status";
+ }
+ return $text;
+ }
+
+ // Return human readable HTTP date
+ function getHttpDateFormatted($timestamp)
+ {
+ return gmdate("D, d M Y H:i:s", $timestamp)." GMT";
+ }
+
+ // Return MIME content type
+ function getMimeContentType($fileName)
+ {
+ $mimeTypes = array(
+ "css" => "text/css",
+ "ico" => "image/x-icon",
+ "js" => "application/javascript",
+ "jpg" => "image/jpeg",
+ "png" => "image/png",
+ "txt" => "text/plain",
+ "woff" => "application/font-woff",
+ "xml" => "text/xml; charset=utf-8");
+ $contentType = "text/html; charset=utf-8";
+ $extension = $this->getFileExtension($fileName);
+ if(array_key_exists(strtoloweru($extension), $mimeTypes)) $contentType = $mimeTypes[$extension];
+ return $contentType;
+ }
+
+ // Return files and directories
+ function getDirectoryEntries($path, $regex = "/.*/", $sort = true, $directories = true, $includePath = true)
+ {
+ $entries = array();
+ $dirHandle = @opendir($path);
+ if($dirHandle)
+ {
+ $path = rtrim($path, '/');
+ while(($entry = readdir($dirHandle)) !== 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);
+ }
+ }
+ }
+ if($sort) natsort($entries);
+ closedir($dirHandle);
+ }
+ return $entries;
+ }
+
+ // Return files and directories recursively
+ 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));
+ }
+ }
+ return $entries;
+ }
+
+ // Delete directory
+ function deleteDirectory($path, $recursive = false)
+ {
+ if($recursive)
+ {
+ $iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
+ $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST);
+ foreach($files as $file)
+ {
+ if($file->isDir())
+ {
+ @rmdir($file->getRealPath());
+ } else {
+ @unlink($file->getRealPath());
+ }
+ }
+ }
+ return @rmdir($path);
+ }
+
+ // Return file data, empty string if not found
+ function getFileData($fileName)
+ {
+ return is_readable($fileName) ? file_get_contents($fileName) : "";
+ }
+
+ // Return file extension
+ function getFileExtension($fileName)
+ {
+ return strtoloweru(($pos = strrposu($fileName, '.')) ? substru($fileName, $pos+1) : "");
+ }
+
+ // Return file modification date, Unix time
+ function getFileModified($fileName)
+ {
+ $modified = is_readable($fileName) ? filemtime($fileName) : 0;
+ if($modified == 0)
+ {
+ $path = dirname($fileName);
+ $modified = is_readable($path) ? filemtime($path) : 0;
+ }
+ return $modified;
+ }
+
+ // Create file
+ 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, "w");
+ if($fileHandle)
+ {
+ fwrite($fileHandle, $fileData);
+ fclose($fileHandle);
+ $ok = true;
+ }
+ return $ok;
+ }
+
+ // Copy file
+ function copyFile($fileNameSource, $fileNameDest, $mkdir = false)
+ {
+ if($mkdir)
+ {
+ $path = dirname($fileNameDest);
+ if(!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ }
+ return @copy($fileNameSource, $fileNameDest);
+ }
+
+ // Rename file
+ function renameFile($fileNameSource, $fileNameDest, $mkdir = false)
+ {
+ if($mkdir)
+ {
+ $path = dirname($fileNameDest);
+ if(!empty($path) && !is_dir($path)) @mkdir($path, 0777, true);
+ }
+ return @rename($fileNameSource, $fileNameDest);
+ }
+
+ // Set file modification date, Unix time
+ function modifyFile($fileName, $modified)
+ {
+ return @touch($fileName, $modified);
+ }
+
+ // Delete file
+ function deleteFile($fileName)
+ {
+ return @unlink($fileName);
+ }
+
+ // Return lines from text string
+ function getTextLines($text)
+ {
+ $lines = array();
+ $split = preg_split("/(\R)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ for($i=0; $i<count($split)-1; $i+=2) array_push($lines, $split[$i].$split[$i+1]);
+ if($split[$i] != '') array_push($lines, $split[$i]);
+ return $lines;
+ }
+
+ // Return arguments from text string
+ function getTextArgs($text, $optional = "-")
+ {
+ $text = preg_replace("/\s+/s", " ", trim($text));
+ $tokens = str_getcsv($text, ' ', '"');
+ foreach($tokens as $key=>$value) if($value == $optional) $tokens[$key] = "";
+ return $tokens;
+ }
+
+ // Create description from text string
+ function createTextDescription($text, $lengthMax, $removeHtml = true, $endMarker = "", $endMarkerText = "")
+ {
+ if(preg_match("/^<h1>.*?<\/h1>(.*)$/si", $text, $matches)) $text = $matches[1];
+ if($removeHtml)
+ {
+ while(true)
+ {
+ $elementFound = preg_match("/<\s*?([\/!]?\w*)(.*?)\s*?\>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
+ $element = $matches[0][0];
+ $elementName = $matches[1][0];
+ $elementText = $matches[2][0];
+ $elementOffsetBytes = $elementFound ? $matches[0][1] : strlenb($text);
+ $string = html_entity_decode(substrb($text, $offsetBytes, $elementOffsetBytes - $offsetBytes), ENT_QUOTES, "UTF-8");
+ if(preg_match("/^(blockquote|br|div|h\d|hr|li|ol|p|pre|ul)/i", $elementName)) $string .= ' ';
+ if(preg_match("/^\/(code|pre)/i", $elementName)) $string = preg_replace("/^(\d+\n){2,}$/", "", $string);
+ $string = preg_replace("/\s+/s", " ", $string);
+ if(substru($string, 0, 1)==" " && (empty($output) || substru($output, -1)==' ')) $string = substru($string, 1);
+ $length = strlenu($string);
+ $output .= substru($string, 0, $length < $lengthMax ? $length : $lengthMax-1);
+ $lengthMax -= $length;
+ if(!empty($element) && $element==$endMarker) { $lengthMax = 0; $endMarkerFound = true; }
+ if($lengthMax<=0 || !$elementFound) break;
+ $offsetBytes = $elementOffsetBytes + strlenb($element);
+ }
+ $output = rtrim($output);
+ if($lengthMax <= 0) $output .= $endMarkerFound ? $endMarkerText : "…";
+ } else {
+ $elementsOpen = array();
+ while(true)
+ {
+ $elementFound = preg_match("/&.*?\;|<\s*?([\/!]?\w*)(.*?)\s*?\>/s", $text, $matches, PREG_OFFSET_CAPTURE, $offsetBytes);
+ $element = $matches[0][0];
+ $elementName = $matches[1][0];
+ $elementText = $matches[2][0];
+ $elementOffsetBytes = $elementFound ? $matches[0][1] : strlenb($text);
+ $string = substrb($text, $offsetBytes, $elementOffsetBytes - $offsetBytes);
+ $length = strlenu($string);
+ $output .= substru($string, 0, $length < $lengthMax ? $length : $lengthMax-1);
+ $lengthMax -= $length + ($element[0]=='&' ? 1 : 0);
+ if(!empty($element) && $element==$endMarker) { $lengthMax = 0; $endMarkerFound = true; }
+ if($lengthMax<=0 || !$elementFound) break;
+ if(!empty($elementName) && substru($elementText, -1)!='/' &&
+ !preg_match("/^(area|br|col|hr|img|input|col|param|!)/i", $elementName))
+ {
+ if($elementName[0] != '/')
+ {
+ array_push($elementsOpen, $elementName);
+ } else {
+ array_pop($elementsOpen);
+ }
+ }
+ $output .= $element;
+ $offsetBytes = $elementOffsetBytes + strlenb($element);
+ }
+ $output = rtrim($output);
+ for($i=count($elementsOpen)-1; $i>=0; --$i)
+ {
+ if(!preg_match("/^(dl|ol|ul|table|tbody|thead|tfoot|tr)/i", $elementsOpen[$i])) break;
+ $output .= "</".$elementsOpen[$i].">";
+ }
+ if($lengthMax <= 0) $output .= $endMarkerFound ? $endMarkerText : "…";
+ for(; $i>=0; --$i) $output .= "</".$elementsOpen[$i].">";
+ }
+ return $output;
+ }
+
+ // Create keywords from text string
+ function createTextKeywords($text, $keywordsMax = 0)
+ {
+ $tokens = array_unique(preg_split("/[,\s\(\)\+\-]/", strtoloweru($text)));
+ foreach($tokens as $key=>$value) if(strlenu($value) < 3) unset($tokens[$key]);
+ if($keywordsMax) $tokens = array_slice($tokens, 0, $keywordsMax);
+ return implode(", ", $tokens);
+ }
+
+ // Create title from text string
+ function createTextTitle($text)
+ {
+ if(preg_match("/^.*\/([\w\-]+)/", $text, $matches)) $text = strreplaceu('-', ' ', ucfirst($matches[1]));
+ return $text;
+ }
+
+ // Create random text for cryptography
+ function createSalt($length, $bcryptFormat = false)
+ {
+ $dataBuffer = $salt = "";
+ $dataBufferSize = $bcryptFormat ? intval(ceil($length/4) * 3) : intval(ceil($length/2));
+ if(empty($dataBuffer) && function_exists("mcrypt_create_iv"))
+ {
+ $dataBuffer = @mcrypt_create_iv($dataBufferSize, MCRYPT_DEV_URANDOM);
+ }
+ if(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 {
+ $salt = substrb(bin2hex($dataBuffer), 0, $length);
+ }
+ }
+ return $salt;
+ }
+
+ // Create hash with random salt, bcrypt or sha256
+ 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;
+ }
+ return $hash;
+ }
+
+ // Verify that text matches hash
+ 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 = substrb($hash, 0, 4);
+ $salt = substrb($hash, 4, 32);
+ $hashCalculated = "$prefix$salt".hash("sha256", $salt.$text);
+ }
+ break;
+ }
+ $ok = !empty($hashCalculated) && strlenb($hashCalculated)==strlenb($hash);
+ if($ok) for($i=0; $i<strlenb($hashCalculated); ++$i) $ok &= $hashCalculated[$i] == $hash[$i];
+ return $ok;
+ }
+
+ // Detect web browser language
+ function detectBrowserLanguage($languages, $languageDefault)
+ {
+ $language = $languageDefault;
+ if(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
+ {
+ foreach(preg_split("/,\s*/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $string)
+ {
+ $tokens = explode(';', $string);
+ if(in_array($tokens[0], $languages)) { $language = $tokens[0]; break; }
+ }
+ }
+ return $language;
+ }
+
+ // Detect image dimensions and type, png or jpg
+ function detectImageInfo($fileName)
+ {
+ $width = $height = 0;
+ $type = "";
+ $fileHandle = @fopen($fileName, "rb");
+ if($fileHandle)
+ {
+ if(substru(strtoloweru($fileName), -3) == "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 = "png";
+ }
+ } else if(substru(strtoloweru($fileName), -3) == "jpg") {
+ $dataBufferSizeMax = filesize($fileName);
+ $dataBufferSize = min($dataBufferSizeMax, 4096);
+ $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 = "jpg";
+ 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;
+ $dataBuffer .= fread($fileHandle, $dataBufferDiff);
+ if(feof($fileHandle)) { $dataBufferSize = 0; break; }
+ }
+ }
+ }
+ }
+ fclose($fileHandle);
+ }
+ return array($width, $height, $type);
+ }
+
+ // Start timer
+ function timerStart(&$time)
+ {
+ $time = microtime(true);
+ }
+
+ // Stop timer and calculate elapsed time in milliseconds
+ function timerStop(&$time)
+ {
+ $time = intval((microtime(true)-$time) * 1000);
+ }
+}
+
+// Unicode support for PHP
+mb_internal_encoding("UTF-8");
+function strempty($string) { return is_null($string) || $string===""; }
+function strreplaceu() { return call_user_func_array("str_replace", func_get_args()); }
+function strtoloweru() { return call_user_func_array("mb_strtolower", func_get_args()); }
+function strtoupperu() { return call_user_func_array("mb_strtoupper", func_get_args()); }
+function strlenu() { return call_user_func_array("mb_strlen", func_get_args()); }
+function strlenb() { return call_user_func_array("strlen", func_get_args()); }
+function strposu() { return call_user_func_array("mb_strpos", func_get_args()); }
+function strposb() { return call_user_func_array("strpos", func_get_args()); }
+function strrposu() { return call_user_func_array("mb_strrpos", func_get_args()); }
+function strrposb() { return call_user_func_array("strrpos", func_get_args()); }
+function substru() { return call_user_func_array("mb_substr", func_get_args()); }
+function substrb() { return call_user_func_array("substr", func_get_args()); }
+
+// Error reporting for PHP
+error_reporting(E_ALL ^ E_NOTICE);
+?>
+\ No newline at end of file
diff --git a/system/plugins/markdown.php b/system/plugins/markdown.php
@@ -5,7 +5,7 @@
// Markdown plugin
class YellowMarkdown
{
- const Version = "0.5.8";
+ const Version = "0.6.1";
var $yellow; //access to API
// Handle initialisation
diff --git a/system/plugins/webinterface.css b/system/plugins/webinterface.css
@@ -0,0 +1,105 @@
+/* Yellow web interface 0.6.1 */
+
+.yellow-bar { position:relative; overflow:hidden; height:2em; margin-bottom:10px; }
+.yellow-bar-left { display:block; float:left; }
+.yellow-bar-right { display:block; float:right; }
+.yellow-bar-right a { margin-left:1em; }
+.yellow-bar-right #yellow-pane-create-link { padding:0 0.5em; }
+.yellow-bar-right #yellow-pane-delete-link { padding:0 0.5em; }
+.yellow-body-modal-open { overflow:hidden; }
+.yellow-body-modal-open .page { display:none; }
+
+.yellow-pane {
+ position:absolute; display:none; z-index:100;
+ margin:10px 0px; padding:10px;
+ background-color:#fff; color:#000;
+ border:1px solid #bbb;
+ border-radius:4px; box-shadow:2px 4px 10px rgba(0, 0, 0, 0.2);
+}
+.yellow-pane h1 { color:#000; }
+.yellow-pane p { margin:0.5em; }
+.yellow-pane ul { list-style:none; margin:0 0.5em; padding:0; }
+.yellow-pane div { overflow:hidden; }
+.yellow-cancel { display:block; float:right; padding:0 0.5em; color:#bbb; }
+.yellow-cancel:hover { text-decoration:none; color:#000; }
+.yellow-arrow { position:absolute; top:0; left:0; }
+.yellow-arrow:after, .yellow-arrow:before {
+ position:absolute;
+ bottom:100%;
+ height:0; width:0;
+ border:solid transparent;
+ content:" ";
+}
+.yellow-arrow:after {
+ border-color:rgba(255, 255, 255, 0);
+ border-bottom-color:#fff;
+ border-width:10px;
+ margin-left:-10px;
+}
+.yellow-arrow:before {
+ border-color:rgba(187, 187, 187, 0);
+ border-bottom-color:#bbb;
+ border-width:11px;
+ margin-left:-11px;
+}
+
+.yellow-form-control {
+ margin:0; padding:2px 4px;
+ display:inline-block;
+ background-color:#fff; color:#000;
+ 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;
+}
+.yellow-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;
+}
+.yellow-btn:hover, .yellow-btn:focus, .yellow-btn:active {
+ color:#333333;
+ background-image:none;
+ text-decoration:none;
+}
+.yellow-btn:active { box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.1); }
+.yellow-btn-create {
+ background-color:#3cc335; color:#ffffff;
+ background-image:linear-gradient(to bottom, #5fee5b, #36bd2f);
+ border-color:#31b121 #31b121 #20b020;
+}
+.yellow-btn-create:hover, .yellow-btn-create:focus, .yellow-btn-create:active { color:#ffffff; }
+.yellow-btn-edit {
+ background-color:#3cc335; color:#ffffff;
+ background-image:linear-gradient(to bottom, #5fee5b, #36bd2f);
+ border-color:#31b121 #31b121 #20b020;
+}
+.yellow-btn-edit:hover, .yellow-btn-edit:focus, .yellow-btn-edit:active { color:#ffffff; }
+.yellow-btn-delete {
+ background-color:#c33c35; color:#ffffff;
+ background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);
+ border-color:#b13121 #b13121 #802020;
+}
+.yellow-btn-delete:hover, .yellow-btn-delete:focus, .yellow-btn-delete:active { color:#ffffff; }
+
+#yellow-pane-login { text-align:center; white-space:nowrap; }
+#yellow-pane-login h1 { margin:0 1em; font-size:2em; }
+#yellow-pane-login-status { display:inline-block; }
+#yellow-pane-login-fields { width:15em; text-align:left; margin:0 auto; }
+#yellow-pane-login input { width:15em; box-sizing:border-box; }
+#yellow-pane-login .yellow-btn { width:15em; margin:1em 1em 0.5em 0; }
+#yellow-pane-edit { }
+#yellow-pane-edit h1 { margin:0 0 10px 0; font-size:1.5em; }
+#yellow-pane-edit-page { padding:5px; outline:none; resize:none; }
+#yellow-pane-edit-buttons { margin-top:5px; }
+#yellow-pane-edit-buttons input { margin-right:10px; }
+#yellow-pane-user { cursor:pointer; }
+#yellow-pane-user a { text-decoration:none; }
+#yellow-pane-user a:hover { text-decoration:underline; }
diff --git a/system/plugins/webinterface.js b/system/plugins/webinterface.js
@@ -0,0 +1,618 @@
+// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
+// This file may be used and distributed under the terms of the public license.
+
+// Yellow API
+var yellow =
+{
+ version: "0.6.1",
+ action: function(text) { yellow.webinterface.action(text); },
+ onClick: function(e) { yellow.webinterface.hidePanesOnClick(yellow.toolbox.getEventElement(e)); },
+ onKeydown: function(e) { yellow.webinterface.hidePanesOnKeydown(yellow.toolbox.getEventKeycode(e)); },
+ onResize: function() { yellow.webinterface.resizePanes(); },
+ onUpdate: function() { yellow.webinterface.updatePane(yellow.webinterface.paneId, yellow.webinterface.paneType); },
+ webinterface:{}, toolbox:{}, page:{}, config:{}, text:{}
+}
+
+// Yellow web interface
+yellow.webinterface =
+{
+ loaded: false, //web interface loaded? (boolean)
+ intervalId: 0, //timer interval ID
+ paneId: 0, //visible pane ID
+ paneType: 0, //visible pane type
+
+ // Initialise web interface
+ init: function()
+ {
+ this.intervalId = setInterval("yellow.webinterface.load()", 1);
+ yellow.toolbox.addEvent(document, "click", yellow.onClick);
+ yellow.toolbox.addEvent(document, "keydown", yellow.onKeydown);
+ yellow.toolbox.addEvent(window, "resize", yellow.onResize);
+ },
+
+ // Load web interface
+ load: function()
+ {
+ var body = document.getElementsByTagName("body")[0];
+ if(body && body.firstChild && !this.loaded)
+ {
+ this.loaded = true;
+ if(yellow.config.webinterfaceLocation)
+ {
+ if(yellow.debug) console.log("yellow.webinterface.load email:"+yellow.config.userEmail+" "+yellow.config.userName);
+ if(yellow.config.userEmail)
+ {
+ this.createBar("yellow-bar", true, body.firstChild);
+ this.createPane("yellow-pane-edit", true, body.firstChild);
+ this.createPane("yellow-pane-user", true, body.firstChild);
+ yellow.toolbox.addEvent(document.getElementById("yellow-pane-edit-page"), "keyup", yellow.onUpdate);
+ yellow.toolbox.addEvent(document.getElementById("yellow-pane-edit-page"), "change", yellow.onUpdate);
+ } else {
+ this.createBar("yellow-bar", false, body.firstChild);
+ this.createPane("yellow-pane-login", false, body.firstChild);
+ if(yellow.config.login) this.showPane("yellow-pane-login");
+ }
+ }
+ clearInterval(this.intervalId);
+ }
+ },
+
+ // Execute action
+ action: function(text)
+ {
+ switch(text)
+ {
+ case "create": this.togglePane("yellow-pane-edit", "create", true); break;
+ case "edit": this.togglePane("yellow-pane-edit", "edit", true); break;
+ case "delete": this.togglePane("yellow-pane-edit", "delete", true); break;
+ case "user": this.togglePane("yellow-pane-user"); break;
+ case "send": this.sendPane(this.paneId, this.paneType); break;
+ case "cancel": this.hidePane(this.paneId); break;
+ case "login": this.togglePane("yellow-pane-login"); break;
+ case "logout": yellow.toolbox.submitForm({"action":"logout"}); break;
+ }
+ },
+
+ // Create bar
+ createBar: function(id, normal, elementReference)
+ {
+ if(yellow.debug) console.log("yellow.webinterface.createBar id:"+id);
+ var elementBar = document.createElement("div");
+ elementBar.className = "yellow-bar";
+ elementBar.setAttribute("id", id);
+ if(normal)
+ {
+ elementBar.innerHTML =
+ "<div class=\"yellow-bar-left\">"+
+ "<a href=\"#\" onclick=\"yellow.action('edit'); return false;\" id=\"yellow-pane-edit-link\">"+this.getText("Edit")+"</a>"+
+ "</div>"+
+ "<div class=\"yellow-bar-right\">"+
+ "<a href=\"#\" onclick=\"yellow.action('create'); return false;\" id=\"yellow-pane-create-link\">"+this.getText("Create")+"</a>"+
+ "<a href=\"#\" onclick=\"yellow.action('delete'); return false;\" id=\"yellow-pane-delete-link\">"+this.getText("Delete")+"</a>"+
+ "<a href=\"#\" onclick=\"yellow.action('user'); return false;\" id=\"yellow-pane-user-link\">"+yellow.config.userName+"</a>"+
+ "</div>";
+ }
+ yellow.toolbox.insertBefore(elementBar, elementReference);
+ },
+
+ // Create pane
+ createPane: function(paneId, bubble, elementReference)
+ {
+ if(yellow.debug) console.log("yellow.webinterface.createPane id:"+paneId);
+ var elementPane = document.createElement("div");
+ elementPane.className = "yellow-pane";
+ elementPane.setAttribute("id", paneId);
+ elementPane.style.display = "none";
+ if(bubble)
+ {
+ var elementArrow = document.createElement("span");
+ elementArrow.className = "yellow-arrow";
+ elementArrow.setAttribute("id", paneId+"-arrow");
+ elementPane.appendChild(elementArrow);
+ }
+ var elementDiv = document.createElement("div");
+ elementDiv.setAttribute("id", paneId+"-content");
+ if(paneId == "yellow-pane-login")
+ {
+ elementDiv.innerHTML =
+ "<form method=\"post\">"+
+ "<a href=\"#\" onclick=\"yellow.action('cancel'); return false;\" class=\"yellow-cancel\">x</a>"+
+ "<h1>"+this.getText("LoginText")+"</h1>"+
+ "<div id=\"yellow-pane-login-fields\">"+
+ "<input type=\"hidden\" name=\"action\" value=\"login\" />"+
+ "<p><label for=\"email\">"+this.getText("LoginEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"email\" maxlength=\"64\" value=\""+yellow.config.loginEmail+"\" /></p>"+
+ "<p><label for=\"password\">"+this.getText("LoginPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"password\" maxlength=\"64\" value=\""+yellow.config.loginPassword+"\" /></p>"+
+ "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+
+ "</div>"+
+ "</form>";
+ } else if(paneId == "yellow-pane-edit") {
+ elementDiv.innerHTML =
+ "<form method=\"post\">"+
+ "<h1 id=\"yellow-pane-edit-title\">"+this.getText("Edit")+"</h1>"+
+ "<textarea id=\"yellow-pane-edit-page\" class=\"yellow-form-control\" name=\"rawdataedit\"></textarea>"+
+ "<div id=\"yellow-pane-edit-buttons\">"+
+ "<input id=\"yellow-pane-edit-send\" class=\"yellow-btn\" type=\"button\" onclick=\"yellow.action('send'); return false;\" value=\""+this.getText("EditButton")+"\" />"+
+ "<input id=\"yellow-pane-edit-cancel\" class=\"yellow-btn\" type=\"button\" onclick=\"yellow.action('cancel'); return false;\" value=\""+this.getText("CancelButton")+"\" />"+
+ "</div>"+
+ "</form>";
+ } else if(paneId == "yellow-pane-user") {
+ elementDiv.innerHTML =
+ "<p>"+yellow.config.userEmail+"</p>"+
+ "<p><a href=\""+this.getText("UserHelpUrl")+"\" onclick=\"yellow.action('user'); return true;\">"+this.getText("UserHelp")+"</a></p>" +
+ "<p><a href=\"#\" onclick=\"yellow.action('logout'); return false;\">"+this.getText("UserLogout")+"</a></p>";
+ }
+ elementPane.appendChild(elementDiv);
+ yellow.toolbox.insertAfter(elementPane, elementReference);
+ },
+
+ // Update pane
+ updatePane: function(paneId, paneType, init)
+ {
+ if(yellow.debug) console.log("yellow.webinterface.updatePane id:"+paneId);
+ if(paneId == "yellow-pane-edit")
+ {
+ if(init)
+ {
+ var title = yellow.page.title;
+ var string = yellow.page.rawDataEdit;
+ switch(paneType)
+ {
+ case "create": title = this.getText("CreateTitle"); string = yellow.page.rawDataNew; break;
+ case "delete": title = this.getText("DeleteTitle"); break;
+ }
+ document.getElementById("yellow-pane-edit-title").innerHTML = yellow.toolbox.encodeHtml(title);
+ document.getElementById("yellow-pane-edit-page").value = string;
+ yellow.toolbox.setCursorPosition(document.getElementById("yellow-pane-edit-page"), 0);
+ }
+ var action = this.getPaneAction(paneId, paneType)
+ if(action)
+ {
+ var key, className;
+ switch(action)
+ {
+ case "create": key = "CreateButton"; className = "yellow-btn yellow-btn-create"; break;
+ case "edit": key = "EditButton"; className = "yellow-btn yellow-btn-edit"; break;
+ case "delete": key = "DeleteButton"; className = "yellow-btn yellow-btn-delete"; break;
+ }
+ document.getElementById("yellow-pane-edit-send").value = this.getText(key);
+ document.getElementById("yellow-pane-edit-send").className = className;
+ } else {
+ document.getElementById("yellow-pane-edit-send").style.display = "none";
+ }
+ }
+ },
+
+ // Send pane
+ sendPane: function(paneId, paneType)
+ {
+ if(yellow.debug) console.log("yellow.webinterface.sendPane id:"+paneId);
+ if(paneId == "yellow-pane-edit")
+ {
+ var action = this.getPaneAction(paneId, paneType);
+ if(action)
+ {
+ var params = {};
+ params.action = action;
+ params.rawdatasource = yellow.page.rawDataSource;
+ params.rawdataedit = document.getElementById("yellow-pane-edit-page").value;
+ yellow.toolbox.submitForm(params, true);
+ } else {
+ this.hidePane(paneId);
+ }
+ }
+ },
+
+ // Show or hide pane
+ togglePane: function(paneId, paneType, modal)
+ {
+ if(this.paneId!=paneId || this.paneType!=paneType)
+ {
+ this.hidePane(this.paneId);
+ this.showPane(paneId, paneType, modal);
+ } else {
+ this.hidePane(paneId);
+ }
+ },
+
+ // Show pane
+ showPane: function(paneId, paneType, modal)
+ {
+ var element = document.getElementById(paneId);
+ if(!yellow.toolbox.isVisible(element))
+ {
+ if(yellow.debug) console.log("yellow.webinterface.showPane id:"+paneId);
+ element.style.display = "block";
+ if(modal) yellow.toolbox.addClass(document.body, "yellow-body-modal-open");
+ this.paneId = paneId;
+ this.paneType = paneType;
+ this.resizePanes();
+ this.updatePane(paneId, paneType, true);
+ }
+ },
+
+ // Hide pane
+ hidePane: function(paneId)
+ {
+ var element = document.getElementById(paneId);
+ if(yellow.toolbox.isVisible(element))
+ {
+ if(yellow.debug) console.log("yellow.webinterface.hidePane id:"+paneId);
+ element.style.display = "none";
+ yellow.toolbox.removeClass(document.body, "yellow-body-modal-open");
+ this.paneId = 0;
+ this.paneType = 0;
+ }
+ },
+
+ // Hide all panes
+ hidePanes: function()
+ {
+ for(var element=document.getElementById("yellow-bar"); element; element=element.nextSibling)
+ {
+ if(element.className && element.className.indexOf("yellow-pane")>=0)
+ {
+ this.hidePane(element.getAttribute("id"));
+ }
+ }
+ },
+
+ // Hide all panes on mouse click outside
+ hidePanesOnClick: function(element)
+ {
+ for(;element; element=element.parentNode)
+ {
+ if(element.className)
+ {
+ if(element.className.indexOf("yellow-pane")>=0 || element.className.indexOf("yellow-bar-")>=0) return;
+ }
+ }
+ this.hidePanes();
+ },
+
+ // Hide all panes on ESC key
+ hidePanesOnKeydown: function(keycode)
+ {
+ if(keycode == 27) this.hidePanes();
+ },
+
+ // Resize panes, recalculate width and height where needed
+ resizePanes: function()
+ {
+ if(document.getElementById("yellow-bar"))
+ {
+ var elementBar = document.getElementById("yellow-bar");
+ var paneTop = yellow.toolbox.getOuterTop(elementBar) + yellow.toolbox.getOuterHeight(elementBar);
+ var paneWidth = yellow.toolbox.getOuterWidth(elementBar, true);
+ var paneHeight = yellow.toolbox.getWindowHeight() - paneTop - yellow.toolbox.getOuterHeight(elementBar);
+ if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-login")))
+ {
+ yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-login"), paneTop);
+ yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-login"), paneWidth);
+ }
+ if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-edit")))
+ {
+ yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-edit"), paneTop);
+ yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit"), paneHeight);
+ yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit"), paneWidth);
+ yellow.toolbox.setOuterWidth(document.getElementById("yellow-pane-edit-page"), yellow.toolbox.getWidth(document.getElementById("yellow-pane-edit")));
+ var height1 = yellow.toolbox.getHeight(document.getElementById("yellow-pane-edit"));
+ var height2 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-content"));
+ var height3 = yellow.toolbox.getOuterHeight(document.getElementById("yellow-pane-edit-page"));
+ yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-edit-page"), height1 - height2 + height3);
+ var elementLink = document.getElementById("yellow-pane-"+this.paneType+"-link");
+ var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
+ position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-edit")) + 1;
+ yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-edit-arrow"), position);
+ }
+ if(yellow.toolbox.isVisible(document.getElementById("yellow-pane-user")))
+ {
+ yellow.toolbox.setOuterTop(document.getElementById("yellow-pane-user"), paneTop);
+ yellow.toolbox.setOuterHeight(document.getElementById("yellow-pane-user"), paneHeight, true);
+ yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-user"), paneWidth - yellow.toolbox.getOuterWidth(document.getElementById("yellow-pane-user")), true);
+ var elementLink = document.getElementById("yellow-pane-user-link");
+ var position = yellow.toolbox.getOuterLeft(elementLink) + yellow.toolbox.getOuterWidth(elementLink)/2;
+ position -= yellow.toolbox.getOuterLeft(document.getElementById("yellow-pane-user"));
+ yellow.toolbox.setOuterLeft(document.getElementById("yellow-pane-user-arrow"), position);
+ }
+ if(yellow.debug) console.log("yellow.webinterface.resizePanes bar:"+elementBar.offsetWidth+"/"+elementBar.offsetHeight);
+ }
+ },
+
+ // Return pane action
+ getPaneAction: function(paneId, paneType)
+ {
+ var action = "";
+ if(paneId == "yellow-pane-edit")
+ {
+ if(yellow.page.userPermission)
+ {
+ var string = document.getElementById("yellow-pane-edit-page").value;
+ switch(paneType)
+ {
+ case "create": action = "create"; break;
+ case "edit": action = string ? "edit" : "delete"; break;
+ case "delete": action = "delete"; break;
+ }
+ if(yellow.page.statusCode==424 && paneType!="delete") action = "create";
+ }
+ }
+ return action;
+ },
+
+ // Return text string
+ getText: function(key)
+ {
+ return ("webinterface"+key in yellow.text) ? yellow.text["webinterface"+key] : "[webinterface"+key+"]";
+ }
+}
+
+// Yellow toolbox with helpers
+yellow.toolbox =
+{
+ // Insert element before reference element
+ insertBefore: function(element, elementReference)
+ {
+ elementReference.parentNode.insertBefore(element, elementReference);
+ },
+
+ // Insert element after reference element
+ insertAfter: function(element, elementReference)
+ {
+ elementReference.parentNode.insertBefore(element, elementReference.nextSibling);
+ },
+
+ // Add element class
+ addClass: function(element, name)
+ {
+ var string = element.className + " " + name;
+ element.className = string.replace(/^\s+|\s+$/, "");
+ },
+
+ // Remove element class
+ removeClass: function(element, name)
+ {
+ var string = (" " + element.className + " ").replace(" " + name + " ", " ");
+ element.className = string.replace(/^\s+|\s+$/, "");
+ },
+
+ // Add event handler
+ addEvent: function(element, type, handler)
+ {
+ if(element.addEventListener) element.addEventListener(type, handler, false);
+ else element.attachEvent('on'+type, handler);
+ },
+
+ // Remove event handler
+ removeEvent: function(element, type, handler)
+ {
+ if(element.removeEventListener) element.removeEventListener(type, handler, false);
+ else element.detachEvent('on'+type, handler);
+ },
+
+ // Return element of event
+ getEventElement: function(e)
+ {
+ e = e ? e : window.event;
+ return e.target ? e.target : e.srcElement;
+ },
+
+ // Return keycode of event
+ getEventKeycode: function(e)
+ {
+ e = e ? e : window.event;
+ return e.keyCode
+ },
+
+ // Set element width/height in pixel, including padding and border
+ setOuterWidth: function(element, width, maxWidth)
+ {
+ width -= this.getBoxSize(element).width;
+ if(maxWidth)
+ {
+ element.style.maxWidth = Math.max(0, width) + "px";
+ } else {
+ element.style.width = Math.max(0, width) + "px";
+ }
+ },
+
+ setOuterHeight: function(element, height, maxHeight)
+ {
+ height -= this.getBoxSize(element).height;
+ if(maxHeight)
+ {
+ element.style.maxHeight = Math.max(0, height) + "px";
+ } else {
+ element.style.height = Math.max(0, height) + "px";
+ }
+ },
+
+ // Return element width/height in pixel, including padding and border
+ getOuterWidth: function(element, includeMargin)
+ {
+ var width = element.offsetWidth;
+ if(includeMargin) width += this.getMarginSize(element).width;
+ return width;
+ },
+
+ getOuterHeight: function(element, includeMargin)
+ {
+ var height = element.offsetHeight;
+ if(includeMargin) height += this.getMarginSize(element).height;
+ return height;
+ },
+
+ // Return element width/height in pixel
+ getWidth: function(element)
+ {
+ return element.offsetWidth - this.getBoxSize(element).width;
+ },
+
+ getHeight: function(element)
+ {
+ return element.offsetHeight - this.getBoxSize(element).height;
+ },
+
+ // Set element top/left position in pixel
+ setOuterTop: function(element, top, marginTop)
+ {
+ if(marginTop)
+ {
+ element.style.marginTop = Math.max(0, top) + "px";
+ } else {
+ element.style.top = Math.max(0, top) + "px";
+ }
+ },
+
+ setOuterLeft: function(element, left, marginLeft)
+ {
+ if(marginLeft)
+ {
+ element.style.marginLeft = Math.max(0, left) + "px";
+ } else {
+ element.style.left = Math.max(0, left) + "px";
+ }
+ },
+
+ // Return element top/left position in pixel
+ getOuterTop: function(element)
+ {
+ var top = element.getBoundingClientRect().top;
+ return top + (window.pageYOffset || document.documentElement.scrollTop);
+ },
+
+ getOuterLeft: function(element)
+ {
+ var left = element.getBoundingClientRect().left;
+ return left + (window.pageXOffset || document.documentElement.scrollLeft);
+ },
+
+ // Return window width/height in pixel
+ getWindowWidth: function()
+ {
+ return window.innerWidth || document.documentElement.clientWidth;
+ },
+
+ getWindowHeight: function()
+ {
+ return window.innerHeight || document.documentElement.clientHeight;
+ },
+
+ // Return element CSS property
+ getStyle: function(element, property)
+ {
+ var string = "";
+ if(window.getComputedStyle)
+ {
+ string = window.getComputedStyle(element, null).getPropertyValue(property);
+ } else {
+ property = property.replace(/\-(\w)/g, function(match, m) { return m.toUpperCase(); });
+ string = element.currentStyle[property];
+ }
+ return string;
+ },
+
+ // Return element CSS padding and border
+ getBoxSize: function(element)
+ {
+ var paddingLeft = parseFloat(this.getStyle(element, "padding-left")) || 0;
+ var paddingRight = parseFloat(this.getStyle(element, "padding-right")) || 0;
+ var borderLeft = parseFloat(this.getStyle(element, "border-left-width")) || 0;
+ var borderRight = parseFloat(this.getStyle(element, "border-right-width")) || 0;
+ var width = paddingLeft + paddingRight + borderLeft + borderRight;
+ var paddingTop = parseFloat(this.getStyle(element, "padding-top")) || 0;
+ var paddingBottom = parseFloat(this.getStyle(element, "padding-bottom")) || 0;
+ var borderTop = parseFloat(this.getStyle(element, "border-top-width")) || 0;
+ var borderBottom = parseFloat(this.getStyle(element, "border-bottom-width")) || 0;
+ var height = paddingTop + paddingBottom + borderTop + borderBottom;
+ return { "width":width, "height":height };
+ },
+
+ // Return element CSS margin
+ getMarginSize: function(element)
+ {
+ var marginLeft = parseFloat(this.getStyle(element, "margin-left")) || 0;
+ var marginRight = parseFloat(this.getStyle(element, "margin-right")) || 0;
+ var width = marginLeft + marginRight;
+ var marginTop = parseFloat(this.getStyle(element, "margin-top")) || 0;
+ var marginBottom = parseFloat(this.getStyle(element, "margin-bottom")) || 0;
+ var height = marginTop + marginBottom;
+ return { "width":width, "height":height };
+ },
+
+ // Set input cursor position
+ setCursorPosition: function(element, pos)
+ {
+ if(element.setSelectionRange)
+ {
+ element.focus();
+ element.setSelectionRange(pos, pos);
+ } else if(element.createTextRange) {
+ var range = element.createTextRange();
+ range.move('character', pos);
+ range.select();
+ }
+ },
+
+ // Get input cursor position
+ getCursorPosition: function(element)
+ {
+ var pos = 0;
+ if(element.setSelectionRange)
+ {
+ pos = element.selectionStart;
+ } else if(document.selection) {
+ var range = document.selection.createRange();
+ var rangeDuplicate = range.duplicate();
+ rangeDuplicate.moveToElementText(element);
+ rangeDuplicate.setEndPoint('EndToEnd', range);
+ pos = rangeDuplicate.text.length - range.text.length;
+ }
+ return pos;
+ },
+
+ // Check if element exists and is visible
+ isVisible: function(element)
+ {
+ return element && element.style.display != "none";
+ },
+
+ // Encode newline characters
+ encodeNewline: function(string)
+ {
+ return string
+ .replace(/[%]/g, "%25")
+ .replace(/[\r]/g, "%0d")
+ .replace(/[\n]/g, "%0a");
+ },
+
+ // Encode HTML special characters
+ encodeHtml: function(string)
+ {
+ return string
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/"/g, """);
+ },
+
+ // Submit form with post method
+ submitForm: function(params, encodeNewline)
+ {
+ var elementForm = document.createElement("form");
+ elementForm.setAttribute("method", "post");
+ for(var key in params)
+ {
+ if(!params.hasOwnProperty(key)) continue;
+ var value = encodeNewline ? this.encodeNewline(params[key]) : params[key];
+ var elementInput = document.createElement("input");
+ elementInput.setAttribute("type", "hidden");
+ elementInput.setAttribute("name", key);
+ elementInput.setAttribute("value", value);
+ elementForm.appendChild(elementInput);
+ }
+ document.body.appendChild(elementForm);
+ elementForm.submit();
+ }
+}
+
+yellow.webinterface.init();
+\ No newline at end of file
diff --git a/system/plugins/webinterface.php b/system/plugins/webinterface.php
@@ -0,0 +1,948 @@
+<?php
+// Copyright (c) 2013-2015 Datenstrom, http://datenstrom.se
+// This file may be used and distributed under the terms of the public license.
+
+// Web interface plugin
+class YellowWebinterface
+{
+ const Version = "0.6.1";
+ var $yellow; //access to API
+ var $active; //web interface is active? (boolean)
+ var $userLoginFailed; //web interface login failed? (boolean)
+ var $userPermission; //web interface can change page? (boolean)
+ var $users; //web interface users
+ var $merge; //web interface merge
+ var $rawDataSource; //raw data of page for comparison
+ var $rawDataEdit; //raw data of page for editing
+
+ // Handle initialisation
+ function onLoad($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->users = new YellowUsers($yellow);
+ $this->merge = new YellowMerge($yellow);
+ $this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
+ $this->yellow->config->setDefault("webinterfaceServerScheme", "http");
+ $this->yellow->config->setDefault("webinterfaceServerName", $this->yellow->config->get("serverName"));
+ $this->yellow->config->setDefault("webinterfaceUserHashAlgorithm", "bcrypt");
+ $this->yellow->config->setDefault("webinterfaceUserHashCost", "10");
+ $this->yellow->config->setDefault("webinterfaceUserHome", "/");
+ $this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
+ $this->yellow->config->setDefault("webinterfaceNewFile", "page-new-(.*).txt");
+ $this->yellow->config->setDefault("webinterfaceMetaFilePrefix", "published");
+ $this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
+ }
+
+ // Handle request
+ function onRequest($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if($this->checkRequest($location))
+ {
+ list($serverScheme, $serverName, $base, $location, $fileName) = $this->updateRequestInformation();
+ $statusCode = $this->processRequest($serverScheme, $serverName, $base, $location, $fileName);
+ } else {
+ $activeLocation = $this->yellow->config->get("webinterfaceLocation");
+ if(rtrim($location, '/') == rtrim($activeLocation, '/'))
+ {
+ $statusCode = 301;
+ $location = $this->yellow->lookup->normaliseUrl(
+ $this->yellow->config->get("webinterfaceServerScheme"),
+ $this->yellow->config->get("webinterfaceServerName"), $base, $activeLocation);
+ $this->yellow->sendStatus($statusCode, $location);
+ }
+ }
+ return $statusCode;
+ }
+
+ // Handle page meta data parsing
+ function onParseMeta($page)
+ {
+ if($this->isActive() && $this->isUser())
+ {
+ if($page == $this->yellow->page)
+ {
+ if(empty($this->rawDataSource)) $this->rawDataSource = $page->rawData;
+ if(empty($this->rawDataEdit)) $this->rawDataEdit = $page->rawData;
+ if($page->statusCode == 424)
+ {
+ $title = $this->yellow->toolbox->createTextTitle($page->location);
+ $this->rawDataEdit = $this->getDataNew($title);
+ }
+ }
+ }
+ }
+
+ // Handle page content parsing of custom block
+ function onParseContentBlock($page, $name, $text, $shortcut)
+ {
+ $output = NULL;
+ if($name=="edit" && $shortcut)
+ {
+ $editText = "$name $text";
+ if(substru($text, 0, 2)=="- ") $editText = trim(substru($text, 2));
+ $output = "<a href=\"".$page->get("pageEdit")."\">".htmlspecialchars($editText)."</a>";
+ }
+ if($name=="debug" && $shortcut)
+ {
+ $output = "<div class=\"".htmlspecialchars($name)."\">\n";
+ if(empty($text))
+ {
+ $serverSoftware = $this->yellow->toolbox->getServerSoftware();
+ $output .= "Yellow ".YellowCore::Version.", PHP ".PHP_VERSION.", $serverSoftware\n";
+ } else {
+ foreach($this->yellow->config->getData($text) as $key=>$value)
+ {
+ $output .= htmlspecialchars(ucfirst($key).": ".$value)."<br />\n";
+ }
+ if($page->parserSafeMode) $page->error(500, "Debug '$text' is not allowed!");
+ }
+ $output .= "</div>\n";
+ }
+ return $output;
+ }
+
+ // Handle page extra HTML data
+ function onExtra($name)
+ {
+ $output = NULL;
+ if($this->isActive() && $name=="header")
+ {
+ if($this->users->getNumber())
+ {
+ $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("pluginLocation")."webinterface";
+ $output = "<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".htmlspecialchars($location).".css\" />\n";
+ $output .= "<script type=\"text/javascript\" src=\"".htmlspecialchars($location).".js\"></script>\n";
+ $output .= "<script type=\"text/javascript\">\n";
+ $output .= "// <![CDATA[\n";
+ if($this->isUser())
+ {
+ $output .= "yellow.page.title = ".json_encode($this->getDataTitle($this->rawDataEdit)).";\n";
+ $output .= "yellow.page.rawDataSource = ".json_encode($this->rawDataSource).";\n";
+ $output .= "yellow.page.rawDataEdit = ".json_encode($this->rawDataEdit).";\n";
+ $output .= "yellow.page.rawDataNew = ".json_encode($this->getDataNew()).";\n";
+ $output .= "yellow.page.pageFile = ".json_encode($this->yellow->page->get("pageFile")).";\n";
+ $output .= "yellow.page.userPermission = ".json_encode($this->userPermission).";\n";
+ $output .= "yellow.page.parserSafeMode = ".json_encode($this->yellow->page->parserSafeMode).";\n";
+ $output .= "yellow.page.statusCode = ".json_encode($this->yellow->page->statusCode).";\n";
+ }
+ $output .= "yellow.config = ".json_encode($this->getDataConfig()).";\n";
+ $language = $this->isUser() ? $this->users->getLanguage() : $this->yellow->page->get("language");
+ if(!$this->yellow->text->isLanguage($language)) $language = $this->yellow->config->get("language");
+ $output .= "yellow.text = ".json_encode($this->yellow->text->getData("webinterface", $language)).";\n";
+ if(defined("DEBUG") && DEBUG>=1) $output .= "yellow.debug = ".json_encode(DEBUG).";\n";
+ $output .= "// ]]>\n";
+ $output .= "</script>\n";
+ }
+ }
+ return $output;
+ }
+
+ // Handle command
+ function onCommand($args)
+ {
+ list($name, $command) = $args;
+ switch($command)
+ {
+ case "user": $statusCode = $this->userCommand($args); break;
+ default: $statusCode = 0;
+ }
+ return $statusCode;
+ }
+
+ // Handle command help
+ function onCommandHelp()
+ {
+ return "user [EMAIL PASSWORD NAME LANGUAGE STATUS HOME]\n";
+ }
+
+ // Update user account
+ function userCommand($args)
+ {
+ $statusCode = 0;
+ list($dummy, $command, $email, $password, $name, $language, $status, $home) = $args;
+ if(!empty($email) && !empty($password))
+ {
+ $fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
+ $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
+ $cost = $this->yellow->config->get("webinterfaceUserHashCost");
+ $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
+ if(empty($hash))
+ {
+ $statusCode = 500;
+ echo "ERROR creating hash: Algorithm '$algorithm' not supported!\n";
+ } else {
+ $statusCode = $this->users->createUser($fileName, $email, $hash, $name, $language, $status, $home) ? 200 : 500;
+ if($statusCode != 200) echo "ERROR updating configuration: Can't write file '$fileName'!\n";
+ }
+ echo "Yellow $command: User account ".($statusCode!=200 ? "not " : "");
+ echo ($this->users->isExisting($email) ? "updated" : "created")."\n";
+ } else {
+ $statusCode = 200;
+ foreach($this->getUserData() as $line) echo "$line\n";
+ if(!$this->users->getNumber()) echo "Yellow $command: No user accounts\n";
+ }
+ return $statusCode;
+ }
+
+ // Process request
+ function processRequest($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if($this->checkUser($location, $fileName))
+ {
+ switch($_POST["action"])
+ {
+ case "": $statusCode = $this->processRequestShow($serverScheme, $serverName, $base, $location, $fileName); break;
+ case "create": $statusCode = $this->processRequestCreate($serverScheme, $serverName, $base, $location, $fileName); break;
+ case "edit": $statusCode = $this->processRequestEdit($serverScheme, $serverName, $base, $location, $fileName); break;
+ case "delete": $statusCode = $this->processRequestDelete($serverScheme, $serverName, $base, $location, $fileName); break;
+ case "login": $statusCode = $this->processRequestLogin($serverScheme, $serverName, $base, $location, $fileName); break;
+ case "logout": $statusCode = $this->processRequestLogout($serverScheme, $serverName, $base, $location, $fileName); break;
+ }
+ }
+ if($statusCode == 0)
+ {
+ $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ if($this->users->getNumber())
+ {
+ if($this->userLoginFailed) $this->yellow->page->error(500, "Login failed, [please log in](javascript:yellow.action('login');)!");
+ } else {
+ $url = $this->yellow->text->get("webinterfaceUserAccountUrl");
+ $this->yellow->page->error(500, "You are not authorised on this server, [please add a user account]($url)!");
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request to show page
+ function processRequestShow($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if(is_readable($fileName))
+ {
+ $statusCode = $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ } else {
+ if($this->yellow->isRequestContentDirectory($location))
+ {
+ $statusCode = 301;
+ $location = $this->yellow->lookup->isFileLocation($location) ? "$location/" : "/".$this->yellow->getRequestLanguage()."/";
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
+ $this->yellow->sendStatus($statusCode, $location);
+ } else {
+ $statusCode = $this->userPermission ? 424 : 404;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ $this->yellow->page->error($statusCode);
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request to create page
+ function processRequestCreate($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if($this->userPermission && !empty($_POST["rawdataedit"]))
+ {
+ $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
+ $page = $this->getPageNew($serverScheme, $serverName, $base, $location, $fileName, rawurldecode($_POST["rawdataedit"]));
+ if(!$page->isError())
+ {
+ if($this->yellow->toolbox->createFile($page->fileName, $page->rawData))
+ {
+ $statusCode = 303;
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $page->location);
+ $this->yellow->sendStatus($statusCode, $location);
+ } else {
+ $statusCode = 500;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
+ }
+ } else {
+ $statusCode = 500;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, $false);
+ $this->yellow->page->error($statusCode, $page->get("pageError"));
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request to edit page
+ function processRequestEdit($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if($this->userPermission && !empty($_POST["rawdataedit"]))
+ {
+ $this->rawDataSource = rawurldecode($_POST["rawdatasource"]);
+ $this->rawDataEdit = rawurldecode($_POST["rawdataedit"]);
+ $page = $this->getPageUpdate($serverScheme, $serverName, $base, $location, $fileName,
+ $this->rawDataSource, $this->rawDataEdit, $this->yellow->toolbox->getFileData($fileName));
+ if(!$page->isError())
+ {
+ if($this->yellow->toolbox->renameFile($fileName, $page->fileName) &&
+ $this->yellow->toolbox->createFile($page->fileName, $page->rawData))
+ {
+ $statusCode = 303;
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $page->location);
+ $this->yellow->sendStatus($statusCode, $location);
+ } else {
+ $statusCode = 500;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ $this->yellow->page->error($statusCode, "Can't write file '$page->fileName'!");
+ }
+ } else {
+ $statusCode = 500;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ $this->yellow->page->error($statusCode, $page->get("pageError"));
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request to delete page
+ function processRequestDelete($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ if($this->userPermission)
+ {
+ $this->rawDataSource = $this->rawDataEdit = rawurldecode($_POST["rawdatasource"]);
+ if(!is_file($fileName) || $this->yellow->toolbox->deleteFile($fileName))
+ {
+ $statusCode = 303;
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
+ $this->yellow->sendStatus($statusCode, $location);
+ } else {
+ $statusCode = 500;
+ $this->yellow->processRequest($serverScheme, $serverName, $base, $location, $fileName, false);
+ $this->yellow->page->error($statusCode, "Can't delete file '$fileName'!");
+ }
+ }
+ return $statusCode;
+ }
+
+ // Process request for user login
+ function processRequestLogin($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 0;
+ $home = $this->users->getHome();
+ if(substru($location, 0, strlenu($home)) == $home)
+ {
+ $statusCode = 303;
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $location);
+ $this->yellow->sendStatus($statusCode, $location);
+ } else {
+ $statusCode = 302;
+ $location = $this->yellow->lookup->normaliseUrl($serverScheme, $serverName, $base, $home);
+ $this->yellow->sendStatus($statusCode, $location);
+ }
+ return $statusCode;
+ }
+
+ // Process request for user logout
+ function processRequestLogout($serverScheme, $serverName, $base, $location, $fileName)
+ {
+ $statusCode = 302;
+ $this->users->destroyCookie("login");
+ $this->users->email = "";
+ $location = $this->yellow->lookup->normaliseUrl(
+ $this->yellow->config->get("serverScheme"),
+ $this->yellow->config->get("serverName"),
+ $this->yellow->config->get("serverBase"), $location);
+ $this->yellow->sendStatus($statusCode, $location);
+ return $statusCode;
+ }
+
+ // Check web interface request
+ function checkRequest($location)
+ {
+ if($this->yellow->toolbox->getServerScheme()==$this->yellow->config->get("webinterfaceServerScheme") &&
+ $this->yellow->toolbox->getServerName()==$this->yellow->config->get("webinterfaceServerName"))
+ {
+ $locationLength = strlenu($this->yellow->config->get("webinterfaceLocation"));
+ $this->active = substru($location, 0, $locationLength) == $this->yellow->config->get("webinterfaceLocation");
+ }
+ return $this->isActive();
+ }
+
+ // Check web interface user
+ function checkUser($location, $fileName)
+ {
+ if($_POST["action"] == "login")
+ {
+ $email = $_POST["email"];
+ $password = $_POST["password"];
+ if($this->users->checkUser($email, $password))
+ {
+ $this->users->createCookie("login", $email);
+ $this->users->email = $email;
+ $this->userPermission = $this->getUserPermission($location, $fileName);
+ } else {
+ $this->userLoginFailed = true;
+ }
+ } else if(isset($_COOKIE["login"])) {
+ list($email, $session) = $this->users->getCookieInformation($_COOKIE["login"]);
+ if($this->users->checkCookie($email, $session))
+ {
+ $this->users->email = $email;
+ $this->userPermission = $this->getUserPermission($location, $fileName);
+ } else {
+ $this->userLoginFailed = true;
+ }
+ }
+ return $this->isUser();
+ }
+
+ // Return permission to change page
+ function getUserPermission($location, $fileName)
+ {
+ $userPermission = NULL;
+ foreach($this->yellow->plugins->plugins as $key=>$value)
+ {
+ if(method_exists($value["obj"], "onUserPermission"))
+ {
+ $userPermission = $value["obj"]->onUserPermission($location, $fileName, $this->users);
+ if(!is_null($userPermission)) break;
+ }
+ }
+ if(is_null($userPermission))
+ {
+ $userPermission = is_dir(dirname($fileName)) && strlenu(basename($fileName))<128;
+ $userPermission &= substru($location, 0, strlenu($this->users->getHome())) == $this->users->getHome();
+ }
+ return $userPermission;
+ }
+
+ // Return user data
+ function getUserData()
+ {
+ $data = array();
+ foreach($this->users->users as $key=>$value)
+ {
+ $data[$key] = "$value[email] - $value[name] $value[language] $value[status] $value[home]";
+ }
+ usort($data, strnatcasecmp);
+ return $data;
+ }
+
+ // Update request information
+ function updateRequestInformation()
+ {
+ $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
+ $serverName = $this->yellow->config->get("webinterfaceServerName");
+ $base = rtrim($this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation"), '/');
+ $this->yellow->page->base = $base;
+ return $this->yellow->getRequestInformation($serverScheme, $serverName, $base);
+ }
+
+ // Update page data with title
+ function updateDataTitle($rawData, $title)
+ {
+ foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
+ {
+ if(preg_match("/^(\s*Title\s*:\s*)(.*?)(\s*)$/i", $line, $matches)) $line = $matches[1].$title.$matches[3];
+ $rawDataNew .= $line;
+ }
+ return $rawDataNew;
+ }
+
+ // Return page data title
+ function getDataTitle($rawData)
+ {
+ $title = $this->yellow->page->get("title");
+ if(preg_match("/^(\xEF\xBB\xBF)?\-\-\-[\r\n]+(.+?)[\r\n]+\-\-\-[\r\n]+/s", $rawData))
+ {
+ foreach($this->yellow->toolbox->getTextLines($rawData) as $line)
+ {
+ if(preg_match("/^(\s*Title\s*:\s*)(.*?)(\s*)$/i", $line, $matches)) { $title = $matches[2]; break; }
+ }
+ }
+ return $title;
+ }
+
+ // Return new page
+ function getPageNew($serverScheme, $serverName, $base, $location, $fileName, $rawData)
+ {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
+ $page->parseData($rawData, false, 0);
+ $page->fileName = $this->yellow->lookup->findFileFromTitle(
+ $page->get($this->yellow->config->get("webinterfaceMetaFilePrefix")), $page->get("title"), $fileName,
+ $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
+ $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
+ if($this->yellow->pages->find($page->location))
+ {
+ preg_match("/^(.*?)(\d*)$/", $page->get("title"), $matches);
+ $titleText = $matches[1];
+ $titleNumber = $matches[2];
+ if(strempty($titleNumber)) { $titleNumber = 2; $titleText = $titleText.' '; }
+ for(; $titleNumber<=999; ++$titleNumber)
+ {
+ $page->rawData = $this->updateDataTitle($rawData, $titleText.$titleNumber);
+ $page->fileName = $this->yellow->lookup->findFileFromTitle(
+ $page->get($this->yellow->config->get("webinterfaceMetaFilePrefix")), $titleText.$titleNumber, $fileName,
+ $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
+ $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
+ if(!$this->yellow->pages->find($page->location)) { $ok = true; break; }
+ }
+ if(!$ok) $page->error(500, "Page '".$page->get("title")."' can not be created!");
+ }
+ if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
+ return $page;
+ }
+
+ // Return modified page
+ function getPageUpdate($serverScheme, $serverName, $base, $location, $fileName, $rawDataSource, $rawDataEdit, $rawDataFile)
+ {
+ $page = new YellowPage($this->yellow);
+ $page->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
+ $page->parseData($this->merge->merge($rawDataSource, $rawDataEdit, $rawDataFile), false, 0);
+ if(empty($page->rawData)) $page->error(500, "Page has been modified by someone else!");
+ if($this->yellow->lookup->isFileLocation($location) && !$page->isError())
+ {
+ $pageSource = new YellowPage($this->yellow);
+ $pageSource->setRequestInformation($serverScheme, $serverName, $base, $location, $fileName);
+ $pageSource->parseData($rawDataSource, false, 0);
+ $prefix = $this->yellow->config->get("webinterfaceMetaFilePrefix");
+ if($pageSource->get($prefix)!=$page->get($prefix) || $pageSource->get("title")!=$page->get("title"))
+ {
+ $page->fileName = $this->yellow->lookup->findFileFromTitle(
+ $page->get($prefix), $page->get("title"), $fileName,
+ $this->yellow->config->get("contentDefaultFile"), $this->yellow->config->get("contentExtension"));
+ $page->location = $this->yellow->lookup->findLocationFromFile($page->fileName);
+ if($pageSource->location!=$page->location && $this->yellow->pages->find($page->location))
+ {
+ $page->error(500, "Page '".$page->get("title")."' already exists!");
+ }
+ }
+ }
+ if(!$this->getUserPermission($page->location, $page->fileName)) $page->error(500, "Page '".$page->get("title")."' is not allowed!");
+ return $page;
+ }
+
+ // Return content data for new page
+ function getDataNew($title = "")
+ {
+ $fileName = $this->yellow->lookup->findFileFromLocation($this->yellow->page->location);
+ $fileName = $this->yellow->lookup->findFileNew($fileName,
+ $this->yellow->config->get("webinterfaceNewFile"), $this->yellow->config->get("configDir"),
+ $this->yellow->config->get("template"));
+ $fileData = $this->yellow->toolbox->getFileData($fileName);
+ $fileData = preg_replace("/@datetime/i", date("Y-m-d H:i:s"), $fileData);
+ $fileData = preg_replace("/@date/i", date("Y-m-d"), $fileData);
+ $fileData = preg_replace("/@username/i", $this->users->getName(), $fileData);
+ $fileData = preg_replace("/@userlanguage/i", $this->users->getLanguage(), $fileData);
+ if(!empty($title)) $fileData = $this->updateDataTitle($fileData, $title);
+ return $fileData;
+ }
+
+ // Return configuration data including information of current user
+ function getDataConfig()
+ {
+ $data = $this->yellow->config->getData("", "Location");
+ if($this->isUser())
+ {
+ $data["userEmail"] = $this->users->email;
+ $data["userName"] = $this->users->getName();
+ $data["userLanguage"] = $this->users->getLanguage();
+ $data["userStatus"] = $this->users->getStatus();
+ $data["userHome"] = $this->users->getHome();
+ $data["serverScheme"] = $this->yellow->config->get("serverScheme");
+ $data["serverName"] = $this->yellow->config->get("serverName");
+ $data["serverBase"] = $this->yellow->config->get("serverBase");
+ } else {
+ $data["login"] = $this->yellow->page->statusCode==200;
+ $data["loginEmail"] = $this->yellow->config->get("loginEmail");
+ $data["loginPassword"] = $this->yellow->config->get("loginPassword");
+ }
+ return $data;
+ }
+
+ // Check if web interface request
+ function isActive()
+ {
+ return $this->active;
+ }
+
+ // Check if user is logged in
+ function isUser()
+ {
+ return !empty($this->users->email);
+ }
+}
+
+// Yellow users
+class YellowUsers
+{
+ var $yellow; //access to API
+ var $users; //registered users
+ var $email; //current user
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ $this->users = array();
+ }
+
+ // Load users from file
+ function load($fileName)
+ {
+ $fileData = @file($fileName);
+ if($fileData)
+ {
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowUsers::load file:$fileName<br/>\n";
+ foreach($fileData as $line)
+ {
+ if(preg_match("/^\#/", $line)) continue;
+ preg_match("/^(.*?)\s*:\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
+ if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]) &&
+ !empty($matches[5]) && !empty($matches[6]))
+ {
+ $this->set($matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]);
+ if(defined("DEBUG") && DEBUG>=3) echo "YellowUsers::load email:$matches[1] $matches[5]<br/>\n";
+ }
+ }
+ }
+ }
+
+ // Set user data
+ function set($email, $hash, $name, $language, $status, $home)
+ {
+ $this->users[$email] = array();
+ $this->users[$email]["email"] = $email;
+ $this->users[$email]["hash"] = $hash;
+ $this->users[$email]["name"] = $name;
+ $this->users[$email]["language"] = $language;
+ $this->users[$email]["status"] = $status;
+ $this->users[$email]["home"] = $home;
+ }
+
+ // Create or update user in file
+ function createUser($fileName, $email, $hash, $name, $language, $status, $home)
+ {
+ $email = strreplaceu(',', '-', $email);
+ $hash = strreplaceu(',', '-', $hash);
+ $fileData = @file($fileName);
+ if($fileData)
+ {
+ foreach($fileData as $line)
+ {
+ preg_match("/^(.*?)\s*:\s*(.*?),\s*(.*?),\s*(.*?),\s*(.*?)\s*$/", $line, $matches);
+ if(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3]) && !empty($matches[4]))
+ {
+ if($matches[1] == $email)
+ {
+ $name = strreplaceu(',', '-', empty($name) ? $matches[3] : $name);
+ $language = strreplaceu(',', '-', empty($language) ? $matches[4] : $language);
+ $status = strreplaceu(',', '-', empty($status) ? $matches[5] : $status);
+ $home = strreplaceu(',', '-', empty($home) ? $matches[6] : $home);
+ $fileDataNew .= "$email: $hash,$name,$language,$status,$home\n";
+ $found = true;
+ continue;
+ }
+ }
+ $fileDataNew .= $line;
+ }
+ }
+ if(!$found)
+ {
+ $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
+ $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
+ $status = strreplaceu(',', '-', empty($status) ? "active" : $status);
+ $home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home);
+ $fileDataNew .= "$email: $hash,$name,$language,$status,$home\n";
+ }
+ return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
+ }
+
+ // Check user login
+ function checkUser($email, $password)
+ {
+ $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
+ return $this->isExisting($email) && $this->users[$email]["status"]=="active" &&
+ $this->yellow->toolbox->verifyHash($password, $algorithm, $this->users[$email]["hash"]);
+ }
+
+ // Create browser cookie
+ function createCookie($cookieName, $email)
+ {
+ if($this->isExisting($email))
+ {
+ $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
+ $serverName = $this->yellow->config->get("webinterfaceServerName");
+ $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
+ $expire = time()+60*60*24*30*365;
+ $session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256");
+ if(empty($session)) $session = "error-hash-algorithm-sha256";
+ if($serverName == "localhost") $serverName = false;
+ setcookie($cookieName, "$email,$session", $expire, $location, $serverName, $serverScheme=="https");
+ }
+ }
+
+ // Destroy browser cookie
+ function destroyCookie($cookieName)
+ {
+ $serverScheme = $this->yellow->config->get("webinterfaceServerScheme");
+ $serverName = $this->yellow->config->get("webinterfaceServerName");
+ $location = $this->yellow->config->get("serverBase").$this->yellow->config->get("webinterfaceLocation");
+ if($serverName == "localhost") $serverName = false;
+ setcookie($cookieName, "", time()-3600, $location, $serverName, $serverScheme=="https");
+ }
+
+ // Return information from browser cookie
+ function getCookieInformation($cookie)
+ {
+ return explode(',', $cookie, 2);
+ }
+
+ // Check user login from browser cookie
+ function checkCookie($email, $session)
+ {
+ return $this->isExisting($email) && $this->users[$email]["status"]=="active" &&
+ $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session);
+ }
+
+ // Retun user login information
+ function getUserInfo($email, $password, $name, $language, $home)
+ {
+ $algorithm = $this->yellow->config->get("webinterfaceUserHashAlgorithm");
+ $cost = $this->yellow->config->get("webinterfaceUserHashCost");
+ $hash = $this->yellow->toolbox->createHash($password, $algorithm, $cost);
+ if(!empty($hash))
+ {
+ $email = strreplaceu(',', '-', $email);
+ $hash = strreplaceu(',', '-', $hash);
+ $name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
+ $language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
+ $status = strreplaceu(',', '-', empty($status) ? "active" : $status);
+ $home = strreplaceu(',', '-', empty($home) ? $this->yellow->config->get("webinterfaceUserHome") : $home);
+ $user = "$email: $hash,$name,$language,$status,$home\n";
+ }
+ return $user;
+ }
+
+ // Return user name
+ function getName($email = "")
+ {
+ if(empty($email)) $email = $this->email;
+ return $this->isExisting($email) ? $this->users[$email]["name"] : "";
+ }
+
+ // Return user language
+ function getLanguage($email = "")
+ {
+ if(empty($email)) $email = $this->email;
+ return $this->isExisting($email) ? $this->users[$email]["language"] : "";
+ }
+
+ // Return user status
+ function getStatus($email = "")
+ {
+ if(empty($email)) $email = $this->email;
+ return $this->isExisting($email) ? $this->users[$email]["status"] : "";
+ }
+
+ // Return user home
+ function getHome($email = "")
+ {
+ if(empty($email)) $email = $this->email;
+ return $this->isExisting($email) ? $this->users[$email]["home"] : "";
+ }
+
+ // Return number of users
+ function getNumber()
+ {
+ return count($this->users);
+ }
+
+ // Check if user exists
+ function isExisting($email)
+ {
+ return !is_null($this->users[$email]);
+ }
+}
+
+// Yellow merge
+class YellowMerge
+{
+ var $yellow; //access to API
+ const Add = '+'; //merge types
+ const Modify = '*';
+ const Remove = '-';
+ const Same = ' ';
+
+ function __construct($yellow)
+ {
+ $this->yellow = $yellow;
+ }
+
+ // Merge text, NULL if not possible
+ function merge($textSource, $textMine, $textYours, $showDiff = false)
+ {
+ if($textMine != $textYours)
+ {
+ $diffMine = $this->buildDiff($textSource, $textMine);
+ $diffYours = $this->buildDiff($textSource, $textYours);
+ $diff = $this->mergeDiff($diffMine, $diffYours);
+ $output = $this->getOutput($diff, $showDiff);
+ } else {
+ $output = $textMine;
+ }
+ return $output;
+ }
+
+ // Build differences to common source
+ function buildDiff($textSource, $textOther)
+ {
+ $diff = array();
+ $lastRemove = -1;
+ $textStart = 0;
+ $textSource = $this->yellow->toolbox->getTextLines($textSource);
+ $textOther = $this->yellow->toolbox->getTextLines($textOther);
+ $sourceEnd = $sourceSize = count($textSource);
+ $otherEnd = $otherSize = count($textOther);
+ while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$textStart]==$textOther[$textStart]) ++$textStart;
+ while($textStart<$sourceEnd && $textStart<$otherEnd && $textSource[$sourceEnd-1]==$textOther[$otherEnd-1])
+ {
+ --$sourceEnd; --$otherEnd;
+ }
+ for($pos=0; $pos<$textStart; ++$pos) array_push($diff, array(YellowMerge::Same, $textSource[$pos], false));
+ $lcs = $this->buildDiffLCS($textSource, $textOther, $textStart, $sourceEnd-$textStart, $otherEnd-$textStart);
+ for($x=0,$y=0,$xEnd=$otherEnd-$textStart,$yEnd=$sourceEnd-$textStart; $x<$xEnd || $y<$yEnd;)
+ {
+ $max = $lcs[$y][$x];
+ if($y<$yEnd && $lcs[$y+1][$x]==$max)
+ {
+ array_push($diff, array(YellowMerge::Remove, $textSource[$textStart+$y], false));
+ if($lastRemove == -1) $lastRemove = count($diff)-1;
+ ++$y;
+ continue;
+ }
+ if($x<$xEnd && $lcs[$y][$x+1]==$max)
+ {
+ if($lastRemove==-1 || $diff[$lastRemove][0]!=YellowMerge::Remove)
+ {
+ array_push($diff, array(YellowMerge::Add, $textOther[$textStart+$x], false));
+ $lastRemove = -1;
+ } else {
+ $diff[$lastRemove] = array(YellowMerge::Modify, $textOther[$textStart+$x], false);
+ ++$lastRemove; if(count($diff)==$lastRemove) $lastRemove = -1;
+ }
+ ++$x;
+ continue;
+ }
+ array_push($diff, array(YellowMerge::Same, $textSource[$textStart+$y], false));
+ $lastRemove = -1;
+ ++$x;
+ ++$y;
+ }
+ for($pos=$sourceEnd;$pos<$sourceSize; ++$pos) array_push($diff, array(YellowMerge::Same, $textSource[$pos], false));
+ return $diff;
+ }
+
+ // Build longest common subsequence
+ function buildDiffLCS($textSource, $textOther, $textStart, $yEnd, $xEnd)
+ {
+ $lcs = array_fill(0, $yEnd+1, array_fill(0, $xEnd+1, 0));
+ for($y=$yEnd-1; $y>=0; --$y)
+ {
+ for($x=$xEnd-1; $x>=0; --$x)
+ {
+ if($textSource[$textStart+$y] == $textOther[$textStart+$x])
+ {
+ $lcs[$y][$x] = $lcs[$y+1][$x+1]+1;
+ } else {
+ $lcs[$y][$x] = max($lcs[$y][$x+1], $lcs[$y+1][$x]);
+ }
+ }
+ }
+ return $lcs;
+ }
+
+ // Merge differences
+ function mergeDiff($diffMine, $diffYours)
+ {
+ $diff = array();
+ $posMine = $posYours = 0;
+ while($posMine<count($diffMine) && $posYours<count($diffYours))
+ {
+ $typeMine = $diffMine[$posMine][0];
+ $typeYours = $diffYours[$posYours][0];
+ if($typeMine==YellowMerge::Same)
+ {
+ array_push($diff, $diffYours[$posYours]);
+ } else if($typeYours==YellowMerge::Same) {
+ array_push($diff, $diffMine[$posMine]);
+ } else if($typeMine==YellowMerge::Add && $typeYours==YellowMerge::Add) {
+ $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
+ } else if($typeMine==YellowMerge::Modify && $typeYours==YellowMerge::Modify) {
+ $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], false);
+ } else if($typeMine==YellowMerge::Remove && $typeYours==YellowMerge::Remove) {
+ array_push($diff, $diffMine[$posMine]);
+ } else if($typeMine==YellowMerge::Add) {
+ array_push($diff, $diffMine[$posMine]);
+ } else if($typeYours==YellowMerge::Add) {
+ array_push($diff, $diffYours[$posYours]);
+ } else {
+ $this->mergeConflict($diff, $diffMine[$posMine], $diffYours[$posYours], true);
+ }
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
+ if($typeMine==YellowMerge::Add || $typeYours==YellowMerge::Add)
+ {
+ if($typeMine==YellowMerge::Add) ++$posMine;
+ if($typeYours==YellowMerge::Add) ++$posYours;
+ } else {
+ ++$posMine;
+ ++$posYours;
+ }
+ }
+ for(;$posMine<count($diffMine); ++$posMine)
+ {
+ array_push($diff, $diffMine[$posMine]);
+ $typeMine = $diffMine[$posMine][0]; $typeYours = ' ';
+ if(defined("DEBUG") && DEBUG>=2) echo "YellowMerge::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 "YellowMerge::mergeDiff $typeMine $typeYours pos:$posMine\t$posYours<br/>\n";
+ }
+ return $diff;
+ }
+
+ // Merge potential conflict
+ function mergeConflict(&$diff, $diffMine, $diffYours, $conflict)
+ {
+ if(!$conflict && $diffMine[1]==$diffYours[1])
+ {
+ array_push($diff, $diffMine);
+ } else {
+ array_push($diff, array($diffMine[0], $diffMine[1], true));
+ array_push($diff, array($diffYours[0], $diffYours[1], true));
+ }
+ }
+
+ // Return merged text, NULL if not possible
+ function getOutput($diff, $showDiff = false)
+ {
+ $output = "";
+ if(!$showDiff)
+ {
+ for($i=0; $i<count($diff); ++$i)
+ {
+ if($diff[$i][0] != YellowMerge::Remove) $output .= $diff[$i][1];
+ $conflict |= $diff[$i][2];
+ }
+ } else {
+ for($i=0; $i<count($diff); ++$i)
+ {
+ $output .= $diff[$i][2] ? "! " : $diff[$i][0].' ';
+ $output .= $diff[$i][1];
+ }
+ }
+ return !$conflict ? $output : NULL;
+ }
+}
+
+$yellow->plugins->register("webinterface", "YellowWebinterface", YellowWebinterface::Version);
+?>
+\ No newline at end of file
diff --git a/yellow.php b/yellow.php
@@ -2,16 +2,16 @@
// Yellow is for people who make websites. http://datenstrom.se/yellow
// This file may be used and distributed under the terms of the public license.
-require_once("system/core/core.php");
+require_once("system/plugins/core.php");
if(PHP_SAPI != "cli")
{
- $yellow = new Yellow();
+ $yellow = new YellowCore();
$yellow->plugins->load();
$yellow->request();
} else {
- $yellow = new Yellow();
+ $yellow = new YellowCore();
$yellow->plugins->load();
- $statusCode = $yellow->command("commandline", $argv[1], $argv[2], $argv[3], $argv[4], $argv[5], $argv[6]);
+ $statusCode = $yellow->command("commandline", $argv[1], $argv[2], $argv[3], $argv[4], $argv[5], $argv[6], $argv[7]);
exit($statusCode<400 ? 0 : 1);
}
?>
\ No newline at end of file