mikuli.cz

:)
git clone https://git.sr.ht/~ashymad/mikuli.cz
Log | Files | Refs

commit c6cf750609e10aef600d3b4489ba6b0d2a22722d
parent ee5b9ff185f1bc445978b4ca2c469561a57c81d5
Author: markseu <mark2011@mayberg.se>
Date:   Tue,  6 Oct 2015 14:19:11 +0200

Core update (fika remix)

Diffstat:
Mmedia/downloads/yellow.pdf | 0
Mmedia/images/icon.png | 0
Msystem/config/config.ini | 99+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msystem/config/language-en.ini | 82+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Dsystem/config/page-error-401.txt | 5-----
Msystem/config/user.ini | 1-
Dsystem/core/core-commandline.php | 445-------------------------------------------------------------------------------
Dsystem/core/core-webinterface.css | 104-------------------------------------------------------------------------------
Dsystem/core/core-webinterface.js | 580-------------------------------------------------------------------------------
Dsystem/core/core-webinterface.php | 933-------------------------------------------------------------------------------
Dsystem/core/core.php | 2870-------------------------------------------------------------------------------
Asystem/plugins/commandline.php | 553+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystem/plugins/core.php | 2859+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msystem/plugins/markdown.php | 2+-
Asystem/plugins/webinterface.css | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystem/plugins/webinterface.js | 619+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asystem/plugins/webinterface.php | 949+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Myellow.php | 8++++----
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, "&amp;") - .replace(/</g, "&lt;") - .replace(/>/g, "&gt;") - .replace(/"/g, "&quot;"); - }, - - // 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, "&amp;") + .replace(/</g, "&lt;") + .replace(/>/g, "&gt;") + .replace(/"/g, "&quot;"); + }, + + // 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