From 184097b324b2e9e0baf46c356cdc0f1649cee8a4 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Mon, 5 Mar 2018 21:27:28 +0000
Subject: [PATCH] Fix a bunch of bugs. We're getting there~

---
 .gitignore                                    |   2 +
 AbstractAction.php                            |   6 +-
 Actions/Report.php                            | 104 +++++++++++++++++
 default_settings.toml                         |  16 +++
 get-report-slot.php                           |   8 --
 index.php                                     |   4 +-
 report.php                                    | 106 ------------------
 ...implexmlwriter.php => SimpleXmlWriter.php} |  16 +--
 utilities/TomlConfig.php                      |  33 ++++--
 utilities/transform.php                       |  34 +++++-
 10 files changed, 189 insertions(+), 140 deletions(-)
 create mode 100644 default_settings.toml
 delete mode 100644 get-report-slot.php
 delete mode 100644 report.php
 rename utilities/{simplexmlwriter.php => SimpleXmlWriter.php} (94%)

diff --git a/.gitignore b/.gitignore
index 45d8b00..ed5091b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
 # The application's data directory
 data/
+# The custom settings file
+/settings.toml
 
 # Created by https://www.gitignore.io/api/composer,git
 
diff --git a/AbstractAction.php b/AbstractAction.php
index c6556f6..419822d 100644
--- a/AbstractAction.php
+++ b/AbstractAction.php
@@ -1,5 +1,7 @@
 <?php
 
+namespace Sandpiper;
+
 abstract class AbstractAction
 {
     public abstract function handle();
@@ -8,11 +10,11 @@ abstract class AbstractAction
 		return !empty($_GET[$key]);
 	}
 	
-	public function get_param($key, $default_value) {
+	public function param_get($key, $default_value) {
 		return $_GET[$key] ?? $default_value;
 	}
 	
-	public get_post_body() {
+	public function get_post_body() {
 		return file_get_contents("php://input");
 	}
 }
diff --git a/Actions/Report.php b/Actions/Report.php
index 4dad26e..9a0989c 100644
--- a/Actions/Report.php
+++ b/Actions/Report.php
@@ -2,6 +2,10 @@
 
 namespace Sandpiper\Actions;
 
+use \Yosymfony\Toml\Toml;
+
+use \SBRL\Utilities\SimpleXmlWriter;
+
 class Report extends \Sandpiper\AbstractAction
 {
 	
@@ -11,6 +15,106 @@ class Report extends \Sandpiper\AbstractAction
 	}
 	
 	public function handle() {
+		global $settings;
 		
+		header("content-type: text/plain");
+
+		if(!$this->param_exists("place_id"))
+			exit("Error: No place id provided.\n");
+		if(!$this->param_exists("summary"))
+			exit("Error: No summary provided.\n");
+		if(!$this->param_exists("version"))
+			exit("No version provided.\n");
+		
+		/******************************************************/
+		
+		$place = null;
+		foreach($settings->get("places") as $current_place) {
+			var_dump($current_place);
+			if($current_place["key"] == $this->param_get("place_id", null)) {
+				$place = $current_place;
+				break;
+			}
+		}
+		
+		if($place == null)
+			exit("Error: Place id doesn't match.\n");
+		
+		$new_report = new \stdClass();
+		$new_report->version = escape4xml($this->param_get("version", "?"));
+		$new_report->summary = escape4xml($this->param_get("summary", "(Unknown error)"));
+		$new_report->details = escape4xml($this->param_get("details", ""));
+		$new_report->stack_trace = escape4xml($this->get_post_body());
+
+		$report_dir = ROOT_DIR . "/{$settings->get("data_dir")}/places/" . slugify($place["name"]);
+		$report_filename = "$report_dir/" . slugify($new_report->summary) . ".xml";
+		
+		// Create the directory if it doesn't exist already
+		if(!file_exists($report_dir))
+			mkdir($report_dir, 0750, true);
+
+		// Save the individual report
+
+		if(!file_exists($report_filename)) {
+			$writer = new \SBRL\Utilities\SimpleXmlWriter(); // It's started automagically
+			$writer->prettyprint = true;
+			$writer->add_xslt("/theme/stack_traces.xslt");
+			$writer->open("error_info");
+			$writer->addtag("project_name", [], $place["name"]); // For aesthetic purposes
+			$writer->addtag("summary", [], $new_report->summary);
+			$writer->open("reports");
+			$writer->close();
+			$writer->close();
+			file_put_contents($report_filename, $writer->render());
+		}
+
+		$report_xml = simplexml_load_file($report_filename);
+
+		if($report_xml === false)
+			exit("Error: invalid XML generated when creating a new report file.\n");
+
+		$report_node = $report_xml->reports->addChild("report");
+		$report_node->addChild("timestamp", date(DATE_ATOM));
+		$report_node->addChild("version", $new_report->version);
+		$report_node->addChild("details", $new_report->details);
+		$report_node->addChild("stack_trace", $new_report->stack_trace);
+
+		file_put_contents($report_filename, $report_xml->asXML());
+
+		// Update the place index
+		$place_index_filename = "$report_dir/index.xml";
+		
+		if(!file_exists($place_index_filename)) {
+			$writer = new SimpleXmlWriter();
+			$writer->prettyprint = true;
+
+			$writer->add_xslt(ROOT_DIR . "/theme/place_index.xslt");
+			$writer->open("errors");
+			$writer->close();
+			file_put_contents($place_index_filename, $writer->render());
+		}
+		$index_xml = simplexml_load_file($place_index_filename);
+
+		$index_entry = null;
+		foreach($index_xml as $entry) {
+			if($entry->summary->__toString() != $new_report->summary)
+				continue;
+			$index_entry = $entry;
+			break;
+		}
+
+		if($index_entry == null) {
+			$index_entry = $index_xml->addChild("error");
+			$index_entry->summary = $new_report->summary;
+			$index_entry->last_report = date(DATE_ATOM);
+			$index_entry->report_count = 0;
+		}
+
+		$index_entry->report_count = intval($index_entry->report_count) + 1;
+		
+		file_put_contents($place_index_filename, $index_xml->asXML());
+		
+		http_response_code(200);
+		exit("Report submitted successfully!\n");
 	}
 }
diff --git a/default_settings.toml b/default_settings.toml
new file mode 100644
index 0000000..3306e93
--- /dev/null
+++ b/default_settings.toml
@@ -0,0 +1,16 @@
+###################################
+# Default Sandpiper settings file #
+###################################
+
+# Don't edit this unless you know what you are doing! You probably want to edit
+# the `settings.toml` file in this directory instead.
+
+# The data directory
+# Relative to this directory - and shouldn't contain a trailing slash.
+data_dir = "data"
+
+[places]
+
+# [places.example-project]
+# name = "An Awesome Project";
+# key = "A very sekret key"; # Required by clients to report an error to this project. Should be unique!
diff --git a/get-report-slot.php b/get-report-slot.php
deleted file mode 100644
index 3b0a6d7..0000000
--- a/get-report-slot.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-require("settings.php");
-require("utilities/comment_key.php");
-
-header("content-type: text/plain");
-
-exit(key_generate($settings->comment_key_pass));
diff --git a/index.php b/index.php
index 72497ed..b29525d 100644
--- a/index.php
+++ b/index.php
@@ -1,5 +1,7 @@
 <?php
 
+define("ROOT_DIR", dirname(__FILE__));
+
 require("vendor/autoload.php");
 require("utilities/transform.php");
 
@@ -12,7 +14,7 @@ $aura_loader->register();
 
 // ---------------------------------------------------------------------------------
 
-
+$settings = new \SBRL\Utilities\TomlConfig("settings.toml", "default_settings.toml");
 
 function send_error($code, $message) {
 	http_response_code($code);
diff --git a/report.php b/report.php
deleted file mode 100644
index 2978e20..0000000
--- a/report.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-
-require("settings.php");
-require("utilities/comment_key.php");
-require("utilities/transform.php");
-require("utilities/simplexmlwriter.php");
-
-header("content-type: text/plain");
-
-if(empty($_GET["place_id"]))
-	exit("Error: No place id provided.\n");
-if(empty($_GET["summary"]))
-	exit("Error: No summary provided.\n");
-if(empty($_GET["version"]))
-	exit("No version provided.\n");
-
-/*if(empty($_GET["key"]))
-	exit("Error: No key provided.\n");
-
-if(!key_verify(
-	$_GET["key"],
-	$settings->comment_key_pass,
-	$settings->comment_key_min_age, $settings->comment_key_max_age
-	))
-	exit("Error: Invalid key.\n");
-*/
-/******************************************************/
-
-$places = json_decode(file_get_contents("$settings->data_dir/places.json"));
-
-$place = null;
-foreach($places as $current_place) {
-	if($place->key == $_GET["place_id"]) {
-		$place = $current_place;
-		break;
-	}
-}
-
-if($place == null)
-	exit("Error: Place id doesn't match.\n");
-
-$new_report = new stdClass();
-$new_report->version = escape4xml($_GET["version"]);
-$new_report->summary = escape4xml($_GET["summary"]);
-$new_report->stack_trace = escape4xml(file_get_contents('php://input'));
-
-$report_dir = "$settings->data_dir/places/$place->name";
-$report_filename = "$report_dir/" . slugify($new_report->summary) . ".xml";
-
-// Save the individual report
-
-if(!file_exists($report_filename)) {
-	$writer = new SimpleXmlWriter();
-	$writer->start();
-	$writer->add_xslt("../../theme/stack_traces.xslt");
-	$writer->open("error_info");
-	$writer->addtag("summary", [], $new_report->summary);
-	$writer->open("reports");
-	$writer->close();
-	$writer->close();
-	file_put_contents($report_filename, $writer->render());
-}
-
-$report_xml = simplexml_load_file($report_filename);
-
-if($report_xml === false)
-	exit("Error: invalid XML generated when creating a new report file.\n");
-
-$report_node = $report_xml->reports->addChild("report");
-$report_node->addChild("timestamp", date(DATE_ATOM));
-$report_node->addChild("version", $new_report->version);
-$report_node->addChild("summary", $new_report->summary);
-$report_node->addChild("stack_trace", $new_report->stack_trace);
-
-file_put_contents($report_filename, $report_xml->asXML());
-
-// Update the place index
-$place_index_filename = "$report_dir/index.xml";
-if(!file_exists($place_index_filename)) {
-	$writer = new SimpleXmlWriter();
-	$writer->start();
-	$writer->add_xslt("../../theme/place_index.xslt");
-	$writer->open("errors");
-	$writer->close();
-	file_put_contents($place_index_filename, $writer->render());
-}
-$index_xml = simplexml_load_file($place_index_filename);
-
-$index_entry = null;
-foreach($index_xml as $entry) {
-	if($entry->summary->__toString() != $new_report->summary)
-		continue;
-	$index_entry = $entry;
-	break;
-}
-
-if($index_entry == null) {
-	$index_entry = $index_xml->addChild("error");
-	$index_entry->summary = $new_report->summary;
-	$index_entry->last_report = date(DATE_ATOM);
-	$index_entry->report_count = 0;
-}
-
-$index_entry->report_count = intval($index_entry->report_count) + 1;
-
-file_put_contents($place_index_filename, $index_xml->asXML());
diff --git a/utilities/simplexmlwriter.php b/utilities/SimpleXmlWriter.php
similarity index 94%
rename from utilities/simplexmlwriter.php
rename to utilities/SimpleXmlWriter.php
index 5ba1a45..b3bb8ce 100644
--- a/utilities/simplexmlwriter.php
+++ b/utilities/SimpleXmlWriter.php
@@ -123,19 +123,16 @@ class SimpleXmlWriter {
 	{
 		$result = $this->xml;
 		
-		$revopentags = array_reverse($this->opentags);
-		foreach($revopentags as $tagname)
-		{
-			if($this->prettyprint)
-				$result .= str_repeat("\t", count($revopentags)) . "</$tagname>\n";
-			else
-				$result .= "</$tagname>";
-				
-		}
+		$this->closeall();
 		
 		return $result;
 	}
 	
+	protected function indent()
+	{
+		$this->add($this->getindent());
+	}
+	
 	//get the appropriate number of tab characters for indentation purposes
 	protected function getindent()
 	{
@@ -145,7 +142,6 @@ class SimpleXmlWriter {
 	//function to add to the rendered XML string
 	protected function add($text = "", $newline = true)
 	{
-		
 		if($newline and $this->prettyprint)
 			$this->xml .= $this->getindent();
 		
diff --git a/utilities/TomlConfig.php b/utilities/TomlConfig.php
index 9a8ac2d..bcf55bd 100644
--- a/utilities/TomlConfig.php
+++ b/utilities/TomlConfig.php
@@ -133,9 +133,15 @@ class TomlConfig
 		$pathParts = explode(".", $path);
 		$subObj = $obj;
 		foreach($pathParts as $part) {
-			if(!isset($subObj->$part))
-				return false;
-			$subObj = $subObj->$part;
+			if(is_array($part)) {
+				if(!isset($subObj[$part]))
+					return false;
+				$subObj = $subObj[$part];
+			} else {
+				if(!isset($subObj->$part))
+					return false;
+				$subObj = $subObj->$part;
+			}
 		}
 		return true;
 	}
@@ -146,11 +152,22 @@ class TomlConfig
 	 * @param	string		$path	The path to extract from the given object.
 	 * @return	mixed				The property identified by the given object.
 	 */
-	public static function getPropertyByPath($obj, $path) {
+	public static function getPropertyByPath($obj, $path, $defaultValue = null) {
 		$pathParts = explode(".", $path);
 		$subObj = $obj;
 		foreach($pathParts as $part) {
-			$subObj = $subObj->$part;
+			if(is_array($part)) {
+				if(!isset($subObj[$part]))
+					return $defaultValue;
+				
+				$subObj = $subObj[$part];
+			}
+			else {
+				if(!isset($subObj->$part))
+					return $defaultValue;
+				
+				$subObj = $subObj->$part;
+			}
 		}
 		return $subObj;
 	}
@@ -169,14 +186,14 @@ class TomlConfig
 		for($i = 0; $i < $pathPartsCount; $i++) {
 			// Set the property on the last iteration
 			if($i + 1 == $pathPartsCount) {
-				$subObj->$part = $value;
+				$subObj[$part] = $value;
 				break;
 			}
 			// Create sub-objects if they dobn't exist already
 			if(!isset($subObj->$part)) {
-				$subObj->$part = new stdClass();
+				$subObj[$part] = new stdClass();
 			}
-			$subObj = $subObj->$part;
+			$subObj = $subObj[$part];
 		}
 	}
 }
diff --git a/utilities/transform.php b/utilities/transform.php
index 31940cb..df4a4e5 100644
--- a/utilities/transform.php
+++ b/utilities/transform.php
@@ -1,18 +1,42 @@
 <?php
 
+/**
+ * Normalises and removes any unknown characters from a string, making it safe
+ * for use as a filename.
+ * @param	string  $string		The string to slugificate.
+ * @param	integer $maxlength	The maximum desired length of the resultant string.
+ * @return	string				The slugified string.
+ */
 function slugify($string, $maxlength = 50) {
-	return preg_replace([
-			'\s+',
-			'[^a-z0-9\-_]'
+	var_dump($string);
+	return substr(preg_replace([
+			'/\s+/',
+			'/[^a-z0-9\-_]/'
 		], [
 			'-',
 			''
 		],
 		strtolower(trim($string))
-	);
+	), 0, $maxlength);
 }
 
-
+/**
+ * Escapes a string ready for inclusion in an XML document.
+ * @param  string $string The string to escape.
+ * @return string         The escaped string.
+ */
 function escape4xml($string) {
 	return htmlspecialchars($string, ENT_QUOTES | ENT_XML1);
 }
+
+/**
+ * Renders a SimpleXML element node to a pretty-printed XML document.
+ * @param	SimpleXmlElement	$simplexml_node The SimpleXML node to render.
+ * @return	string				The pretty-printed XML representation of the provided SimpleXML node.
+ */
+function simplexml_asxml_pretty($simplexml_node) {
+	$dom = dom_import_simplexml($simplexml_node);
+	$dom->preserveWhitespace = false;
+	$dom->formatOutput = true;
+	return $dom->saveXML();
+}
