diff --git a/.htaccess b/.htaccess index 1f2780d..c2586e4 100644 --- a/.htaccess +++ b/.htaccess @@ -4,10 +4,14 @@ RewriteEngine on # RewriteBase /yellow -RewriteRule ^content/(.*) error [R=301,L] -RewriteRule ^system/(.*) error [R=301,L] +RewriteCond %{ENV:REDIRECT_STATUS} ^$ +RewriteRule ^(content|system)/ error404 [L] +RewriteCond %{REQUEST_URI} \.(css|js|png)$ +RewriteRule ^media/plugins/(core_.+) system/core/$1 [L] +RewriteCond %{REQUEST_URI} \.(css|js|png)$ +RewriteRule ^media/plugins/(.+) system/plugins/$1 [L] -RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.+) index.php [L] +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ index.php [L] diff --git a/README.md b/README.md index 3b039be..667e527 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ Yellow ====== -Yellow is a CMS for people, it's web-based and flat-file. \ No newline at end of file +Yellow is a CMS for people, it's web-based and flat-file. + +How do I install this? +---------------------- +[Download Yellow](https://github.com/markseu/yellowcms/archive/master.zip) and unzip it. +Copy all files to your web server hosting. +Open your website in a browser, that's it! + +Installation requirements are Apache, mod_rewrite and PHP 5.3. + +Need help? Have a question? +--------------------------- +Visit [Yellow on Reddit](http://www.reddit.com/r/yellowcms/), follow [Yellow on Twitter](https://twitter.com/yellowcms). \ No newline at end of file diff --git a/content/1-home/page.txt b/content/1-home/page.txt index ad92077..28f15db 100755 --- a/content/1-home/page.txt +++ b/content/1-home/page.txt @@ -2,6 +2,6 @@ Title: Home --- Yes, it works! Your Yellow installation was successful. -You can now [edit this page](wiki/) or use your favorite text editor. +You can now [edit this page](@baselocation/wiki/) or use your favorite text editor. -[Visit Yellow on Github](https://github.com/markseu/yellowcms). \ No newline at end of file +Visit [Yellow on Github](https://github.com/markseu/yellowcms). \ No newline at end of file diff --git a/system/config/config.ini b/system/config/config.ini index 962251a..8cd23c1 100644 --- a/system/config/config.ini +++ b/system/config/config.ini @@ -1,5 +1,5 @@ -// Yellow CMS configuration -// All directories and locations have to end with a slash +// Yellow site configuration +// All locations and directories have to end with a slash sitename = Website author = Website @@ -10,7 +10,6 @@ template = default stylesLocation = /media/styles/ imagesLocation = /media/images/ pluginsLocation = /media/plugins/ - systemDir = system/ configDir = system/config/ pluginDir = system/plugins/ @@ -18,7 +17,9 @@ snippetDir = system/snippets/ templateDir = system/templates/ contentDir = content/ contentHomeDir = 1-home/ -contentDefaultFile = page +contentDefaultFile = page.txt contentExtension = .txt configExtension = .ini -systemExtension = .php \ No newline at end of file +systemExtension = .php +errorPageFile = error(.*).txt +textStringFile = text_(.*).ini \ No newline at end of file diff --git a/system/config/error401.txt b/system/config/error401.txt new file mode 100644 index 0000000..30078dd --- /dev/null +++ b/system/config/error401.txt @@ -0,0 +1,4 @@ +--- +Title: Unauthorised +--- +You are not authorised on this server. Please log in. \ No newline at end of file diff --git a/system/config/error424.txt b/system/config/error424.txt new file mode 100644 index 0000000..16c1dcd --- /dev/null +++ b/system/config/error424.txt @@ -0,0 +1,4 @@ +--- +Title: Page does not exist +--- +You can [create this page](javascript:showPane('cmspaneeditor');). \ No newline at end of file diff --git a/system/config/text_english.ini b/system/config/text_english.ini new file mode 100644 index 0000000..639e4ab --- /dev/null +++ b/system/config/text_english.ini @@ -0,0 +1,19 @@ +// Yellow text strings +// Format: key = text string + +language = en +languageDescription = English +languageAuthor = Mark Seuffert + +webinterfaceLoginText = Yellow login +webinterfaceLoginEmail = Email: +webinterfaceLoginPassword = Password: +webinterfaceLoginButton = Login +webinterfaceSaveButton = Save +webinterfaceCancelButton = Cancel +webinterfaceEdit = Edit +webinterfaceShow = Show +webinterfaceUser = User +webinterfaceUserLogout = Logout +webinterface424Title = New page +webinterface424Text = Write text here \ No newline at end of file diff --git a/system/config/user.ini b/system/config/user.ini new file mode 100644 index 0000000..cbb222f --- /dev/null +++ b/system/config/user.ini @@ -0,0 +1,4 @@ +// Yellow user accounts +// Format: Email, password (sha256 with email prefix as salt), name, language + +// user@user.com,e1e2f704225259e91c44fb259c7db595e6cfd1a2e85a2b56508ab5aa9217d265,User,en diff --git a/system/core/core.php b/system/core/core.php index 0c81526..8ed24ea 100755 --- a/system/core/core.php +++ b/system/core/core.php @@ -5,27 +5,30 @@ // Yellow main class class Yellow { - const Version = "0.0.0"; //Hello world! - - var $page; //current page data - var $pages; //current page tree, top level - var $toolbox; //toolbox with helpers - var $config; //site configuration - var $plugins; //site plugins + const Version = "0.1.0"; + var $page; //current page data + var $pages; //current page tree, top level + var $toolbox; //toolbox with helpers + var $config; //site configuration + var $text; //site text strings + var $plugins; //site plugins function __construct() { - $this->toolbox = new Yellow_Toolbox(); + $this->toolbox = new Yellow_Toolbox(); $this->config = new Yellow_Config(); - $this->config->setDefault("sitename", "Yellow"); - $this->config->setDefault("author", "Yellow"); - $this->config->setDefault("language", "en"); + $this->text = new Yellow_Text(); + $this->plugins = new Yellow_Plugins(); + $this->config->setDefault("sitename", "Yellow"); + $this->config->setDefault("author", "Yellow"); + $this->config->setDefault("language", "en"); $this->config->setDefault("parser", "markdown"); $this->config->setDefault("template", "default"); - $this->config->setDefault("baseLocation", $this->toolbox->getBaseLocation()); + $this->config->setDefault("yellowVersion", Yellow::Version); + $this->config->setDefault("baseLocation", $this->toolbox->getBaseLocation()); $this->config->setDefault("stylesLocation", "/media/styles/"); $this->config->setDefault("imagesLocation", "/media/images/"); - $this->config->setDefault("pluginsLocation", "media/plugins/"); + $this->config->setDefault("pluginsLocation", "media/plugins/"); $this->config->setDefault("systemDir", "system/"); $this->config->setDefault("configDir", "system/config/"); $this->config->setDefault("pluginDir", "system/plugins/"); @@ -33,700 +36,870 @@ class Yellow $this->config->setDefault("templateDir", "system/templates/"); $this->config->setDefault("contentDir", "content/"); $this->config->setDefault("contentHomeDir", "1-home/"); - $this->config->setDefault("contentDefaultFile", "page"); + $this->config->setDefault("contentDefaultFile", "page.txt"); $this->config->setDefault("contentExtension", ".txt"); - $this->config->setDefault("configExtension", ".ini"); + $this->config->setDefault("configExtension", ".ini"); $this->config->setDefault("systemExtension", ".php"); $this->config->setDefault("configFile", "config.ini"); - $this->config->setDefault("yellowVersion", Yellow::Version); - $this->config->load($this->config->get("configDir").$this->config->get("configFile")); - $this->plugins = new Yellow_Plugins(); - } + $this->config->setDefault("errorPageFile", "error(.*).txt"); + $this->config->setDefault("textStringFile", "text_(.*).ini"); + $this->config->load($this->config->get("configDir").$this->config->get("configFile")); + $this->text->load($this->config->get("configDir").$this->config->get("textStringFile"), $this->toolbox); + } - // Start CMS and handle request - function request() - { - $this->toolbox->timerStart($time); - $this->plugins->load(); - $this->processRequest(); - $this->toolbox->timerStop($time); - if(defined("DEBUG") && DEBUG>=1) echo "Yellow::request time:$time ms
\n"; - } + // Start and handle request + function request() + { + $this->toolbox->timerStart($time); + $this->plugins->load(); + $this->processRequest(); + $this->toolbox->timerStop($time); + if(defined("DEBUG") && DEBUG>=1) echo "Yellow::request time:$time ms
\n"; + } - // Process request + // Process request function processRequest() { - $statusCode = 0; - $baseLocation = $this->config->get("baseLocation"); - $location = $this->getRelativeLocation($baseLocation); + $statusCode = 0; + $baseLocation = $this->config->get("baseLocation"); + $location = $this->getRelativeLocation($baseLocation); $fileName = $this->getContentFileName($location); - foreach($this->plugins->plugins as $key=>$value) - { + foreach($this->plugins->plugins as $key=>$value) + { if(method_exists($value["obj"], "onRequest")) - { + { $statusCode = $value["obj"]->onRequest($baseLocation, $location, $fileName); if($statusCode) break; - } - } - if($statusCode == 0) $statusCode = $this->processRequestFile($baseLocation, $location, $fileName, $statusCode); - if(defined("DEBUG") && DEBUG>=1) echo "Yellow::processRequest status:$statusCode, location:$location
\n"; - } + } + } + if($statusCode == 0) $statusCode = $this->processRequestFile($baseLocation, $location, $fileName, $statusCode); + if(defined("DEBUG") && DEBUG>=1) echo "Yellow::processRequest status:$statusCode location:$location
\n"; + } - // Process request for a file - function processRequestFile($baseLocation, $location, $fileName, $statusCode, $cache = true) - { - if($statusCode == 0) - { - if(is_readable($fileName)) - { - $time = gmdate("D, d M Y H:i:s", filemtime($fileName))." GMT"; - if(isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) && $_SERVER["HTTP_IF_MODIFIED_SINCE"]==$time && $cache) - { - $statusCode = 304; - $this->sendStatus($statusCode); - } else { - $statusCode = 200; - header("Content-Type: text/html; charset=UTF-8"); - if($cache) - { - header("Last-Modified: ".$time); - } else { - header("Cache-Control: no-cache"); - header("Pragma: no-cache"); - header("Expires: 0"); - } - $fileHandle = @fopen($fileName, "r"); - if($fileHandle) - { - $fileData = fread($fileHandle, filesize($fileName)); - fclose($fileHandle); - } else { - die("Server problem: Can't read file '$fileName'!"); - } - } - } else { - if($this->toolbox->isFileLocation($location) && is_dir($this->getContentDirectory("$location/"))) - { - $statusCode = 301; - $this->sendStatus($statusCode, "Location: http://$_SERVER[SERVER_NAME]$baseLocation$location/"); - } else { - $statusCode = 404; - } - } - } - if($statusCode >= 400) - { - header($this->toolbox->getHttpStatusFormated($statusCode)); - header("Content-Type: text/html; charset=UTF-8"); - $fileName = $this->config->get("configDir").sprintf("error%d.txt", $statusCode); - $fileHandle = @fopen($fileName, "r"); - if($fileHandle) - { - $fileData = fread($fileHandle, filesize($fileName)); - fclose($fileHandle); - } else { - die("Configuration problem: Can't open file '$fileName'!"); - } - } - if($fileData != "") $this->sendPage($baseLocation, $location, $fileName, $fileData, $statusCode); - if(defined("DEBUG") && DEBUG>=1) echo "Yellow::processRequestFile base:$baseLocation, file:$fileName
\n"; - return $statusCode; - } - - // Send status response - function sendStatus($statusCode, $text = "") - { - header($this->toolbox->getHttpStatusFormated($statusCode)); - if($text != "") header($text); - } - - // Send page response - function sendPage($baseLocation, $location, $fileName, $fileData, $statusCode) - { - $this->pages = new Yellow_Pages($baseLocation, $this->toolbox, $this->config); + // Process request for a file + function processRequestFile($baseLocation, $location, $fileName, $statusCode, $cache = true) + { + if($statusCode == 0) + { + if(is_readable($fileName)) + { + $time = gmdate("D, d M Y H:i:s", filemtime($fileName))." GMT"; + if(isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) && $_SERVER["HTTP_IF_MODIFIED_SINCE"]==$time && $cache) + { + $statusCode = 304; + $this->sendStatus($statusCode); + } else { + $statusCode = 200; + header("Content-Type: text/html; charset=UTF-8"); + if($cache) + { + header("Last-Modified: ".$time); + } else { + header("Cache-Control: no-cache"); + header("Pragma: no-cache"); + header("Expires: 0"); + } + $fileHandle = @fopen($fileName, "r"); + if($fileHandle) + { + $fileData = fread($fileHandle, filesize($fileName)); + fclose($fileHandle); + } else { + die("Server problem: Can't read file '$fileName'!"); + } + } + } else { + if($this->toolbox->isFileLocation($location) && is_dir($this->getContentDirectory("$location/"))) + { + $statusCode = 301; + $this->sendStatus($statusCode, "Location: http://$_SERVER[SERVER_NAME]$baseLocation$location/"); + } else { + $statusCode = 404; + } + } + } + if($statusCode >= 400) + { + header($this->toolbox->getHttpStatusFormated($statusCode)); + header("Content-Type: text/html; charset=UTF-8"); + $fileName = str_replace("(.*)", $statusCode, $this->config->get("configDir").$this->config->get("errorPageFile")); + $fileHandle = @fopen($fileName, "r"); + if($fileHandle) + { + $fileData = fread($fileHandle, filesize($fileName)); + fclose($fileHandle); + } else { + die("Configuration problem: Can't open file '$fileName'!"); + } + } + if($fileData != "") $this->sendPage($baseLocation, $location, $fileName, $fileData, $statusCode); + if(defined("DEBUG") && DEBUG>=1) echo "Yellow::processRequestFile base:$baseLocation file:$fileName
\n"; + return $statusCode; + } + + // Send status response + function sendStatus($statusCode, $text = "") + { + header($this->toolbox->getHttpStatusFormated($statusCode)); + if($text != "") header($text); + } + + // Send page response + function sendPage($baseLocation, $location, $fileName, $fileData, $statusCode) + { + $this->pages = new Yellow_Pages($baseLocation, $this->toolbox, $this->config); $this->page = new Yellow_Page($baseLocation, $location, $fileName, $fileData, $this->toolbox, $this->config); - - $text = $this->page->getContentRawText(); + $this->text->setLanguage($this->page->get("language")); + + $text = $this->page->getContentRawText(); foreach($this->plugins->plugins as $key=>$value) - { + { if(method_exists($value["obj"], "onParseBefore")) - { + { $text = $value["obj"]->onParseBefore($text, $statusCode); - } - } - if(is_null($this->plugins->plugins[$this->page->get("parser")])) die("Parser '".$this->page->get("parser")."' does not exist!"); + } + } + if(!$this->plugins->isExisting($this->page->get("parser"))) die("Parser '".$this->page->get("parser")."' does not exist!"); $this->page->parser = $this->plugins->plugins[$this->page->get("parser")]["obj"]; $text = $this->page->parser->parse($text); foreach($this->plugins->plugins as $key=>$value) - { + { if(method_exists($value["obj"], "onParseAfter")) - { + { $text = $value["obj"]->onParseAfter($text, $statusCode); - } - } - $this->page->setContent($text); + } + } + $this->page->setContent($text); if(!$this->page->isExisting("description")) - { - $this->page->set("description", $this->toolbox->createTextDescription($this->page->getContent(), 150)); + { + $this->page->set("description", $this->toolbox->createTextDescription($this->page->getContent(), 150)); } - if(!$this->page->isExisting("keywords")) - { - $this->page->set("keywords", $this->toolbox->createTextKeywords($this->page->get("title"), 10)); - } - - $fileName = $this->config->get("templateDir").$this->page->get("template").$this->config->get("systemExtension"); - if(!is_file($fileName)) die("Template '".$this->page->get("template")."' does not exist!"); - global $yellow; - require($fileName); - } - - // Execute a template snippet - function snippet($snippet) - { - $fileName = $this->config->get("snippetDir").$snippet.$this->config->get("systemExtension"); - if(!is_file($fileName)) die("Snippet '$snippet' does not exist!"); + if(!$this->page->isExisting("keywords")) + { + $this->page->set("keywords", $this->toolbox->createTextKeywords($this->page->get("title"), 10)); + } + + $fileName = $this->config->get("templateDir").$this->page->get("template").$this->config->get("systemExtension"); + if(!is_file($fileName)) die("Template '".$this->page->get("template")."' does not exist!"); global $yellow; require($fileName); } - - // Return extra HTML header lines generated from plugins + + // Execute a template snippet + function snippet($snippet) + { + $fileName = $this->config->get("snippetDir").$snippet.$this->config->get("systemExtension"); + if(!is_file($fileName)) die("Snippet '$snippet' does not exist!"); + global $yellow; + require($fileName); + } + + // Return extra HTML header lines generated from plugins function getHeaderExtra() { - $header = ""; + $header = ""; foreach($this->plugins->plugins as $key=>$value) - { + { if(method_exists($value["obj"], "onHeaderExtra")) $header .= $value["obj"]->onHeaderExtra(); - } - return $header; + } + return $header; + } + + // Return content location for current HTTP request, without base location + function getRelativeLocation($baseLocation) + { + $location = $this->toolbox->getRequestLocation(); + $location = $this->toolbox->normaliseLocation($location); + $position = strlen($baseLocation); + return substr($location, $position); } - - // Return content location for current HTTP request, without base location - function getRelativeLocation($baseLocation) - { - $location = $this->toolbox->getRequestLocation(); - $location = $this->toolbox->normaliseLocation($location); - $position = strlen($baseLocation); - return substr($location, $position); - } // Return content file name from location function getContentFileName($location) { - return $this->toolbox->findFileFromLocation($location, - $this->config->get("contentDir"), $this->config->get("contentHomeDir"), - $this->config->get("contentDefaultFile"), $this->config->get("contentExtension")); + return $this->toolbox->findFileFromLocation($location, + $this->config->get("contentDir"), $this->config->get("contentHomeDir"), + $this->config->get("contentDefaultFile"), $this->config->get("contentExtension")); } - + // Return content directory from location function getContentDirectory($location) { - return $this->toolbox->findFileFromLocation($location, - $this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", ""); + return $this->toolbox->findFileFromLocation($location, + $this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", ""); } - - // Register plugin + + // Register plugin function registerPlugin($name, $class, $version) { - $this->plugins->register($name, $class, $version); + $this->plugins->register($name, $class, $version); } } // Yellow page data class Yellow_Page { - var $baseLocation; //base location - var $location; //page location - var $fileName; //content file name - var $parser; //content parser - var $metaData; //meta data of page - var $rawData; //raw data of page (unparsed) - var $rawTextPos; //raw text of page (unparsed) - var $active; //page is active? + var $baseLocation; //base location + var $location; //page location + var $fileName; //content file name + var $parser; //content parser + var $metaData; //meta data of page + var $rawData; //raw data of page (unparsed) + var $rawTextPos; //raw text of page (unparsed) + var $active; //page is active? + var $hidden; //page is hidden? - function __construct($baseLocation, $location, $fileName, $rawData, $toolbox, $config) - { - $this->baseLocation = $baseLocation; - $this->location = $location; - $this->fileName = $fileName; - $this->setRawData($rawData, $toolbox, $config); - $this->active = $toolbox->isActiveLocation($baseLocation, $location); - } - - // Set page raw data + function __construct($baseLocation, $location, $fileName, $rawData, $toolbox, $config) + { + $this->baseLocation = $baseLocation; + $this->location = $location; + $this->fileName = $fileName; + $this->setRawData($rawData, $toolbox, $config); + $this->active = $toolbox->isActiveLocation($baseLocation, $location); + $this->hidden = $toolbox->isHiddenLocation($baseLocation, $location, $fileName, $config->get("contentDir")); + } + + // Set page raw data function setRawData($rawData, $toolbox, $config) { - $this->metaData = array(); - $this->rawData = $rawData; - $this->rawTextPos = 0; - $this->set("title", $toolbox->createTextTitle($this->location)); + $this->metaData = array(); + $this->rawData = $rawData; + $this->rawTextPos = 0; + $this->set("title", $toolbox->createTextTitle($this->location)); $this->set("author", $config->get("author")); - $this->set("language", $config->get("language")); + $this->set("language", $config->get("language")); $this->set("parser", $config->get("parser")); $this->set("template", $config->get("template")); - + if(preg_match("/^(\-\-\-[\r\n]+)(.+?)([\r\n]+\-\-\-[\r\n]+)/s", $rawData, $parsed)) - { - $this->rawTextPos = strlen($parsed[1]) + strlen($parsed[2]) + strlen($parsed[3]); - preg_match_all("/([^\:\r\n]+)\s*\:\s*([^\r\n]+)/s", $parsed[2], $matches, PREG_SET_ORDER); - foreach($matches as $match) - { - $this->set(strtolower($match[1]), $match[2]); - } - } else if(preg_match("/^([^\r\n]+)([\r\n]+=+[\r\n]+)/", $rawData, $parsed)) { - $this->rawTextPos = strlen($parsed[1]) + strlen($parsed[2]); - $this->set("title", $parsed[1]); - } + { + $this->rawTextPos = strlen($parsed[1]) + strlen($parsed[2]) + strlen($parsed[3]); + preg_match_all("/([^\:\r\n]+)\s*\:\s*([^\r\n]+)/s", $parsed[2], $matches, PREG_SET_ORDER); + foreach($matches as $match) + { + $this->set(strtolower($match[1]), $match[2]); + } + } else if(preg_match("/^([^\r\n]+)([\r\n]+=+[\r\n]+)/", $rawData, $parsed)) { + $this->rawTextPos = strlen($parsed[1]) + strlen($parsed[2]); + $this->set("title", $parsed[1]); + } } - // Set page meta data + // Set page meta data function set($key, $value) { $this->metaData[$key] = $value; } - // Return page meta data + // Return page meta data function get($key) { - return is_null($this->metaData[$key]) ? "" : $this->metaData[$key]; + return $this->IsExisting($key) ? $this->metaData[$key] : ""; } - // Return page meta data, HTML encoded + // Return page meta data, HTML encoded function getHtml($key) { return htmlspecialchars($this->get($key)); } - // Return page title, HTML encoded - function getTitle() - { - return $this->getHtml("title"); - } - - // Set page content, HTML encoded - function setContent($html) - { - $this->parser->html = $html; - } - - // Return page content, HTML encoded - function getContent() - { - return $this->parser->html; - } - - // Return page content, raw text - function getContentRawText() - { - return substr($this->rawData, $this->rawTextPos); - } - - // Return absolut page location - function getLocation() - { - return $this->baseLocation.$this->location; - } - - // Check if meta data exists - function isExisting($key) - { - return !is_null($this->metaData[$key]); - } - - // Check if page is active - function isActive() - { - return $this->active; - } + // Return page title, HTML encoded + function getTitle() + { + return $this->getHtml("title"); + } + + // Set page content, HTML encoded + function setContent($html) + { + $this->parser->html = $html; + } + + // Return page content, HTML encoded + function getContent() + { + return $this->parser->html; + } + + // Return page content, raw text + function getContentRawText() + { + return substr($this->rawData, $this->rawTextPos); + } + + // Return absolut page location + function getLocation() + { + return $this->baseLocation.$this->location; + } + + // Check if meta data exists + function isExisting($key) + { + return !is_null($this->metaData[$key]); + } + + // Check if page is active + function isActive() + { + return $this->active; + } + + // Check if page is active + function isHidden() + { + return $this->hidden; + } } // Yellow page tree from file system class Yellow_Pages { - var $pages; //scanned pages - - function __construct($baseLocation, $toolbox, $config) + var $pages; //scanned pages + + function __construct($baseLocation, $toolbox, $config) { - $this->scan($baseLocation, $toolbox, $config); - } - - // Scan top-level pages - function scan($baseLocation, $toolbox, $config) - { - $this->pages = array(); - foreach($toolbox->getDirectoryEntries($config->get("contentDir"), "/^[\d\-\.]+(.*)$/", true) as $entry) - { - $fileName = $config->get("contentDir").$entry."/".$config->get("contentDefaultFile").$config->get("contentExtension"); - $location = $toolbox->findLocationFromFile($fileName, $config->get("contentDir"), $config->get("contentHomeDir"), - $config->get("contentDefaultFile"), $config->get("contentExtension")); - $fileHandle = @fopen($fileName, "r"); - if($fileHandle) - { - $fileData = fread($fileHandle, 4096); - fclose($fileHandle); - } else { - $fileData = ""; - } - $page = new Yellow_Page($baseLocation, $location, $fileName, $fileData, $toolbox, $config); - array_push($this->pages, $page); - } - } - - // Return top-level pages - function root() - { - return $this->pages; - } + $this->scan($baseLocation, $toolbox, $config); + } + + // Scan top-level pages + function scan($baseLocation, $toolbox, $config) + { + $this->pages = array(); + foreach($toolbox->getDirectoryEntries($config->get("contentDir"), "/.*/", true) as $entry) + { + $fileName = $config->get("contentDir").$entry."/".$config->get("contentDefaultFile"); + $location = $toolbox->findLocationFromFile($fileName, $config->get("contentDir"), $config->get("contentHomeDir"), + $config->get("contentDefaultFile"), $config->get("contentExtension")); + $fileHandle = @fopen($fileName, "r"); + if($fileHandle) + { + $fileData = fread($fileHandle, 4096); + fclose($fileHandle); + } else { + $fileData = ""; + } + $page = new Yellow_Page($baseLocation, $location, $fileName, $fileData, $toolbox, $config); + array_push($this->pages, $page); + } + } + + // Return top-level pages + function root($showHidden = false) + { + $pages = array(); + foreach($this->pages as $page) + { + if($showHidden || !$page->isHidden()) array_push($pages, $page); + } + return $pages; + } } - + // Yellow toolbox with helpers class Yellow_Toolbox { - // Return location from current HTTP request - static function getRequestLocation() - { - $uri = $_SERVER["REQUEST_URI"]; - return ($pos = strpos($uri, '?')) ? substr($uri, 0, $pos) : $uri; - } + // Return location from current HTTP request + static function getRequestLocation() + { + $uri = $_SERVER["REQUEST_URI"]; + return ($pos = strpos($uri, '?')) ? substr($uri, 0, $pos) : $uri; + } - // Return arguments from current HTTP request - static function getRequestLocationArguments() - { - $uri = $_SERVER["REQUEST_URI"]; - return ($pos = strpos($uri, '?')) ? substr($uri, $pos+1) : ""; - } - - // Return base location - static function getBaseLocation() - { - $baseLocation = "/"; - if(preg_match("/^(.*)\//", $_SERVER["SCRIPT_NAME"], $matches)) $baseLocation = $matches[1]; - return $baseLocation; - } - - // Normalise location and remove incorrect path tokens - static function normaliseLocation($location) - { - $str = str_replace('\\', '/', rawurldecode($location)); - $location = ($str[0]=='/') ? '' : '/'; - for($pos=0; $pos(.+?\<\/p\>)/s", $text, $parsedDescription, PREG_SET_ORDER); - foreach($parsedDescription as $matches) - { - preg_match_all("/([^\<]*)[^\>]+./s", $matches[1], $parsedUndoTag, PREG_SET_ORDER); - if(count($parsedUndoTag) > 0) - { - if(!empty($description)) $description .= " "; - foreach($parsedUndoTag as $matchTag) - { - $description .= preg_replace("/[\\x00-\\x1f]+/s", " ", $matchTag[1]); - } - } - if(strlen($description) > $lengthMax) - { - $description = substr($description, 0, $lengthMax-3)."..."; - break; - } - } - return $description; - } + // Create description from text + static function createTextDescription($text, $lengthMax) + { + $description = ""; + preg_match_all("/\(.+?\<\/p\>)/s", $text, $parsedDescription, PREG_SET_ORDER); + foreach($parsedDescription as $matches) + { + preg_match_all("/([^\<]*)[^\>]+./s", $matches[1], $parsedUndoTag, PREG_SET_ORDER); + if(count($parsedUndoTag) > 0) + { + if(!empty($description)) $description .= " "; + foreach($parsedUndoTag as $matchTag) + { + $description .= preg_replace("/[\\x00-\\x1f]+/s", " ", $matchTag[1]); + } + } + if(strlen($description) > $lengthMax) + { + $description = substr($description, 0, $lengthMax-3)."..."; + break; + } + } + return $description; + } - // Create keywords from text string - static function createTextKeywords($text, $keywordsMax) - { - $tokens = preg_split("/[,\s\(\)]/", strtolower($text)); - foreach($tokens as $key => $value) if(strlen($value) < 3) unset($tokens[$key]); - return implode(", ", array_slice(array_unique($tokens), 0, $keywordsMax)); - } - - // Create title from text string - static function createTextTitle($text) - { - if(preg_match("/^.*\/(\w*)/", $text, $matches)) $text = ucfirst($matches[1]); - return $text; - } + // Create keywords from text string + static function createTextKeywords($text, $keywordsMax) + { + $tokens = preg_split("/[,\s\(\)]/", strtolower($text)); + foreach($tokens as $key => $value) if(strlen($value) < 3) unset($tokens[$key]); + return implode(", ", array_slice(array_unique($tokens), 0, $keywordsMax)); + } + + // Create title from text string + static function createTextTitle($text) + { + if(preg_match("/^.*\/(\w*)/", $text, $matches)) $text = ucfirst($matches[1]); + return $text; + } + + // Detect web browser language + function detectBrowserLanguage($languagesAllowed, $languageDefault) + { + $language = $languageDefault; + if(isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) + { + foreach(preg_split("/,\s*/", $_SERVER["HTTP_ACCEPT_LANGUAGE"]) as $string) + { + $tokens = split(";", $string, 2); + if(in_array($tokens[0], $languagesAllowed)) + { + $language = $tokens[0]; + break; + } + } + } + return $language; + } - // Detect PNG and JPG image dimensions - static function detectImageDimensions($fileName) - { - $width = $height = 0; - $fileHandle = @fopen($fileName, "rb"); - if($fileHandle) - { - if(substr($fileName, -3) == "png") - { - $dataSignature = fread($fileHandle, 8); - $dataHeader = fread($fileHandle, 25); - 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]); - } - } else if(substr($fileName, -3) == "jpg") { - $dataSignature = fread($fileHandle, 11); - $dataHeader = fread($fileHandle, 147); - $dataHeader = fread($fileHandle, 16); - if(!feof($fileHandle) && $dataSignature=="\xff\xd8\xff\xe0\x00\x10JFIF\0") - { - $width = (ord($dataHeader[7])<<8) + ord($dataHeader[8]); - $height = (ord($dataHeader[5])<<8) + ord($dataHeader[6]); - } - } - fclose($fileHandle); - } - return array($width, $height); - } + // Detect PNG and JPG image dimensions + static function detectImageDimensions($fileName) + { + $width = $height = 0; + $fileHandle = @fopen($fileName, "rb"); + if($fileHandle) + { + if(substr($fileName, -3) == "png") + { + $dataSignature = fread($fileHandle, 8); + $dataHeader = fread($fileHandle, 25); + 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]); + } + } else if(substr($fileName, -3) == "jpg") { + $dataSignature = fread($fileHandle, 11); + $dataHeader = fread($fileHandle, 147); + $dataHeader = fread($fileHandle, 16); + if(!feof($fileHandle) && $dataSignature=="\xff\xd8\xff\xe0\x00\x10JFIF\0") + { + $width = (ord($dataHeader[7])<<8) + ord($dataHeader[8]); + $height = (ord($dataHeader[5])<<8) + ord($dataHeader[6]); + } + } + fclose($fileHandle); + } + return array($width, $height); + } - // Start timer - static function timerStart(&$time) - { - $time = microtime(true); - } - - // Stop timer and calcuate elapsed time (milliseconds) - static function timerStop(&$time) - { - $time = intval((microtime(true)-$time) * 1000); - } + // Start timer + static function timerStart(&$time) + { + $time = microtime(true); + } + + // Stop timer and calcuate elapsed time (milliseconds) + static function timerStop(&$time) + { + $time = intval((microtime(true)-$time) * 1000); + } } // Yellow configuration class Yellow_Config { - var $config; //configuration - var $configDefaults; //configuration defaults - - function __construct() - { - $this->config = array(); - $this->configDefaults = array(); - } - - // Load configuration 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*$/", $line, $matches); - if($matches[1]!="" && $matches[2]!="") - { - $this->set($matches[1], $matches[2]); - if(defined("DEBUG") && DEBUG>=3) echo "Yellow_Config::load key:$matches[1] $matches[2]
\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) - { - return is_null($this->config[$key]) ? $this->configDefaults[$key] : $this->config[$key]; - } - - // Return configuration, HTML encoded - function getHtml($key) - { - return htmlspecialchars($this->get($key)); - } - - // Check if configuration exists - function isExisting($key) - { - return !is_null($this->config[$key]); - } + var $config; //configuration + var $configDefaults; //configuration defaults + + function __construct() + { + $this->config = array(); + $this->configDefaults = array(); + } + + // Load configuration from file + function load($fileName) + { + $fileData = @file($fileName); + if($fileData) + { + if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Config::load file:$fileName
\n"; + foreach($fileData as $line) + { + if(preg_match("/^\//", $line)) continue; + preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches); + if($matches[1]!="" && $matches[2]!="") + { + $this->set($matches[1], $matches[2]); + if(defined("DEBUG") && DEBUG>=3) echo "Yellow_Config::load key:$matches[1] $matches[2]
\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) + { + return $this->isExisting($key) ? $this->config[$key] : $this->configDefaults[$key]; + } + + // Return configuration, HTML encoded + function getHtml($key) + { + return htmlspecialchars($this->get($key)); + } + + // Return configuration strings + function getData($filterEnd = "") + { + $config = array(); + if($filterEnd == "") + { + $config = $this->config; + } else { + foreach($this->config as $key=>$value) + { + if(substr($key, -strlen($filterEnd)) == $filterEnd) $config[$key] = $value; + } + } + return $config; + } + + // Check if configuration exists + function isExisting($key) + { + return !is_null($this->config[$key]); + } +} + +// Yellow text strings +class Yellow_Text +{ + var $text; //text strings + var $language; //current language + + function __construct() + { + $this->text = array(); + } + + // Load text strings from file + function load($fileName, $toolbox) + { + $path = dirname($fileName); + $regex = basename($fileName); + foreach($toolbox->getDirectoryEntries($path, "/$regex/", true, false) as $entry) + { + $fileData = @file("$path/$entry"); + if($fileData) + { + if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Text::load file:$path/$entry
\n"; + $language = ""; + foreach($fileData as $line) + { + preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches); + if($matches[1]=="language" && $matches[2]!="") { $language = $matches[2]; break; } + } + foreach($fileData as $line) + { + if(preg_match("/^\//", $line)) continue; + preg_match("/^\s*(.*?)\s*=\s*(.*?)\s*$/", $line, $matches); + if($language!="" && $matches[1]!="" && $matches[2]!="") + { + $this->setLanguageText($language, $matches[1], $matches[2]); + if(defined("DEBUG") && DEBUG>=3) echo "Yellow_Text::load key:$matches[1] $matches[2]
\n"; + } + } + } + } + } + + // Set current language + function setLanguage($language) + { + $this->language = $language; + } + + // Set text string + function setLanguageText($language, $key, $value) + { + if(is_null($this->text[$language])) $this->text[$language] = array(); + $this->text[$language][$key] = $value; + } + + // Return text string + function get($key) + { + return $this->isExisting($key) ? $this->text[$this->language][$key] : "[$key]"; + } + + // Return text string, HTML encoded + function getHtml($key) + { + return htmlspecialchars($this->get($key)); + } + + // Return text strings + function getData($language, $filterStart = "") + { + $text = array(); + if(!is_null($this->text[$language])) + { + if($filterStart == "") + { + $text = $this->text[$language]; + } else { + foreach($this->text[$language] as $key=>$value) + { + if(substr($key, 0, strlen("language")) == "language") $text[$key] = $value; + if(substr($key, 0, strlen($filterStart)) == $filterStart) $text[$key] = $value; + } + } + } + return $text; + } + + // Check if text string exists + function isExisting($key) + { + return !is_null($this->text[$this->language]) && !is_null($this->text[$this->language][$key]); + } } // Yellow plugins class Yellow_Plugins { - var $plugins; //registered plugins + var $plugins; //registered plugins - function __construct() - { - $this->plugins = array(); - } - - // Load plugins - function load() + function __construct() + { + $this->plugins = array(); + } + + // Load plugins + function load() { global $yellow; - require_once("core_markdown.php"); - require_once("core_rawhtml.php"); - foreach($yellow->toolbox->getDirectoryEntries($yellow->config->get("pluginDir")) as $entry) - { - $fileName = "$entry/plugin_$entry.php"; - if(is_file($fileName)) require_once($fileName); - } - foreach($this->plugins as $key=>$value) + require_once("core_markdown.php"); + require_once("core_rawhtml.php"); + require_once("core_webinterface.php"); + foreach($yellow->toolbox->getDirectoryEntries($yellow->config->get("pluginDir"), "/.*\.php/", true, false) as $entry) { - $this->plugins[$key]["obj"] = new $value["class"]; - if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Plugins::load class:$value[class] $value[version]
\n"; - if(method_exists($this->plugins[$key]["obj"], "initPlugin")) - { - $this->plugins[$key]["obj"]->initPlugin($yellow); - } + $fileName = $yellow->config->get("pluginDir")."/$entry"; + require_once($fileName); + } + foreach($this->plugins as $key=>$value) + { + $this->plugins[$key]["obj"] = new $value["class"]; + if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Plugins::load class:$value[class] $value[version]
\n"; + if(method_exists($this->plugins[$key]["obj"], "initPlugin")) + { + $this->plugins[$key]["obj"]->initPlugin($yellow); + } } } - - // Register plugin + + // Register plugin function register($name, $class, $version) { - if(is_null($this->plugins[$name])) - { - $this->plugins[$name] = array(); - $this->plugins[$name]["class"] = $class; - $this->plugins[$name]["version"] = $version; - } + if(!$this->isExisting($name)) + { + $this->plugins[$name] = array(); + $this->plugins[$name]["class"] = $class; + $this->plugins[$name]["version"] = $version; + } + } + + // Check if plugin exists + function isExisting($name) + { + return !is_null($this->plugins[$name]); } } ?> \ No newline at end of file diff --git a/system/core/core_markdown.php b/system/core/core_markdown.php index 9f1d028..8db6636 100755 --- a/system/core/core_markdown.php +++ b/system/core/core_markdown.php @@ -5,51 +5,60 @@ // Markdown parser core plugin class Yellow_Markdown { - var $markdown; //markdown parser - var $html; //generated HTML - - // Initialise plugin + const Version = "0.1.1"; + var $markdown; //markdown parser + var $html; //generated HTML + + // Initialise plugin function initPlugin($yellow) { $this->markdown = new Yellow_MarkdownExtraParser($yellow); } - - // Parse text + + // Parse text function parse($text) { return $this->html = $this->markdown->transform($text); } } - + require("markdown.php"); class Yellow_MarkdownExtraParser extends MarkdownExtra_Parser { - var $yellow; //access to API + var $yellow; //access to API - function __construct($yellow) - { - $this->yellow = $yellow; - parent::__construct(); - } - - // Handle images + function __construct($yellow) + { + $this->yellow = $yellow; + parent::__construct(); + } + + // Transform text + function transform($text) + { + $baseLocation = $this->yellow->config->get("baseLocation"); + $text = preg_replace("/@baseLocation/i", $baseLocation, $text); + return parent::transform($text); + } + + // Handle images function _doImages_inline_callback($matches) - { + { $path = $matches[3]=="" ? $matches[4] : $matches[3]; - $src = $this->yellow->config->get("baseLocation").$this->yellow->config->get("imagesLocation").$path; - list($width, $height) = $this->yellow->toolbox->detectImageDimensions(".".$this->yellow->config->get("imagesLocation").$path); + $src = $this->yellow->config->get("baseLocation").$this->yellow->config->get("imagesLocation").$path; + list($width, $height) = $this->yellow->toolbox->detectImageDimensions(".".$this->yellow->config->get("imagesLocation").$path); $alt = $matches[2]; - $title =& $matches[7]; - + $title = $matches[7]; + $result = "encodeAttribute($src)."\""; - if($width && $height) $result .= " width=\"$width\" height=\"$height\""; - if(isset($alt)) $result .= " alt=\"".$this->encodeAttribute($alt)."\""; + if($width && $height) $result .= " width=\"$width\" height=\"$height\""; + if(isset($alt)) $result .= " alt=\"".$this->encodeAttribute($alt)."\""; if(isset($title)) $result .= " title=\"".$this->encodeAttribute($title)."\""; $result .= $this->empty_element_suffix; - + return $this->hashPart($result); } } -$yellow->registerPlugin("markdown", "Yellow_Markdown", "0.1.0"); +$yellow->registerPlugin("markdown", "Yellow_Markdown", Yellow_Markdown::Version); ?> \ No newline at end of file diff --git a/system/core/core_rawhtml.php b/system/core/core_rawhtml.php index 8ef107b..43a6d50 100755 --- a/system/core/core_rawhtml.php +++ b/system/core/core_rawhtml.php @@ -5,14 +5,15 @@ // Raw HTML parser core plugin class Yellow_RawHtml { - var $html; //generated HTML + const Version = "0.1.1"; + var $html; //generated HTML - // Parse text, dummy transformation + // Parse text, dummy transformation function parse($text) { return $this->html = $text; } } -$yellow->registerPlugin("rawhtml", "Yellow_RawHtml", "0.1.0"); +$yellow->registerPlugin("rawhtml", "Yellow_RawHtml", Yellow_RawHtml::Version); ?> \ No newline at end of file diff --git a/system/core/core_webinterface.css b/system/core/core_webinterface.css new file mode 100644 index 0000000..08b2385 --- /dev/null +++ b/system/core/core_webinterface.css @@ -0,0 +1,25 @@ +.yellowlogin { width:600px; position:absolute; top:5px; padding:30px; border:1px solid #ccc; background:#fff; color:#000; } +.yellowlogin h1 { margin:0px; padding:0px; } +.yellowlogin p { margin:0.5em; text-align:right; } + +.yellowbar { width:600px; position:absolute; top:1px; background:#fff; color:#000; } +.yellowbar img { vertical-align:top; } +.yellowbar button { color:#05d; padding-left:0.5em; padding-right:0.5em; } +.yellowbar button:hover { color:#f00; } +.yellowbarleft { margin-left:0px; display:block; float:left; height:100%; } +.yellowbarright { margin-right:0px; display:block; float:right; height:100%; } +.yellowbarlink { cursor:pointer; font:inherit; background:none; border:none; margin:0px padding:0px; } +.yellowbubble { -webkit-border-radius:4px; -moz-border-radius:4px; border-radius:4px; } + +.yellowpane { position:absolute; display:none; margin:0px; padding:5px; border:solid 1px #ccc; background:#fff; color:#000; z-index:10; } +.yellowpane a { text-decoration:none; color:#000; } +.yellowpane a:hover { text-decoration:none; color:#f00; } +.yellowpane p { margin:0.5em; } +.yellowpane ul { list-style:none; margin:0em 0.5em; padding:0px; } + +#yellowpaneedit { } +#yellowpaneshow { min-width:250px; overflow:auto; } +#yellowpaneuser { } +#yellowedittext { margin:0px; margin-bottom:5px; padding:5px; border:solid 1px #ccc; resize:none; font-size:0.9em } +#yelloweditbuttons { margin-bottom:5px; width:100%; } +#yelloweditbuttons input { margin-left:5px; } \ No newline at end of file diff --git a/system/core/core_webinterface.js b/system/core/core_webinterface.js new file mode 100644 index 0000000..c35aae7 --- /dev/null +++ b/system/core/core_webinterface.js @@ -0,0 +1,243 @@ +// Copyright (c) 2013 Datenstrom, http://www.datenstrom.se +// This file may be used and distributed under the terms of the public license. + +// Yellow main API +var yellow = +{ + version: "0.0.0", //Hello web interface! + onClick: function(e) { yellow.webinterface.hidePanesOnClick(e); }, + onShow: function(id) { yellow.webinterface.showPane(id); }, + onReset: function(id) { yellow.webinterface.resetPane(id); }, + onResize: function() { yellow.webinterface.resizePanes(); }, + webinterface:{}, page:{}, pages:{}, toolbox:{}, config:{}, text:{} +} + +// Yellow web interface +yellow.webinterface = +{ + created: false, //interface created? (boolean) + timerId: 0, //interface timer ID + heightOld: 0, //height of big panes + + // Initialise web interface + init: function() + { + this.intervalId = window.setInterval("yellow.webinterface.create()", 1); + window.onresize = yellow.onResize; + window.onclick = yellow.onClick; + }, + + // Create action bar and panes + create: function() + { + var body = document.getElementsByTagName("body")[0]; + if(!body || !body.firstChild || this.created) return; + if(yellow.debug) console.log("yellow.webinterface.create email:"+yellow.config.userEmail+" "+yellow.config.userName); + if(yellow.config.userEmail) + { + var location = yellow.config.baseLocation+yellow.config.pluginsLocation; + var element = document.createElement("div"); + element.className = "yellowbar"; + element.setAttribute("id", "yellowbar"); + element.innerHTML = + "
"+ + " Yellow"+ + ""+ + ""+ + "
"+ + "
"+ + ""+ + "
"; + body.insertBefore(element, body.firstChild); + yellow.toolbox.insertAfter(this.createPane("yellowpaneedit"), body.firstChild); + yellow.toolbox.insertAfter(this.createPane("yellowpaneshow", yellow.pages), body.firstChild); + yellow.toolbox.insertAfter(this.createPane("yellowpaneuser"), body.firstChild); + yellow.toolbox.setText(document.getElementById("yellowusername"), yellow.config.userName+" ↓"); + yellow.toolbox.setText(document.getElementById("yellowedittext"), yellow.page.rawData); + } else { + var element = document.createElement("div"); + element.className = "yellowlogin yellowbubble"; + element.setAttribute("id", "yellowlogin"); + element.innerHTML = + "
"+ + ""+ + "

"+this.getText("LoginText")+"

"+ + "

"+this.getText("LoginEmail")+"

"+ + "

"+this.getText("LoginPassword")+"

"+ + "

"+ + "
"; + body.insertBefore(element, body.firstChild); + } + window.clearInterval(this.intervalId); + this.created = true; + this.resizePanes(true); + }, + + // Create pane + createPane: function (id, data) + { + if(yellow.debug) console.log("yellow.webinterface.createPane id:"+id); + var outDiv = document.createElement("div"); + if(id == "yellowpaneedit") + { + outDiv.innerHTML = + "

Editing page...

"+ + "
"+ + ""+ + ""+ + "
"+ + ""+ + ""+ + "
"+ + "
"; + } else if(id == "yellowpaneshow") { + outDiv.innerHTML = "

Showing files...

"; + for(var n in data) + { + var outUl = document.createElement("ul"); + var outLi = document.createElement("li"); + var outA = document.createElement("a"); + outA.setAttribute("href", data[n]["location"]); + yellow.toolbox.setText(outA, data[n]["title"]); + outLi.appendChild(outA); + outUl.appendChild(outLi); + outDiv.appendChild(outUl); + } + } else if(id == "yellowpaneuser") { + outDiv.innerHTML = + "

"+yellow.config.userEmail+"

"+ + "
"+ + ""+ + "

"+this.getText("UserLogout")+"

"+ + "
"; + } + var element = document.createElement("div"); + element.className = "yellowpane yellowbubble"; + element.setAttribute("id", id); + element.appendChild(outDiv); + return element; + }, + + // Reset pane + resetPane: function(id) + { + if(id == "yellowpaneedit") + { + document.formeditor.reset(); + yellow.toolbox.setText(document.getElementById("yellowedittext"), yellow.page.rawData); + this.hidePane(id); + } + }, + + // Show pane + showPane: function(id) + { + if(document.getElementById(id).style.display == "block") + { + this.hidePanes(); + } else { + this.hidePanes(); + if(yellow.debug) console.log("yellow.webinterface.showPane id:"+id); + document.getElementById(id).style.display = "block"; + this.resizePanes(true); + } + }, + + // Hide pane + hidePane: function(id) + { + if(document.getElementById(id)) document.getElementById(id).style.display = "none"; + }, + + // Hide all panes + hidePanes: function () + { + for(var element=document.getElementById("yellowbar"); element; element=element.nextSibling) + { + if(element.className && element.className.indexOf("yellowpane")>=0) + { + this.hidePane(element.getAttribute("id")); + } + } + }, + + // Hide all panes on mouse click + hidePanesOnClick: function(e) + { + var element = e.target || e.srcElement; + while(element = element.parentNode) + { + if(element.className) + { + if(element.className.indexOf("yellowpane")>=0 || element.className.indexOf("yellowbar")>=0) return; + } + } + this.hidePanes(); + }, + + // Resize panes, recalculate height and width where needed + resizePanes: function(force) + { + var interfaceHeight = Number(window.innerHeight); + if((interfaceHeight!=this.heightOld || force) && document.getElementById("yellowbar")) + { + this.heightOld = interfaceHeight; + var elementBar = document.getElementById("yellowbar"); + var borderRadius = 6; + var panePadding = 5; + var editPadding = 5; + var interfaceTop = elementBar.offsetHeight + 1; + interfaceHeight -= interfaceTop + borderRadius*2; + if(yellow.debug) console.log("yellow.webinterface.resizePanes windowY:"+interfaceHeight+" actionbarY:"+document.getElementById("yellowbar").offsetHeight+" buttonsY:"+document.getElementById("yelloweditbuttons").offsetHeight+" editorX:"+document.getElementById("yellowpaneedit").offsetWidth); + + this.setPaneHeight(document.getElementById("yellowpaneedit"), interfaceHeight, null, interfaceTop); + this.setPaneHeight(document.getElementById("yellowpaneshow"), null, interfaceHeight, interfaceTop); + this.setPaneHeight(document.getElementById("yellowpaneuser"), null, null, interfaceTop); + + var editTextHeight = interfaceHeight - panePadding*2 - editPadding*2 - 10 + - (document.getElementById("yellowedittext").offsetTop-document.getElementById("yellowpaneedit").getElementsByTagName("p")[0].offsetTop) + - document.getElementById("yelloweditbuttons").offsetHeight; + document.getElementById("yellowpaneedit").style.width = (elementBar.offsetWidth - panePadding*2).toString()+"px"; + document.getElementById("yellowedittext").style.height = editTextHeight.toString()+"px"; + document.getElementById("yellowedittext").style.width = (document.getElementById("yellowpaneedit").offsetWidth - 2 - panePadding*2 - editPadding*2).toString()+"px"; + document.getElementById("yellowpaneuser").style.marginLeft = (elementBar.offsetWidth - document.getElementById("yellowpaneuser").offsetWidth).toString()+"px"; + } + }, + + // Set pane height + setPaneHeight: function(element, height, maxHeight, top) + { + if(maxHeight) + { + element.style.maxHeight = maxHeight.toString()+"px"; + } else if(height) { + element.style.height = height.toString()+"px"; + } + element.style.top = top+"px"; + }, + + // Return text string + getText: function(key) + { + return ("webinterface"+key in yellow.text) ? yellow.text["webinterface"+key] : "[webinterface"+key+"]"; + } +} + +// Yellow toolbox with helpers +yellow.toolbox = +{ + // Set element text + setText: function(element, text) + { + while(element.firstChild!==null) element.removeChild(element.firstChild); + element.appendChild(document.createTextNode(text)); + }, + + // Insert element after element + insertAfter: function(newElement, referenceElement) + { + referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling); + } +} + +yellow.webinterface.init(); \ No newline at end of file diff --git a/system/core/core_webinterface.php b/system/core/core_webinterface.php new file mode 100755 index 0000000..1bd2c9b --- /dev/null +++ b/system/core/core_webinterface.php @@ -0,0 +1,317 @@ +yellow = $yellow; + $this->yellow->config->setDefault("webinterfaceLocation", "/wiki/"); + $this->yellow->config->setDefault("webinterfaceUserFile", "user.ini"); + $this->users = new Yellow_WebinterfaceUsers(); + $this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile")); + } + + // Handle web interface location + function onRequest($baseLocation, $location, $fileName) + { + $statusCode = 0; + if($this->checkWebinterfaceLocation($location)) + { + $baseLocation .= rtrim($this->yellow->config->get("webinterfaceLocation"), '/'); + $location = $this->yellow->getRelativeLocation($baseLocation); + $fileName = $this->yellow->getContentFileName($location); + if($this->checkUser()) $statusCode = $this->processRequestAction($baseLocation, $location, $fileName); + if($statusCode == 0) $statusCode = $this->yellow->processRequestFile($baseLocation, $location, $fileName, + $this->activeUserFail ? 401 : 0, false); + } else { + if($this->yellow->config->get("webinterfaceLocation") == "$location/") + { + $statusCode = 301; + $this->yellow->sendStatus($statusCode, "Location: http://$_SERVER[SERVER_NAME]$baseLocation$location/"); + } + } + return $statusCode; + } + + // Handle extra HTML header lines + function onHeaderExtra() + { + $header = ""; + if($this->isWebinterfaceLocation()) + { + $location = $this->yellow->config->getHtml("baseLocation").$this->yellow->config->getHtml("pluginsLocation"); + $language = $this->isUser() ? $this->users->getLanguage($this->activeUserEmail) : $this->yellow->page->get("language"); + $header .= "\n"; + $header .= "\n"; + $header .= "\n"; + } + return $header; + } + + // Handle page before parser + function onParseBefore($text, $statusCode) + { + if($this->isWebinterfaceLocation() && $this->isUser()) + { + if($statusCode == 424) + { + $this->yellow->page->rawData = "---\r\n"; + $this->yellow->page->rawData .= "Title: ".$this->yellow->text->get("webinterface424Title")."\r\n"; + $this->yellow->page->rawData .= "Author: ".$this->users->getName($this->activeUserEmail)."\r\n"; + $this->yellow->page->rawData .= "---\r\n"; + $this->yellow->page->rawData .= $this->yellow->text->get("webinterface424Text"); + } + } + return $text; + } + + // Handle page after parser + function onParseAfter($text, $statusCode) + { + if($this->isWebinterfaceLocation() && $this->isUser()) + { + $this->yellow->toolbox->timerStart($time); + $baseLocation = $this->yellow->config->get("baseLocation"); + $webinterfaceLocation = rtrim($this->yellow->config->get("webinterfaceLocation"), '/'); + $text = preg_replace("##", + "", $text); + } + return $text; + } + + // Process request for an action + function processRequestAction($baseLocation, $location, $fileName) + { + $statusCode = 0; + if($_POST["action"] == "edit") + { + if(strlen($_POST["rawdata"])) + { + $fileHandle = @fopen($fileName, "w"); + if($fileHandle) + { + fwrite($fileHandle, $_POST["rawdata"]); + fclose($fileHandle); + } else { + die("Configuration problem: Can't write page '$fileName'!"); + } + } + } else if($_POST["action"]== "logout") { + $this->users->destroyCookie("login"); + $this->activeUserEmail = ""; + $statusCode = 302; + $newLocation = $this->yellow->config->getHtml("baseLocation").$location; + $this->yellow->sendStatus($statusCode, "Location: http://$_SERVER[SERVER_NAME]$newLocation"); + } else { + if(!is_readable($fileName)) + { + if($this->yellow->toolbox->isFileLocation($location) && is_dir($this->yellow->getContentDirectory("$location/"))) + { + $statusCode = 301; + $this->yellow->sendStatus($statusCode, "Location: http://$_SERVER[SERVER_NAME]$baseLocation$location/"); + } else { + $statusCode = $this->checkUserPermissions($location, $fileName) ? 424 : 404; + $this->yellow->processRequestFile($baseLocation, $location, $fileName, $statusCode, false); + } + } + } + return $statusCode; + } + + // Check web interface location + function checkWebinterfaceLocation($location) + { + $locationLength = strlen($this->yellow->config->get("webinterfaceLocation")); + $this->activeLocation = substr($location, 0, $locationLength) == $this->yellow->config->get("webinterfaceLocation"); + return $this->isWebinterfaceLocation(); + } + + // Check user login + function checkUser() + { + if($_POST["action"] == "login") + { + $email = $_POST["email"]; + $password = $_POST["password"]; + if($this->users->checkUser($email, $password)) + { + $this->users->createCookie("login", $email); + $this->activeUserEmail = $email; + } else { + $this->activeUserFail = true; + } + } else if(isset($_COOKIE["login"])) { + $cookie = $_COOKIE["login"]; + if($this->users->checkCookie($cookie)) + { + $this->activeUserEmail = $this->users->getCookieEmail($cookie); + } else { + $this->activeUserFail = true; + } + } + return $this->isUser(); + } + + // Check users permissions for creating new page + function checkUserPermissions($location, $fileName) + { + $path = dirname($fileName); + return is_dir($path); + } + + // Check if web interface location + function isWebinterfaceLocation() + { + return $this->activeLocation; + } + + // Check if user is logged in + function isUser() + { + return !empty($this->activeUserEmail); + } + + // Return page tree with content/media information + function getPagesData() + { + $data = array(); + foreach($this->yellow->pages->root(true) as $page) + { + $data[$page->fileName] = array(); + $data[$page->fileName]["location"] = $page->getLocation(); + $data[$page->fileName]["title"] = $page->getTitle(); + } + return $data; + } + + // Return configuration data including user information + function getConfigData($email) + { + $data = array("userEmail" => $email, + "userName" => $this->users->getName($email), + "userLanguage" => $this->users->getLanguage($email), + "baseLocation" => $this->yellow->config->get("baseLocation")); + return array_merge($data, $this->yellow->config->getData("Location")); + } +} + +// Yellow web interface users +class Yellow_WebinterfaceUsers +{ + var $users; //registered users + + function __construct() + { + $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*$/", $line, $matches); + if($matches[1]!="" && $matches[2]!="" && $matches[3]!="" && $matches[4]!="") + { + $this->setUser($matches[1], $matches[2], $matches[3], $matches[4]); + if(defined("DEBUG") && DEBUG>=3) echo "Yellow_WebinterfaceUsers::load email:$matches[1] $matches[3]
\n"; + } + } + } + } + + // Set user data + function setUser($email, $password, $name, $language) + { + $this->users[$email] = array(); + $this->users[$email]["email"] = $email; + $this->users[$email]["password"] = $password; + $this->users[$email]["name"] = $name; + $this->users[$email]["language"] = $language; + $this->users[$email]["session"] = hash("sha256", $email.$password.strrev($email.$password)); + } + + // Check user login + function checkUser($email, $password) + { + return $this->isExisting($email) && hash("sha256", $email.$password)==$this->users[$email]["password"]; + } + + // Create browser cookie + function createCookie($cookieName, $email) + { + if($this->isExisting($email)) + { + $salt = hash("sha256", uniqid(mt_rand(), true)); + $text = $email.";".$salt.";".hash("sha256", $salt.$this->users[$email]["session"]); + setcookie($cookieName, $text, time()+60*60*24*30*365*10, "/") || die("Server problem: Can't create '$cookieName' cookie!"); + } + } + + // Destroy browser cookie + function destroyCookie($cookieName) + { + setcookie($cookieName, "", time()-3600, "/"); + } + + // Check user login from browser cookie + function checkCookie($cookie) + { + list($email, $salt, $session) = explode(";", $cookie); + return $this->isExisting($email) && hash("sha256", $salt.$this->users[$email]["session"])==$session; + } + + // Return user email from browser cookie + function getCookieEmail($cookie) + { + list($email, $salt, $session) = explode(";", $cookie); + return $email; + } + + // Return user name + function getName($email) + { + return $this->isExisting($email) ? $this->users[$email]["name"] : ""; + } + + // Return user language + function getLanguage($email) + { + return $this->isExisting($email) ? $this->users[$email]["language"] : ""; + } + + // Check if user exists + function isExisting($email) + { + return !is_null($this->users[$email]); + } +} + +$yellow->registerPlugin("webinterface", "Yellow_Webinterface", Yellow_Webinterface::Version); +?> \ No newline at end of file diff --git a/system/core/core_webinterface.png b/system/core/core_webinterface.png new file mode 100644 index 0000000..a33c131 Binary files /dev/null and b/system/core/core_webinterface.png differ diff --git a/system/plugins/example.php b/system/plugins/example.php new file mode 100755 index 0000000..d85af99 --- /dev/null +++ b/system/plugins/example.php @@ -0,0 +1,14 @@ +registerPlugin("example", "Yellow_ExamplePlugin", Yellow_ExamplePlugin::Version); +?> \ No newline at end of file