toolbox = new Yellow_Toolbox(); $this->config = new Yellow_Config(); $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("style", "default"); $this->config->setDefault("yellowVersion", Yellow::Version); $this->config->setDefault("serverName", $this->toolbox->getServerName()); $this->config->setDefault("baseLocation", $this->toolbox->getServerBase()); $this->config->setDefault("styleLocation", "/media/styles/"); $this->config->setDefault("imageLocation", "/media/images/"); $this->config->setDefault("pluginLocation", "media/plugins/"); $this->config->setDefault("systemDir", "system/"); $this->config->setDefault("configDir", "system/config/"); $this->config->setDefault("pluginDir", "system/plugins/"); $this->config->setDefault("snippetDir", "system/snippets/"); $this->config->setDefault("templateDir", "system/templates/"); $this->config->setDefault("styleDir", "media/styles/"); $this->config->setDefault("imageDir", "media/images/"); $this->config->setDefault("contentDir", "content/"); $this->config->setDefault("contentHomeDir", "1-home/"); $this->config->setDefault("contentDefaultFile", "page.txt"); $this->config->setDefault("contentExtension", ".txt"); $this->config->setDefault("configExtension", ".ini"); $this->config->setDefault("configFile", "config.ini"); $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 and handle request function request() { $this->toolbox->timerStart($time); $this->processRequest(); $this->toolbox->timerStop($time); if(defined("DEBUG") && DEBUG>=1) echo "Yellow::request time:$time ms
\n"; } // Process request function processRequest() { $statusCode = 0; $baseLocation = $this->config->get("baseLocation"); $location = $this->getRelativeLocation($baseLocation); $fileName = $this->getContentFileName($location); 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"; } // 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; $serverName = $this->config->get("serverName"); $this->sendStatus($statusCode, "Location: http://$serverName$baseLocation$location/"); } else { $statusCode = 404; } } } if($statusCode >= 400) { header($this->toolbox->getHttpStatusFormated($statusCode)); header("Content-Type: text/html; charset=UTF-8"); $fileName = strreplaceu("(.*)", $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(!empty($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(!empty($text)) header($text); } // Send page response function sendPage($baseLocation, $location, $fileName, $fileData, $statusCode) { $this->pages = new Yellow_Pages($baseLocation, $this->toolbox, $this->config, $this->plugins); $this->page = new Yellow_Page($baseLocation, $location, $fileName, $fileData, $this->pages, true); $this->text->setLanguage($this->page->get("language")); $fileName = $this->config->get("styleDir").$this->page->get("style").".css"; if(!is_file($fileName)) die("Style '".$this->page->get("style")."' does not exist!"); $fileName = $this->config->get("templateDir").$this->page->get("template").".php"; if(!is_file($fileName)) die("Template '".$this->page->get("template")."' does not exist!"); global $yellow; require($fileName); } // Execute a template snippet function snippet($name, $args = NULL) { $this->page->args = func_get_args(); $fileName = $this->config->get("snippetDir")."$name.php"; if(!is_file($fileName)) die("Snippet '$name' does not exist!"); global $yellow; require($fileName); } // Return template snippet arguments function getSnippetArgs() { return $this->page->args; } // Return extra HTML header lines generated from plugins function getHeaderExtra() { $header = ""; foreach($this->plugins->plugins as $key=>$value) { if(method_exists($value["obj"], "onHeaderExtra")) $header .= $value["obj"]->onHeaderExtra(); } return $header; } // Return content location for current HTTP request, without base location function getRelativeLocation($baseLocation) { $location = $this->toolbox->getRequestLocation(); $location = $this->toolbox->normaliseLocation($location); return substru($location, strlenu($baseLocation)); } // 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 content directory from location function getContentDirectory($location) { return $this->toolbox->findFileFromLocation($location, $this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", ""); } // Execute a plugin command function plugin($name, $args = NULL) { $statusCode = 0; if(!$this->plugins->isExisting($name)) die("Pluggin '$name' does not exist!"); $plugin = $this->plugins->plugins[$name]; if(method_exists($plugin["obj"], "onCommand")) $statusCode = $plugin["obj"]->onCommand(func_get_args()); return $statusCode; } // Register plugin function registerPlugin($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 $rawTextOffsetBytes; //raw text of page (unparsed) var $args; //arguments for template snippet var $pages; //access to file system var $active; //page is active? var $hidden; //page is hidden in navigation? function __construct($baseLocation, $location, $fileName, $rawData, $pages, $parseContent = false) { $this->baseLocation = $baseLocation; $this->location = $location; $this->fileName = $fileName; $this->setRawData($rawData, $pages->toolbox, $pages->config); $this->pages = $pages; $this->active = $pages->toolbox->isActiveLocation($baseLocation, $location); $this->hidden = $pages->toolbox->isHiddenLocation($baseLocation, $location, $fileName, $pages->config->get("contentDir")); if($parseContent) $this->parseContent(); } // Set page raw data function setRawData($rawData, $toolbox, $config) { $this->metaData = array(); $this->rawData = $rawData; $this->rawTextOffsetBytes = 0; $this->set("title", $toolbox->createTextTitle($this->location)); $this->set("author", $config->get("author")); $this->set("language", $config->get("language")); $this->set("parser", $config->get("parser")); $this->set("template", $config->get("template")); $this->set("style", $config->get("style")); if(preg_match("/^(\-\-\-[\r\n]+)(.+?)([\r\n]+\-\-\-[\r\n]+)/s", $rawData, $parsed)) { $this->rawTextOffsetBytes = strlenb($parsed[0]); preg_match_all("/([^\:\r\n]+)\s*\:\s*([^\r\n]+)/s", $parsed[2], $matches, PREG_SET_ORDER); foreach($matches as $match) $this->set(strtoloweru($match[1]), $match[2]); } else if(preg_match("/^([^\r\n]+)([\r\n]+=+[\r\n]+)/", $rawData, $parsed)) { $this->rawTextOffsetBytes = strlenb($parsed[0]); $this->set("title", $parsed[1]); } } // Parse page content on demand function parseContent() { if(empty($this->parser->html)) { if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Page::parseContent location:".$this->location."
\n"; $text = $this->getContentRawText(); foreach($this->pages->plugins->plugins as $key=>$value) { if(method_exists($value["obj"], "onParseBefore")) $text = $value["obj"]->onParseBefore($text, $statusCode); } if(!$this->pages->plugins->isExisting($this->get("parser"))) die("Parser '".$this->get("parser")."' does not exist!"); $this->parser = $this->pages->plugins->plugins[$this->get("parser")]["obj"]; $text = $this->parser->parse($text); foreach($this->pages->plugins->plugins as $key=>$value) { if(method_exists($value["obj"], "onParseAfter")) $text = $value["obj"]->onParseAfter($text, $statusCode); } $this->setContent($text); if(!$this->isExisting("description")) { $this->set("description", $this->pages->toolbox->createTextDescription($this->getContent(), 150)); } if(!$this->isExisting("keywords")) { $this->set("keywords", $this->pages->toolbox->createTextKeywords($this->get("title"), 10)); } } } // 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 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() { $this->parseContent(); return $this->parser->html; } // Return page content, raw text function getContentRawText() { return substrb($this->rawData, $this->rawTextOffsetBytes); } // Return absolut page location function getLocation() { return $this->baseLocation.$this->location; } // Return page modification time (Unix time UTC) function getModified() { return filemtime($this->fileName); } // Return child pages relative to current page function getChildren($hidden = false) { return $this->pages->findChildren($this->location, $hidden); } // Return pages on the same level as current page function getSiblings($hidden = false) { $parentLocation = $this->pages->getParentLocation($this->location); return $this->pages->findChildren($parentLocation, $hidden); } // Return parent page relative to current page function getParent() { $parentLocation = $this->pages->getParentLocation($this->location); return $this->pages->findPage($parentLocation); } // Check if meta data exists function isExisting($key) { return !is_null($this->metaData[$key]); } // Check if page is within current HTTP request function isActive() { return $this->active; } // Check if page is hidden in navigation function isHidden() { return $this->hidden; } } // Yellow page collection as array class Yellow_PageCollection extends ArrayObject { var $baseLocation; //base location var $location; //collection location var $paginationPage; //current page number in pagination var $paginationCount; //highest page number in pagination var $toolbox; //access to toolbox function __construct($input, $baseLocation, $location, $toolbox) { parent::__construct($input); $this->baseLocation = $baseLocation; $this->location = $location; $this->toolbox = $toolbox; } // Filter page collection by meta data function filter($key, $value, $exactMatch = true) { if(!empty($key)) { $array = array(); $value = strtoloweru($value); $valueLength = strlenu($value); foreach($this->getArrayCopy() as $page) { if($page->isExisting($key)) { foreach(preg_split("/,\s*/", strtoloweru($page->get($key))) as $valuePage) { $length = $exactMatch ? strlenu($valuePage) : $valueLength; if($value == substru($valuePage, 0, $length)) array_push($array, $page); } } } $this->exchangeArray($array); } return $this; } // Reverse page collection function reverse($entriesMax = 0) { $this->exchangeArray(array_reverse($this->getArrayCopy())); return $this; } // Paginate page collection function pagination($limit, $reverse = true) { $array = $this->getArrayCopy(); if($reverse) $array = array_reverse($array); $this->paginationPage = 1; $this->paginationCount = ceil($this->count() / $limit); if($limit < $this->count() && isset($_REQUEST["page"])) $this->paginationPage = max(1, $_REQUEST["page"]); $this->exchangeArray(array_slice($array, ($this->paginationPage - 1) * $limit, $limit)); return $this; } // Return current page number in pagination function getPaginationPage() { return $this->$paginationPage; } // Return highest page number in pagination function getPaginationCount() { return $this->paginationCount; } // Return absolut location for a page in pagination function getLocationPage($pageNumber) { if($pageNumber>=1 && $pageNumber<=$this->paginationCount) { $locationArgs = $this->toolbox->getRequestLocationArgs($pageNumber>1 ? "page:$pageNumber" : "page:"); $location = $this->baseLocation.$this->location.$locationArgs; } return $location; } // Return absolut location for previous page in pagination function getLocationPrevious() { $pageNumber = $this->paginationPage; $pageNumber = ($pageNumber>1 && $pageNumber<=$this->paginationCount) ? $pageNumber-1 : 0; return $this->getLocationPage($pageNumber); } // Return absolut location for next page in pagination function getLocationNext() { $pageNumber = $this->paginationPage; $pageNumber = ($pageNumber>=1 && $pageNumber<$this->paginationCount) ? $pageNumber+1 : 0; return $this->getLocationPage($pageNumber); } // Check if there is an active pagination function isPagination() { return $this->paginationCount > 1; } } // Yellow page tree from file system class Yellow_Pages { var $baseLocation; //base location var $pages; //scanned pages var $toolbox; //access to toolbox var $config; //access to configuration var $plugins; //access to plugins function __construct($baseLocation, $toolbox, $config, $plugins) { $this->baseLocation = $baseLocation; $this->pages = array(); $this->toolbox = $toolbox; $this->config = $config; $this->plugins = $plugins; } // Return top-level navigation pages function root($hidden = false) { return $this->findChildren("", $hidden); } // Return child pages for a location function findChildren($location, $hidden = false) { $pages = new Yellow_PageCollection(array(), $this->baseLocation, $location, $this->toolbox); $this->scanChildren($location); foreach($this->pages[$location] as $page) { if($hidden || !$page->isHidden()) $pages->append($page); } return $pages; } // Return page for a location, false if not found function findPage($location) { $parentLocation = $this->getParentLocation($location); $this->scanChildren($parentLocation); foreach($this->pages[$parentLocation] as $page) { if($this->baseLocation.$location == $page->getLocation()) return $page; } return false; } // Scan child pages on demand function scanChildren($location) { if(is_null($this->pages[$location])) { if(defined("DEBUG") && DEBUG>=2) echo "Yellow_Pages::scanChildren location:$location
\n"; $this->pages[$location] = array(); $path = $this->config->get("contentDir"); if(!empty($location)) { $path = $this->toolbox->findFileFromLocation($location, $this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", ""); } $fileNames = array(); foreach($this->toolbox->getDirectoryEntries($path, "/.*/", true) as $entry) { array_push($fileNames, $path.$entry."/".$this->config->get("contentDefaultFile")); } $fileRegex = "/.*\\".$this->config->get("contentExtension")."/"; foreach($this->toolbox->getDirectoryEntries($path, $fileRegex, true, false) as $entry) { if($entry == $this->config->get("contentDefaultFile")) continue; array_push($fileNames, $path.$entry); } foreach($fileNames as $fileName) { $childLocation = $this->toolbox->findLocationFromFile($fileName, $this->config->get("contentDir"), $this->config->get("contentHomeDir"), $this->config->get("contentDefaultFile"), $this->config->get("contentExtension")); $fileHandle = @fopen($fileName, "r"); if($fileHandle) { $fileData = fread($fileHandle, 4096); fclose($fileHandle); } else { $fileData = ""; } $page = new Yellow_Page($this->baseLocation, $childLocation, $fileName, $fileData, $this); array_push($this->pages[$location], $page); } } } // Return parent navigation location function getParentLocation($location) { $parentLocation = ""; if(preg_match("/^(.*\/)(.+?)$/", $location, $matches)) { $parentLocation = $matches[1]!="/" ? $matches[1] : ""; } return $parentLocation; } } // Yellow toolbox with helpers class Yellow_Toolbox { // Return server name from current HTTP request static function getServerName() { return $_SERVER["SERVER_NAME"]; } // Return server base from current HTTP request static function getServerBase() { $serverBase = "/"; if(preg_match("/^(.*)\//", $_SERVER["SCRIPT_NAME"], $matches)) $serverBase = $matches[1]; return $serverBase; } // Return location from current HTTP request static function getRequestLocation() { $uri = $_SERVER["REQUEST_URI"]; return ($pos = strposu($uri, '?')) ? substru($uri, 0, $pos) : $uri; } // Return arguments from current HTTP request static function getRequestLocationArgs($arg = "", $encodeArgs = true) { preg_match("/^(.*?):(.*)$/", $arg, $args); if(preg_match("/^(.*?\/)(\w+:.*)$/", rawurldecode(self::getRequestLocation()), $matches)) { foreach(explode('/', $matches[2]) as $token) { preg_match("/^(.*?):(.*)$/", $token, $matches); if($matches[1] == $args[1]) { $matches[2] = $args[2]; $found = true; } if(!empty($matches[1]) && !empty($matches[2])) { if(!empty($locationArgs)) $locationArgs .= '/'; $locationArgs .= "$matches[1]:$matches[2]"; } } } if(!$found && !empty($args[1]) && !empty($args[2])) { if(!empty($locationArgs)) $locationArgs .= '/'; $locationArgs .= "$args[1]:$args[2]"; } if($encodeArgs) { $locationArgs = rawurlencode($locationArgs); $locationArgs = strreplaceu(array('%3A','%2F'), array(':','/'), $locationArgs); } return $locationArgs; } // Normalise location and remove unwanted path tokens static function normaliseLocation($location, $removeArgs = true) { $string = strreplaceu('\\', '/', rawurldecode($location)); $location = ($string[0]=='/') ? '' : '/'; for($pos=0; $pos