Browse Source

Core update (sinus iridum remix)

markseu 11 năm trước cách đây
mục cha
commit
c352b1e5d4

+ 72 - 27
system/core/core-commandline.php

@@ -5,7 +5,7 @@
 // Command line core plugin
 class YellowCommandline
 {
-	const Version = "0.2.1";
+	const Version = "0.2.2";
 	var $yellow;			//access to API
 
 	// Initialise plugin
@@ -17,41 +17,49 @@ class YellowCommandline
 		$this->yellow->config->setDefault("commandBuildCustomErrorFile", "error404.html");
 	}
 	
+	// Handle command help
+	function onCommandHelp()
+	{
+		$help .= "version\n";
+		$help .= "build DIRECTORY [LOCATION]\n";
+		return $help;
+	}
+	
 	// Handle command
 	function onCommand($args)
 	{
 		list($name, $command) = $args;
 		switch($command)
 		{
-			case "build":	$statusCode = $this->build($args); break;
-			case "version":	$statusCode = $this->version(); break;
-			default:		$statusCode = $this->help();
+			case "":		$statusCode = $this->helpCommand(); break;
+			case "version":	$statusCode = $this->versionCommand(); break;
+			case "build":	$statusCode = $this->buildCommand($args); break;
+			default:		$statusCode = $this->pluginCommand($args); break;
 		}
 		return $statusCode;
 	}
 	
 	// Show available commands
-	function help()
+	function helpCommand()
 	{
 		echo "Yellow command line ".YellowCommandline::Version."\n";
-		echo "Syntax: yellow.php build DIRECTORY [LOCATION]\n";
-		echo "        yellow.php version\n";
-		return 0;
+		foreach($this->getCommandHelp() as $line) echo (++$lineCounter>1 ? "        " : "Syntax: ")."yellow.php $line\n";
+		return 200;
 	}
 	
 	// Show software version
-	function version()
+	function versionCommand()
 	{
 		echo "Yellow ".Yellow::Version."\n";
 		foreach($this->yellow->plugins->plugins as $key=>$value) echo "$value[class] $value[version]\n";
-		return 0;
+		return 200;
 	}
 	
 	// Build website
-	function build($args)
+	function buildCommand($args)
 	{
 		$statusCode = 0;
-		list($name, $command, $path, $location) = $args;
+		list($dummy, $command, $path, $location) = $args;
 		if(!empty($path) && $path!="/")
 		{
 			if($this->yellow->config->isExisting("serverName"))
@@ -64,11 +72,12 @@ class YellowCommandline
 				$fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("configFile");
 				echo "ERROR bulding website: Please configure serverName and serverBase in file '$fileName'!\n";
 			}
-			echo "Yellow build: $content content, $media media, $system system";
+			echo "Yellow $command: $content content, $media media, $system system";
 			echo ", $error error".($error!=1 ? 's' : '');
 			echo ", status $statusCode\n";
 		} else {
-			echo "Yellow build: Invalid arguments\n";
+			echo "Yellow $command: Invalid arguments\n";
+			$statusCode = 400;
 		}
 		return $statusCode;
 	}
@@ -87,7 +96,8 @@ class YellowCommandline
 				".", "/.*\\".$this->yellow->config->get("commandBuildCustomMediaExtension")."/", false, false));
 			$fileNamesSystem = array($this->yellow->config->get("commandBuildCustomErrorFile"));
 		} else {
-			$pages = new YellowPageCollection($this->yellow, $location);
+			if($location[0] != '/') $location = '/'.$location;
+			$pages = new YellowPageCollection($this->yellow);
 			$pages->append(new YellowPage($this->yellow, $location));
 			$fileNamesMedia = array();
 			$fileNamesSystem = array();
@@ -150,18 +160,18 @@ class YellowCommandline
 				$fileName = $this->getStaticFileName($location, $path);
 				$fileData = ob_get_contents();
 				if($statusCode>=301 && $statusCode<=303) $fileData = $this->getStaticRedirect($this->yellow->page->getHeader("Location"));
-				$fileOk = $this->makeStaticFile($fileName, $fileData, $modified);
+				$fileOk = $this->createStaticFile($fileName, $fileData, $modified);
 			} else {
 				if(!$this->yellow->toolbox->isFileLocation($location))
 				{
 					$fileName = $this->getStaticFileName($location, $path);
 					$fileData = $this->getStaticRedirect("http://$serverName$serverBase$staticLocation");
-					$fileOk = $this->makeStaticFile($fileName, $fileData, $modified);
+					$fileOk = $this->createStaticFile($fileName, $fileData, $modified);
 					if($fileOk)
 					{
 						$fileName = $this->getStaticFileName($staticLocation, $path);
 						$fileData = ob_get_contents();
-						$fileOk = $this->makeStaticFile($fileName, $fileData, $modified);
+						$fileOk = $this->createStaticFile($fileName, $fileData, $modified);
 					}
 				} else {
 					$statusCode = 409;
@@ -190,7 +200,7 @@ class YellowCommandline
 		if($statusCode == $statusCodeRequest)
 		{
 			$modified = strtotime($this->yellow->page->getHeader("Last-Modified"));			
-			if(!$this->makeStaticFile($fileName, ob_get_contents(), $modified))
+			if(!$this->createStaticFile($fileName, ob_get_contents(), $modified))
 			{
 				$statusCode = 500;
 				$this->yellow->page->error($statusCode, "Can't write file '$fileName'!");
@@ -201,9 +211,9 @@ class YellowCommandline
 	}
 	
 	// Create static file
-	function makeStaticFile($fileName, $fileData, $modified)
+	function createStaticFile($fileName, $fileData, $modified)
 	{
-		return $this->yellow->toolbox->makeFile($fileName, $fileData, true) &&
+		return $this->yellow->toolbox->createFile($fileName, $fileData, true) &&
 			$this->yellow->toolbox->modifyFile($fileName, $modified);
 	}
 	
@@ -253,14 +263,49 @@ class YellowCommandline
 	// Return static redirect data
 	function getStaticRedirect($url)
 	{
-		$data  = "<!DOCTYPE html><html>\n";
-		$data .= "<head>\n";
-		$data .= "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n";
-		$data .= "<meta http-equiv=\"refresh\" content=\"0;url=$url\" />\n";
-		$data .= "</head>\n";
-		$data .= "</html>\n";
+		$text  = "<!DOCTYPE html><html>\n";
+		$text .= "<head>\n";
+		$text .= "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n";
+		$text .= "<meta http-equiv=\"refresh\" content=\"0;url=$url\" />\n";
+		$text .= "</head>\n";
+		$text .= "</html>\n";
+		return $text;
+	}
+	
+	// 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;
 	}
+	
+	// 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;
+	}
 }
 	
 $yellow->registerPlugin("commandline", "YellowCommandline", YellowCommandline::Version);

+ 4 - 4
system/core/core-markdownextra.php

@@ -2,10 +2,10 @@
 // Copyright (c) 2013 Datenstrom, http://datenstrom.se
 // This file may be used and distributed under the terms of the public license.
 
-// Markdown extra parser core plugin
+// Markdown extra core plugin
 class YellowMarkdownExtra
 {
-	const Version = "0.2.4";
+	const Version = "0.2.5";
 	var $yellow;		//access to API
 	var $textHtml;		//generated text (HTML format)
 	
@@ -15,8 +15,8 @@ class YellowMarkdownExtra
 		$this->yellow = $yellow;
 	}
 	
-	// Parse text
-	function parse($text)
+	// Handle text parsing
+	function onParse($text)
 	{
 		$markdown = new YellowMarkdownExtraParser($this->yellow);
 		return $this->textHtml = $markdown->transform($text);

+ 80 - 7
system/core/core-webinterface.php

@@ -5,7 +5,7 @@
 // Web interface core plugin
 class YellowWebinterface
 {
-	const Version = "0.2.3";
+	const Version = "0.2.4";
 	var $yellow;				//access to API
 	var $users;					//web interface users
 	var $activeLocation;		//web interface location? (boolean)
@@ -19,7 +19,7 @@ class YellowWebinterface
 		$this->yellow = $yellow;
 		$this->yellow->config->setDefault("webinterfaceLocation", "/edit/");
 		$this->yellow->config->setDefault("webinterfaceUserFile", "user.ini");
-		$this->users = new YellowWebinterfaceUsers();
+		$this->users = new YellowWebinterfaceUsers($yellow);
 		$this->users->load($this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile"));
 	}
 
@@ -30,8 +30,7 @@ class YellowWebinterface
 		if($this->checkWebinterfaceLocation($location))
 		{
 			$serverBase .= rtrim($this->yellow->config->get("webinterfaceLocation"), '/');
-			$location = $this->yellow->getRelativeLocation($serverBase);
-			$fileName = $this->yellow->getContentFileName($location);
+			list($location, $fileName) = $this->yellow->getRequestLocationFile($serverBase);
 			if($this->checkUser()) $statusCode = $this->processRequestAction($serverName, $serverBase, $location, $fileName);
 			if($statusCode == 0) $statusCode = $this->yellow->processRequest($serverName, $serverBase, $location, $fileName,
 													false, $this->activeUserFail ? 401 : 0);
@@ -101,6 +100,43 @@ class YellowWebinterface
 		return $header;
 	}
 	
+	// Handle command help
+	function onCommandHelp()
+	{
+		return "user EMAIL PASSWORD [NAME LANGUAGE]\n";
+	}
+	
+	// Handle command
+	function onCommand($args)
+	{
+		list($name, $command) = $args;		
+		switch($command)
+		{
+			case "user":	$statusCode = $this->userCommand($args); break;
+			default:		$statusCode = 0;
+		}
+		return $statusCode;
+	}
+	
+	// Create or update user
+	function userCommand($args)
+	{
+		$statusCode = 0;
+		list($dummy, $command, $email, $password, $name, $language) = $args;
+		if(!empty($email) && !empty($password) )
+		{
+			$fileName = $this->yellow->config->get("configDir").$this->yellow->config->get("webinterfaceUserFile");
+			$statusCode = $this->users->createUser($fileName, $email, $password, $name, $language)  ? 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 {
+			echo "Yellow $command: Invalid arguments\n";
+			$statusCode = 400;
+		}
+		return $statusCode;
+	}
+	
 	// Process request for an action
 	function processRequestAction($serverName, $serverBase, $location, $fileName)
 	{
@@ -110,7 +146,7 @@ class YellowWebinterface
 			case "edit":	if(!empty($_POST["rawdata"]) && $this->checkUserPermissions($location, $fileName))
 							{
 								$this->rawDataOriginal = $_POST["rawdata"];
-								if($this->yellow->toolbox->makeFile($fileName, $_POST["rawdata"]))
+								if($this->yellow->toolbox->createFile($fileName, $_POST["rawdata"]))
 								{
 									$statusCode = 303;
 									$locationHeader = $this->yellow->toolbox->getHttpLocationHeader($serverName, $serverBase, $location);
@@ -134,7 +170,7 @@ class YellowWebinterface
 							break;
 			default:		if(!is_readable($fileName))
 							{
-								if($this->yellow->toolbox->isFileLocation($location) && is_dir($this->yellow->getContentDirectory("$location/")))
+								if($this->yellow->toolbox->isFileLocation($location) && $this->yellow->isContentDirectory("$location/"))
 								{
 									$statusCode = 301;
 									$locationHeader = $this->yellow->toolbox->getHttpLocationHeader($serverName, $serverBase, "$location/");
@@ -230,10 +266,12 @@ class YellowWebinterface
 // Yellow web interface users
 class YellowWebinterfaceUsers
 {
+	var $yellow;	//access to API
 	var $users;		//registered users
 	
-	function __construct()
+	function __construct($yellow)
 	{
+		$this->yellow = $yellow;
 		$this->users = array();
 	}
 
@@ -255,6 +293,41 @@ class YellowWebinterfaceUsers
 			}
 		}
 	}
+	
+	// Create or update user in file
+	function createUser($fileName, $email, $password, $name, $language)
+	{
+		$email = strreplaceu(',', '-', $email);
+		$password = hash("sha256", $email.$password);
+		$fileNewUser = true;
+		$fileData = @file($fileName);
+		if($fileData)
+		{
+			foreach($fileData as $line)
+			{
+				preg_match("/^(.*?),\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);
+						$fileDataNew .= "$email,$password,$name,$language\n";
+						$fileNewUser = false;
+						continue;
+					}
+				}
+				$fileDataNew .= $line;
+			}
+		}
+		if($fileNewUser)
+		{
+			$name = strreplaceu(',', '-', empty($name) ? $this->yellow->config->get("sitename") : $name);
+			$language = strreplaceu(',', '-', empty($language) ? $this->yellow->config->get("language") : $language);
+			$fileDataNew .= "$email,$password,$name,$language\n";
+		}
+		return $this->yellow->toolbox->createFile($fileName, $fileDataNew);
+	}
 
 	// Set user data
 	function setUser($email, $password, $name, $language)

+ 128 - 112
system/core/core.php

@@ -5,7 +5,7 @@
 // Yellow main class
 class Yellow
 {
-	const Version = "0.2.4";
+	const Version = "0.2.5";
 	var $page;				//current page data
 	var $pages;				//current page tree from file system
 	var $config;			//configuration
@@ -59,8 +59,7 @@ class Yellow
 		$statusCode = 0;
 		$serverName = $this->config->get("serverName");
 		$serverBase = $this->config->get("serverBase");
-		$location = $this->getRelativeLocation($serverBase);
-		$fileName = $this->getContentFileName($location);
+		list($location, $fileName) = $this->getRequestLocationFile($serverBase);
 		$this->page = new YellowPage($this, $location);
 		foreach($this->plugins->plugins as $key=>$value)
 		{
@@ -103,11 +102,11 @@ class Yellow
 					$fileName = $this->readPage($serverBase, $location, $fileName, $cacheable, $statusCode);
 				} else {
 					$statusCode = 303;
-					$locationArgs = $this->toolbox->getLocationArgsCleanUrl();
+					$locationArgs = $this->toolbox->getLocationArgsCleanUrl($location);
 					$this->sendStatus($statusCode, $this->toolbox->getHttpLocationHeader($serverName, $serverBase, $location.$locationArgs));
 				}
 			} else {
-				if($this->toolbox->isFileLocation($location) && is_dir($this->getContentDirectory("$location/")))
+				if($this->toolbox->isFileLocation($location) && $this->isContentDirectory("$location/"))
 				{
 					$statusCode = 301;
 					$this->sendStatus($statusCode, $this->toolbox->getHttpLocationHeader($serverName, $serverBase, "$location/"));
@@ -191,7 +190,8 @@ class Yellow
 		   $this->toolbox->isFileNotModified($this->page->getHeader("Last-Modified")))
 		{
 			$statusCode = 304;
-			$this->page->clean($statusCode);
+			if($this->page->isHeader("Cache-Control")) $responseHeader = "Cache-Control: ".$this->page->getHeader("Cache-Control");
+			$this->page->clean($statusCode, $responseHeader);
 		}
 		if($this->page->isExisting("pageClean")) ob_clean();
 		if(PHP_SAPI != "cli")
@@ -217,6 +217,25 @@ class Yellow
 		}
 	}
 	
+	// Return request location and file name, without server base
+	function getRequestLocationFile($serverBase)
+	{
+		$location = $this->toolbox->getLocationNormalised();
+		$location = substru($location, strlenu($serverBase));
+		$fileName = $this->toolbox->findFileFromLocation($location,
+			$this->config->get("contentDir"), $this->config->get("contentHomeDir"),
+			$this->config->get("contentDefaultFile"), $this->config->get("contentExtension"));
+		if(!$this->toolbox->isFileLocation($location) && !is_file($fileName) &&
+		   preg_match("/[^\/]+:.*/", rawurldecode($this->toolbox->getLocation())))
+		{
+			$location = rtrim($location, '/');
+			$fileName = $this->toolbox->findFileFromLocation($location,
+				 $this->config->get("contentDir"), $this->config->get("contentHomeDir"),
+				 $this->config->get("contentDefaultFile"), $this->config->get("contentExtension"));
+		}
+		return array($location, $fileName);
+	}
+	
 	// Return name of request handler
 	function getRequestHandler()
 	{
@@ -229,7 +248,7 @@ class Yellow
 		return isset($_GET["clean-url"]) || isset($_POST["clean-url"]);
 	}
 	
-	// Check for request error
+	// Check if request error happened
 	function isRequestError()
 	{
 		$serverBase = $this->config->get("serverBase");
@@ -240,6 +259,14 @@ class Yellow
 		return $this->page->isExisting("pageError");
 	}
 	
+	// Check if content directory exists
+	function isContentDirectory($location)
+	{
+		$path = $this->toolbox->findFileFromLocation($location,
+			$this->config->get("contentDir"), $this->config->get("contentHomeDir"), "", "");
+		return is_dir($path);
+	}
+	
 	// Execute template
 	function template($name)
 	{
@@ -284,29 +311,6 @@ class Yellow
 		return $header;
 	}
 	
-	// Return content location for current HTTP request, without server base
-	function getRelativeLocation($serverBase)
-	{
-		$location = $this->toolbox->getLocation();
-		$location = $this->toolbox->normaliseLocation($location);
-		return substru($location, strlenu($serverBase));
-	}
-	
-	// 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 plugin command
 	function plugin($name, $args = NULL)
 	{
@@ -439,11 +443,15 @@ class YellowPage
 			$this->parser = new stdClass;
 			if($this->yellow->plugins->isExisting($this->get("parser")))
 			{
-				$this->parser = $this->yellow->plugins->plugins[$this->get("parser")]["obj"];
-				$this->parser->parse($this->getContent(true));
-				$location = $this->yellow->toolbox->getDirectoryLocation($this->getLocation());
-				$this->parser->textHtml = preg_replace("#<a(.*?)href=\"(?!javascript:)([^\/\"]+)\"(.*?)>#",
-													   "<a$1href=\"$location$2\"$3>", $this->parser->textHtml);
+				$plugin = $this->yellow->plugins->plugins[$this->get("parser")];
+				if(method_exists($plugin["obj"], "onParse"))
+				{
+					$this->parser = $plugin["obj"];
+					$this->parser->onParse($this->getContent(true));
+					$location = $this->yellow->toolbox->getDirectoryLocation($this->getLocation());
+					$this->parser->textHtml = preg_replace("#<a(.*?)href=\"(?!javascript:)([^\/\"]+)\"(.*?)>#",
+														   "<a$1href=\"$location$2\"$3>", $this->parser->textHtml);
+				}
 			}
 			foreach($this->yellow->plugins->plugins as $key=>$value)
 			{
@@ -762,10 +770,10 @@ class YellowPageCollection extends ArrayObject
 	{
 		if($pageNumber>=1 && $pageNumber<=$this->paginationCount)
 		{
-			$locationArgs = $this->yellow->toolbox->getLocationArgs($pageNumber>1 ? "page:$pageNumber" : "page:");
-			$location = $this->yellow->page->getLocation().$locationArgs;
+			$location = $this->yellow->page->getLocation();
+			$locationArgs = $this->yellow->toolbox->getLocationArgs($location, $pageNumber>1 ? "page:$pageNumber" : "page:");
 		}
-		return $location;
+		return $location.$locationArgs;
 	}
 	
 	// Return absolute location for previous page in pagination
@@ -1203,11 +1211,50 @@ class YellowToolbox
 		return ($pos = strposu($uri, '?')) ? substru($uri, 0, $pos) : $uri;
 	}
 	
+	// Return location from current HTTP request, remove unwanted path tokens and location arguments
+	function getLocationNormalised()
+	{
+		$string = rawurldecode($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))
+		{
+			$location = $matches[1];
+			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($arg = "")
+	function getLocationArgs($location, $arg = "")
 	{		
 		preg_match("/^(.*?):(.*)$/", $arg, $args);
-		if(preg_match("/^(.*?\/)(\w+:.*)$/", rawurldecode(self::getLocation()), $matches))
+		if(preg_match("/^(.*?\/)([^\/]+:.*)$/", rawurldecode($this->getLocation()), $matches))
 		{
 			foreach(explode('/', $matches[2]) as $token)
 			{
@@ -1225,13 +1272,16 @@ class YellowToolbox
 			if(!empty($locationArgs)) $locationArgs .= '/';
 			$locationArgs .= "$args[1]:$args[2]";
 		}
-		$locationArgs = rawurlencode($locationArgs);
-		$locationArgs = strreplaceu(array('%3A','%2F'), array(':','/'), $locationArgs);
+		if(!empty($locationArgs))
+		{
+			if($this->isFileLocation($location)) $locationArgs = '/'.$locationArgs;
+			$locationArgs = strreplaceu(array('%3A','%2F'), array(':','/'), rawurlencode($locationArgs));
+		}
 		return $locationArgs;
 	}
 	
 	// Return location arguments from current HTTP request, convert form into clean URL
-	function getLocationArgsCleanUrl()
+	function getLocationArgsCleanUrl($location)
 	{
 		foreach(array_merge($_GET, $_POST) as $key=>$value)
 		{
@@ -1243,50 +1293,14 @@ class YellowToolbox
 				$locationArgs .= "$key:$value";
 			}
 		}
-		$locationArgs = rawurlencode($locationArgs);
-		$locationArgs = strreplaceu(array('%3A','%2F'), array(':','/'), $locationArgs);
-		return $locationArgs;
-	}
-
-	// Normalise location and remove unwanted path tokens
-	function normaliseLocation($location, $convertArgs = true)
-	{
-		$string = rawurldecode($location);
-		$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($convertArgs && preg_match("/^(.*?\/)(\w+:.*)$/", $location, $matches))
+		if(!empty($locationArgs))
 		{
-			$location = $matches[1];
-			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];
-				}
-			}
+			if($this->isFileLocation($location)) $locationArgs = '/'.$locationArgs;
+			$locationArgs = strreplaceu(array('%3A','%2F'), array(':','/'), rawurlencode($locationArgs));
 		}
-		return $location;
+		return $locationArgs;
 	}
-	
+
 	// Check if file is unmodified since last HTTP request
 	function isFileNotModified($lastModified)
 	{
@@ -1304,7 +1318,7 @@ class YellowToolbox
 	{
 		$string = "";
 		$tokens = explode('/', $location);
-		for($i=1; $i<count($tokens); ++$i) $string .= '/'.self::normaliseName($tokens[$i]);
+		for($i=1; $i<count($tokens); ++$i) $string .= '/'.$this->normaliseName($tokens[$i]);
 		return $location == $string;
 	}
 	
@@ -1344,32 +1358,32 @@ class YellowToolbox
 			for($i=1; $i<count($tokens)-1; ++$i)
 			{
 				$token = $tokens[$i];
-				if(self::normaliseName($token) != $token) $invalid = true;
+				if($this->normaliseName($token) != $token) $invalid = true;
 				$regex = "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
-				foreach(self::getDirectoryEntries($path, $regex) as $entry)
+				foreach($this->getDirectoryEntries($path, $regex) as $entry)
 				{
-					if(self::normaliseName($entry) == $token) { $token = $entry; break; }
+					if($this->normaliseName($entry) == $token) { $token = $entry; break; }
 				}
 				$path .= "$token/";
 			}
 		} else {
 			$i = 1;
 			$token = rtrim($pathHome, '/');
-			if(self::normaliseName($token) != $token) $invalid = true;
+			if($this->normaliseName($token) != $token) $invalid = true;
 			$regex = "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
-			foreach(self::getDirectoryEntries($path, $regex) as $entry)
+			foreach($this->getDirectoryEntries($path, $regex) as $entry)
 			{
-				if(self::normaliseName($entry) == $token) { $token = $entry; break; }
+				if($this->normaliseName($entry) == $token) { $token = $entry; break; }
 			}
 			$path .= "$token/";
 		}
 		$token = !empty($tokens[$i]) ? $tokens[$i].$fileExtension : $fileDefault;
 		if(!empty($tokens[$i]) && $tokens[$i].$fileExtension==$fileDefault) $invalid = true;
-		if(self::normaliseName($token) != $token) $invalid = true;
+		if($this->normaliseName($token) != $token) $invalid = true;
 		$regex = "/^[\d\-\_\.]*".strreplaceu('-', '.', $token)."$/";
-		foreach(self::getDirectoryEntries($path, $regex, false, false) as $entry)
+		foreach($this->getDirectoryEntries($path, $regex, false, false) as $entry)
 		{
-			if(self::normaliseName($entry) == $token) { $token = $entry; break; }
+			if($this->normaliseName($entry) == $token) { $token = $entry; break; }
 		}
 		$path .= $token;
 		return $invalid ? "" : $path;
@@ -1383,11 +1397,11 @@ class YellowToolbox
 		$tokens = explode('/', $fileName);
 		for($i=0; $i<count($tokens)-1; ++$i)
 		{
-			$token = self::normaliseName($tokens[$i]).'/';
+			$token = $this->normaliseName($tokens[$i]).'/';
 			if($i || $token!=$pathHome) $location .= $token;
 		}
-		$token = self::normaliseName($tokens[$i]);
-		if($token != $fileDefault) $location .= self::normaliseName($tokens[$i], true);
+		$token = $this->normaliseName($tokens[$i]);
+		if($token != $fileDefault) $location .= $this->normaliseName($tokens[$i], true);
 		return $location;
 	}
 	
@@ -1411,6 +1425,7 @@ class YellowToolbox
 			case 302: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Moved temporarily"; break;
 			case 303: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Reload please"; break;
 			case 304: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not modified"; break;
+			case 400: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Bad request"; break;
 			case 401: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Unauthorised"; break;
 			case 404: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Not found"; break;
 			case 409: $text = "$_SERVER[SERVER_PROTOCOL] $statusCode Conflict"; break;
@@ -1442,7 +1457,7 @@ class YellowToolbox
 	// Return HTTP location header
 	function getHttpLocationHeader($serverName, $serverBase, $location)
 	{
-		return "Location: ".self::getHttpUrl($serverName, $serverBase, $location);
+		return "Location: ".$this->getHttpUrl($serverName, $serverBase, $location);
 	}
 	
 	// Return directory location
@@ -1481,20 +1496,20 @@ class YellowToolbox
 	function getDirectoryEntriesRecursive($path, $regex = "/.*/", $sort = false, $directories = true, $levelMax = 0)
 	{
 		$entries = array();
-		foreach(self::getDirectoryEntries($path, $regex, $sort, $directories) as $entry) array_push($entries, "$path/$entry");
+		foreach($this->getDirectoryEntries($path, $regex, $sort, $directories) as $entry) array_push($entries, "$path/$entry");
 		--$levelMax;
 		if($levelMax != 0)
 		{
-			foreach(self::getDirectoryEntries($path, "/.*/", $sort, true) as $entry)
+			foreach($this->getDirectoryEntries($path, "/.*/", $sort, true) as $entry)
 			{
-				$entries = array_merge($entries, self::getDirectoryEntriesRecursive("$path/$entry", $regex, $sort, $directories, $levelMax));
+				$entries = array_merge($entries, $this->getDirectoryEntriesRecursive("$path/$entry", $regex, $sort, $directories, $levelMax));
 			}
 		}
 		return $entries;
 	}
 
 	// Create file
-	function makeFile($fileName, $fileData, $mkdir = false)
+	function createFile($fileName, $fileData, $mkdir = false)
 	{
 		$ok = false;
 		if($mkdir)
@@ -1585,12 +1600,17 @@ class YellowToolbox
 				$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($t=count($elementsOpen)-1; $t>=0; --$t) $output .= "</".$elementsOpen[$t].">";
+			for(; $i>=0; --$i) $output .= "</".$elementsOpen[$i].">";
 		}
 		return $output;
 	}
-
+	
 	// Create keywords from text string
 	function createTextKeywords($text, $keywordsMax)
 	{
@@ -1688,14 +1708,10 @@ class YellowPlugins
 	function load()
 	{
 		global $yellow;
-		require_once("core-commandline.php");
-		require_once("core-markdownextra.php");
-		require_once("core-webinterface.php");
-		foreach($yellow->toolbox->getDirectoryEntries($yellow->config->get("pluginDir"), "/.*\.php/", true, false) as $entry)
-		{
-			$fileNamePlugin = $yellow->config->get("pluginDir")."/$entry";
-			require_once($fileNamePlugin);
-		}
+		$path = dirname(__FILE__);
+		foreach($yellow->toolbox->getDirectoryEntries($path, "/.*\.php/", true, false) as $entry) require_once("$path/$entry");
+		$path = $yellow->config->get("pluginDir");
+		foreach($yellow->toolbox->getDirectoryEntries($path, "/.*\.php/", true, false) as $entry) require_once("$path/$entry");
 		foreach($this->plugins as $key=>$value)
 		{
 			$this->plugins[$key]["obj"] = new $value["class"];

+ 3 - 1
system/snippets/footer.php

@@ -1,4 +1,6 @@
-<div class="footer">&copy; 2013 <?php echo $yellow->page->getHtml("sitename") ?>. Built with <a href="https://github.com/markseu/yellowcms">Yellow</a></div>
+<div class="footer">
+&copy; 2013 <?php echo $yellow->page->getHtml("sitename") ?>. Built with <a href="https://github.com/markseu/yellowcms">Yellow</a>
+</div>
 </div>
 </body>
 </html>