Compare commits
6 Commits
8d2fb1f575
...
7b4af997ac
Author | SHA1 | Date |
---|---|---|
Starbeamrainbowlabs | 7b4af997ac | |
Starbeamrainbowlabs | 066fe36a78 | |
Starbeamrainbowlabs | 9bd4845ad9 | |
Starbeamrainbowlabs | 184097b324 | |
Starbeamrainbowlabs | 37aa996385 | |
Starbeamrainbowlabs | 524796e43d |
|
@ -0,0 +1,20 @@
|
|||
# The application's data directory
|
||||
data/
|
||||
# The custom settings file
|
||||
/settings.toml
|
||||
|
||||
# Created by https://www.gitignore.io/api/composer,git
|
||||
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
# composer.lock
|
||||
|
||||
### Git ###
|
||||
*.orig
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/composer,git
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Sandpiper;
|
||||
|
||||
abstract class AbstractAction
|
||||
{
|
||||
public abstract function handle();
|
||||
|
||||
public function param_exists($key) {
|
||||
return !empty($_GET[$key]);
|
||||
}
|
||||
|
||||
public function param_get($key, $default_value) {
|
||||
return $_GET[$key] ?? $default_value;
|
||||
}
|
||||
|
||||
public function get_post_body() {
|
||||
return file_get_contents("php://input");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Sandpiper\Actions;
|
||||
|
||||
use \DOMDocument; use \DOMElement; use \DOMCDATASection;
|
||||
|
||||
use \Yosymfony\Toml\Toml;
|
||||
|
||||
use \SBRL\Utilities\SimpleXmlWriter;
|
||||
|
||||
class Report extends \Sandpiper\AbstractAction
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
libxml_disable_entity_loader(false);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 = $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());
|
||||
}
|
||||
|
||||
// We have to use a DOMDocument here because SimpleXML mangled the
|
||||
// whitespace in the stack traces
|
||||
$report_xml = new DOMDocument("1.0", "UTF-8");
|
||||
$report_xml->preserveWhiteSpace = false;
|
||||
$report_xml->formatOutput = true;
|
||||
if(!$report_xml->loadXML(file_get_contents($report_filename), LIBXML_NONET))
|
||||
exit("Error: Invalid XML generated when creating a new report file.\n");
|
||||
|
||||
$report_node = new DOMElement("report");
|
||||
$report_xml->getElementsByTagName("reports")->item(0)->appendChild($report_node);
|
||||
|
||||
$report_node->appendChild(new DOMElement("timestamp", date(DATE_ATOM)));
|
||||
$report_node->appendChild(new DOMElement("version", $new_report->version));
|
||||
$report_node->appendChild(new DOMElement("details", $new_report->details));
|
||||
$stack_trace_node = new DOMElement("stack_trace");
|
||||
$report_node->appendChild($stack_trace_node);
|
||||
$stack_trace_node->appendChild($report_xml->createCDATASection($new_report->stack_trace));
|
||||
|
||||
// NOTE: When uploading with curl to test this, make sure to use --data-binary and not simply -d, as this mangles the newline characters.
|
||||
file_put_contents($report_filename, $report_xml->saveXML());
|
||||
|
||||
// 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("/theme/error_index.xslt");
|
||||
$writer->open("errors_index");
|
||||
$writer->addtag("project_name", [], $place["name"]);
|
||||
$writer->open("error_list");
|
||||
$writer->close();
|
||||
$writer->close();
|
||||
file_put_contents($place_index_filename, $writer->render());
|
||||
}
|
||||
$index_xml = simplexml_load_file($place_index_filename);
|
||||
|
||||
$index_entry = null;
|
||||
foreach($index_xml->error_list as $entry) {
|
||||
if($entry->summary->__toString() != $new_report->summary)
|
||||
continue;
|
||||
$index_entry = $entry;
|
||||
break;
|
||||
}
|
||||
|
||||
if($index_entry == null) {
|
||||
$index_entry = $index_xml->error_list->addChild("error");
|
||||
$index_entry->summary = $new_report->summary;
|
||||
$index_entry->last_report = date(DATE_ATOM);
|
||||
$index_entry->filename = basename($report_filename);
|
||||
$index_entry->report_count = 0;
|
||||
}
|
||||
|
||||
$index_entry->report_count = intval($index_entry->report_count) + 1;
|
||||
|
||||
// This is fine because we don't have to worry about the whitespace in the
|
||||
// stack traces here
|
||||
file_put_contents($place_index_filename, simplexml_asxml_pretty($index_xml));
|
||||
|
||||
http_response_code(200);
|
||||
exit("Report submitted successfully!\n");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "sbrl/wandering-sandpiper",
|
||||
"description": "A simple error report / stack trace gathering system.",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"yosymfony/toml": "^1.0",
|
||||
"aura/autoload": "^2.0"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Starbeamrainbowlabs",
|
||||
"email": "sbrl@starbeamrainbowlabs.com"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3f0d14de50f1944fa0995459b410e76c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aura/autoload",
|
||||
"version": "2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/auraphp/Aura.Autoload.git",
|
||||
"reference": "306a7f8d3cb58fb6f94bcff1dddf20c543f68668"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/auraphp/Aura.Autoload/zipball/306a7f8d3cb58fb6f94bcff1dddf20c543f68668",
|
||||
"reference": "306a7f8d3cb58fb6f94bcff1dddf20c543f68668",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"aura": {
|
||||
"type": "library"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Aura\\Autoload\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Aura.Autoload Contributors",
|
||||
"homepage": "https://github.com/auraphp/Aura.Autoload/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides a PSR-4 compliant autoloader implementation.",
|
||||
"homepage": "https://github.com/auraphp/Aura.Autoload",
|
||||
"keywords": [
|
||||
"PSR-4",
|
||||
"SPL autoloader",
|
||||
"autoload",
|
||||
"autoloader",
|
||||
"class loader"
|
||||
],
|
||||
"time": "2016-10-03T19:36:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "yosymfony/parser-utils",
|
||||
"version": "v1.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yosymfony/parser-utils.git",
|
||||
"reference": "5376469d996a8f09139c0c795048b1140d143281"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/yosymfony/parser-utils/zipball/5376469d996a8f09139c0c795048b1140d143281",
|
||||
"reference": "5376469d996a8f09139c0c795048b1140d143281",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Yosymfony\\ParserUtils\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Puertas",
|
||||
"email": "vpgugr@gmail.com",
|
||||
"homepage": "http://yosymfony.com"
|
||||
}
|
||||
],
|
||||
"description": "Parser utilities",
|
||||
"homepage": "http://github.com/yosymfony/toml",
|
||||
"keywords": [
|
||||
"lexer",
|
||||
"parser"
|
||||
],
|
||||
"time": "2017-11-18T22:30:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "yosymfony/toml",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yosymfony/toml.git",
|
||||
"reference": "8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/yosymfony/toml/zipball/8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678",
|
||||
"reference": "8d67c2c9a5941d40a3a37ff42c3c3b72a0a09678",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"yosymfony/parser-utils": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Yosymfony\\Toml\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Victor Puertas",
|
||||
"email": "vpgugr@gmail.com",
|
||||
"homepage": "http://yosymfony.com"
|
||||
}
|
||||
],
|
||||
"description": "A PHP parser for TOML compatible with specification 0.4.0",
|
||||
"homepage": "http://github.com/yosymfony/toml",
|
||||
"keywords": [
|
||||
"mojombo",
|
||||
"parser",
|
||||
"toml"
|
||||
],
|
||||
"time": "2018-02-05T22:43:21+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
|
@ -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!
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
define("ROOT_DIR", dirname(__FILE__));
|
||||
|
||||
require("vendor/autoload.php");
|
||||
require("utilities/transform.php");
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
$aura_loader = new \Aura\Autoload\Loader;
|
||||
$aura_loader->addPrefix('Sandpiper', '.');
|
||||
$aura_loader->addPrefix('SBRL\Utilities', './utilities');
|
||||
$aura_loader->register();
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
$settings = new \SBRL\Utilities\TomlConfig("settings.toml", "default_settings.toml");
|
||||
|
||||
function send_error($code, $message) {
|
||||
http_response_code($code);
|
||||
header("content-type: text/plain");
|
||||
exit("$message\n");
|
||||
}
|
||||
|
||||
if(empty($_GET["action"]))
|
||||
send_error(400, "No action specified.");
|
||||
|
||||
$action = $_GET["action"];
|
||||
|
||||
$handler_name = "Sandpiper\\Actions\\$action";
|
||||
$handler = new $handler_name();
|
||||
$handler->handle();
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:output method="html" encoding="utf-8" indent="yes" />
|
||||
<xsl:template match="/">
|
||||
<!-- <xsl:text disable-output-escaping="yes"><!DOCTYPE html></xsl:text> -->
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><xsl:value-of select="/errors_index/project_name" /> - Sandpiper</title>
|
||||
<link rel="stylesheet" href="/theme/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Sandpiper - <xsl:value-of select="/errors_index/project_name" /></h1>
|
||||
<div class="project-list">
|
||||
<div class="project-list-header">
|
||||
<span>Error</span>
|
||||
<span>Last Updated</span>
|
||||
<span>Reports</span>
|
||||
</div>
|
||||
|
||||
<xsl:for-each select="/errors_index/error_list/error">
|
||||
<a>
|
||||
<xsl:attribute name="href"><xsl:value-of select="filename" /></xsl:attribute>
|
||||
<span class="summary">
|
||||
<xsl:value-of select="summary" />
|
||||
</span>
|
||||
<time class="last-updated">
|
||||
<xsl:value-of select="last_report" />
|
||||
</time>
|
||||
<span class="reports-count">
|
||||
<span><xsl:value-of select="report_count" /></span>
|
||||
</span>
|
||||
</a>
|
||||
</xsl:for-each>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
</xsl:transform>
|
|
@ -0,0 +1,135 @@
|
|||
html, body { font-size: 100%; }
|
||||
body
|
||||
{
|
||||
font-family: sans-serif;
|
||||
|
||||
background: hsl(52, 75%, 50%);
|
||||
color: hsl(232, 76%, 35%);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: [side-right] 10em [main] auto [side-right] 10em;
|
||||
grid-template-rows: [header] 4em [sub-header] 4em [main] auto;
|
||||
grid-template-areas: ". header ."
|
||||
". header "
|
||||
". content .";
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
grid-column-start: main;
|
||||
grid-row: header / span 2;
|
||||
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
h2 ~ h1 { grid-row: header / span 1; }
|
||||
|
||||
h2 {
|
||||
grid-column-start: main;
|
||||
grid-row-start: sub-header;
|
||||
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
.project-list
|
||||
{
|
||||
grid-column-start: main;
|
||||
grid-row-start: main;
|
||||
|
||||
display: table;
|
||||
|
||||
margin: 0; padding: 0.5em 2em 1em 2em;
|
||||
list-style-type: none;
|
||||
|
||||
background: hsla(22, 76%, 50%, 0.68);
|
||||
}
|
||||
|
||||
.project-list-header > span {
|
||||
display: table-cell;
|
||||
|
||||
margin: 0.25em 0;
|
||||
padding: 1em 0 1.5em 0;
|
||||
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.project-list > a, .project-list > div {
|
||||
display: table-row;
|
||||
}
|
||||
.project-list > a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.project-list > a > span {
|
||||
display: table-cell;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.project-list .summary {
|
||||
width: auto;
|
||||
}
|
||||
.project-list .last-updated {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.project-list .reports-count {
|
||||
width: 3em;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
.reports-count > span {
|
||||
display: inline-block;
|
||||
|
||||
min-width: 1.25em; line-height: 1.25em;
|
||||
padding: 0.25em;
|
||||
|
||||
text-align: center;
|
||||
|
||||
border-radius: 50%;
|
||||
background: hsla(232, 76%, 50%, 0.4);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
.report-list {
|
||||
grid-column-start: main;
|
||||
grid-row-start: main;
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.report {
|
||||
--padding-vertical: 0.25em;
|
||||
--padding-horizontal: 1em;
|
||||
|
||||
background: hsl(202, 76%, 50%);
|
||||
margin: 1em 0;
|
||||
padding: var(--padding-vertical) 0;
|
||||
}
|
||||
.report > div:first-child {
|
||||
font-weight: bold;
|
||||
padding: var(--padding-vertical) var(--padding-horizontal);
|
||||
}
|
||||
.report .version { float: right; }
|
||||
.report > pre {
|
||||
background: #efefef;
|
||||
padding: var(--padding-vertical) var(--padding-horizontal);
|
||||
}
|
||||
|
||||
/*
|
||||
* rgb(226,105,34)
|
||||
* rgb(226,201,34)
|
||||
* rgb(155,226,34)
|
||||
* rgb(34,59,226)
|
||||
*
|
||||
* rgb(105,34,226)
|
||||
* rgb(34,155,226)
|
||||
*/
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:output method="html" encoding="utf-8" indent="yes" />
|
||||
<xsl:template match="/">
|
||||
<!-- <xsl:text disable-output-escaping="yes"><!DOCTYPE html></xsl:text> -->
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title><xsl:value-of select="/error_info/summary" /> - <xsl:value-of select="/error_info/project_name" /> - Sandpiper</title>
|
||||
<link rel="stylesheet" href="/theme/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- HACK: We need to shrink the area that the h1 takes
|
||||
up if there's a h2 present, so since we're using the CSS3
|
||||
grid, we can put the h2 first in order to hit the h1
|
||||
as you can't select on the other way around. -->
|
||||
<!-- FUTURE: Use a <header /> here instead? -->
|
||||
<h2><xsl:value-of select="/error_info/summary" /></h2>
|
||||
<h1>Sandpiper - <xsl:value-of select="/error_info/project_name" /></h1>
|
||||
|
||||
<div class="report-list">
|
||||
|
||||
<xsl:for-each select="/error_info/reports/report">
|
||||
<xsl:sort select="position()" data-type="number" order="descending"/>
|
||||
|
||||
<div class="report">
|
||||
<div>
|
||||
<time>
|
||||
<xsl:value-of select="timestamp" />
|
||||
</time>
|
||||
<span class="version">
|
||||
<xsl:value-of select="version" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="details">
|
||||
<xsl:value-of select="details" />
|
||||
</div>
|
||||
<pre class="details"><code>
|
||||
<xsl:value-of select="stack_trace" />
|
||||
</code></pre>
|
||||
</div>
|
||||
</xsl:for-each>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
</xsl:transform>
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace External\Utilities;
|
||||
|
||||
/**
|
||||
* From https://stackoverflow.com/a/20511976/1460422
|
||||
*/
|
||||
class SimpleXMLElementExtended extends SimpleXMLElement {
|
||||
|
||||
/**
|
||||
* Adds a child with $value inside CDATA
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
*/
|
||||
public function addChildWithCDATA($name, $value = NULL) {
|
||||
$new_child = $this->addChild($name);
|
||||
|
||||
if ($new_child !== NULL) {
|
||||
$node = dom_import_simplexml($new_child);
|
||||
$no = $node->ownerDocument;
|
||||
$node->appendChild($no->createCDATASection($value));
|
||||
}
|
||||
|
||||
return $new_child;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
namespace SBRL\Utilities;
|
||||
|
||||
/****************
|
||||
* Todo
|
||||
****************
|
||||
* Test this code!
|
||||
*
|
||||
* Add support of processing instructions (<?....?>, e.g. xslt support)
|
||||
*
|
||||
* Allow customisation of the characters used for indentation
|
||||
* Allow customisation of the newline character(s)
|
||||
*/
|
||||
|
||||
class SimpleXmlWriter {
|
||||
/*
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
*/
|
||||
|
||||
//the XML doctype
|
||||
const xml_doctype = '<?xml version="1.0" encoding="utf-8"?>';
|
||||
|
||||
//the generator information
|
||||
const generator_info = "<!-- Generated by simplexmlwriter.php, which was built by Starbeamrainbowlabs -->";
|
||||
|
||||
/*
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
*/
|
||||
|
||||
//whether we should prettyprint the xml - remember to set this first!
|
||||
public $prettyprint = true;
|
||||
|
||||
//the currently generated XML - don't reference this directly as it may have unclosed tags and therefore invalid XML
|
||||
protected $xml = "";
|
||||
|
||||
//the currently open tags
|
||||
protected $opentags = [];
|
||||
|
||||
/*
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
*/
|
||||
|
||||
//begin constructing XMl
|
||||
protected function start()
|
||||
{
|
||||
$this->add($this::xml_doctype);
|
||||
$this->add($this::generator_info);
|
||||
}
|
||||
|
||||
public function add_xslt($uri = "")
|
||||
{
|
||||
$this->add("<?xml-stylesheet type=\"text/xsl\" href=\"$uri\"?>");
|
||||
}
|
||||
|
||||
//open a tag
|
||||
public function open($tagname = "root", $attributes = [])
|
||||
{
|
||||
$attrtext = "";
|
||||
foreach($attributes as $name => $value)
|
||||
$attrtext .= "$name=\"$value\" ";
|
||||
|
||||
$attrtext = " " . trim($attrtext);
|
||||
|
||||
if(strlen($attrtext) === 1)
|
||||
$attrtext = "";
|
||||
|
||||
$this->add("<$tagname$attrtext>");
|
||||
|
||||
$this->opentags[] = $tagname; //add the tag name to the list of currently open tags
|
||||
}
|
||||
|
||||
//add some plain text
|
||||
public function addtext($text = "", $newline = true)
|
||||
{
|
||||
$this->add($text, $newline);
|
||||
}
|
||||
|
||||
//close a tag
|
||||
public function close()
|
||||
{
|
||||
$this->add("</" . array_pop($this->opentags) . ">");
|
||||
}
|
||||
|
||||
//close all open tags
|
||||
public function closeall()
|
||||
{
|
||||
while(count($this->opentags) > 0)
|
||||
{
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
//shortcut to add a whole tag
|
||||
public function addtag($tagname = "root", $attributes = [], $text = "")
|
||||
{
|
||||
$attrtext = "";
|
||||
foreach($attributes as $name => $value)
|
||||
$attrtext .= "$name=\"$value\" ";
|
||||
|
||||
$attrtext = " " . trim($attrtext);
|
||||
|
||||
if(strlen($attrtext) === 1)
|
||||
$attrtext = "";
|
||||
|
||||
$this->add("<$tagname$attrtext>$text</$tagname>");
|
||||
}
|
||||
|
||||
//add a comment
|
||||
public function addcomment($text = "")
|
||||
{
|
||||
$this->add("<!-- $text -->");
|
||||
}
|
||||
|
||||
//get a rendered version of the currently generated XML
|
||||
public function render()
|
||||
{
|
||||
$result = $this->xml;
|
||||
|
||||
$this->closeall();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function indent()
|
||||
{
|
||||
$this->add($this->getindent());
|
||||
}
|
||||
|
||||
//get the appropriate number of tab characters for indentation purposes
|
||||
protected function getindent()
|
||||
{
|
||||
return str_repeat("\t", count($this->opentags));
|
||||
}
|
||||
|
||||
//function to add to the rendered XML string
|
||||
protected function add($text = "", $newline = true)
|
||||
{
|
||||
if($newline and $this->prettyprint)
|
||||
$this->xml .= $this->getindent();
|
||||
|
||||
$this->xml .= $text;
|
||||
|
||||
if($newline and $this->prettyprint)
|
||||
$this->xml .= "\n";
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Constuctor
|
||||
*
|
||||
*/
|
||||
|
||||
function __construct() {
|
||||
$this->start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"]))
|
||||
{
|
||||
//we were not included
|
||||
|
||||
$xml = new simplexmlwriter();
|
||||
$xml->add_xslt("https://example.com/test.xslt");
|
||||
$xml->open("root");
|
||||
$xml->addcomment("- ------------------------------ -");
|
||||
$xml->addcomment("- -- simplexmlwriter.php test -- -");
|
||||
$xml->addcomment("- ------------------------------ -");
|
||||
foreach(range(1, 10) as $i)
|
||||
{
|
||||
$xml->open("number");
|
||||
$xml->addtag("type", [], "integer");
|
||||
$xml->open("value");
|
||||
$xml->addtext($i);
|
||||
$xml->close();
|
||||
$xml->close();
|
||||
}
|
||||
$xml->closeall();
|
||||
header("content-type: application/xml");
|
||||
echo($xml->render());
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace SBRL\Utilities;
|
||||
|
||||
use \stdClass;
|
||||
use \Yosymfony\Toml\Toml;
|
||||
use \Yosymfony\Toml\TomlBuilder;
|
||||
|
||||
/**
|
||||
* Handles the loading and saving of settings from a toml file.
|
||||
* Supports loading default settings from a separate file.
|
||||
* @author Starbeamrainbowlabs
|
||||
* @version v0.2
|
||||
* @lastModified 22nd March 2017
|
||||
* @license https://www.mozilla.org/en-US/MPL/2.0/ Mozilla Public License 2.0
|
||||
* Changelog:
|
||||
* v0.3 - 5th March 2018
|
||||
* Forked to create a version for toml.
|
||||
* v0.2 - 22nd March 2017
|
||||
* Add license
|
||||
* Filled in some missing comments
|
||||
* v0.1: 28th February 2017
|
||||
* Initial release
|
||||
*/
|
||||
class TomlConfig
|
||||
{
|
||||
private $defaultSettings;
|
||||
|
||||
private $settingsFilePath = "";
|
||||
/**
|
||||
* The custom settings object.
|
||||
* @var stdClass
|
||||
*/
|
||||
private $customSettings;
|
||||
|
||||
/**
|
||||
* The banner to insert at the top of the custom settings file when saving it.
|
||||
* @var string
|
||||
*/
|
||||
private $customSettingsBanner;
|
||||
|
||||
/**
|
||||
* Creates a new JsonConfig
|
||||
* @param string $settingsFilePath The path to the customised settings file.
|
||||
* @param string $defaultSettingsFilePath The path to the default settings file.
|
||||
*/
|
||||
public function __construct($settingsFilePath, $defaultSettingsFilePath) {
|
||||
$this->customSettingsBanner = "-------[ Custom Settings File - Last updated " . date("Y-m-d") . " ]-------";
|
||||
|
||||
$this->defaultSettings = Toml::ParseFile($defaultSettingsFilePath, true);
|
||||
|
||||
$this->customSettings = new stdClass();
|
||||
if(file_exists($settingsFilePath)) {
|
||||
$this->customSettings = Toml::ParseFile($settingsFilePath, true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the value identified by the specified property, in dotted notation (e.g. `Network.Firewalling.Ports.Http`)
|
||||
* @param string $key The property, in dotted notation, to return.
|
||||
* @return mixed The value odentified by the given dotted property notation.
|
||||
*/
|
||||
public function get($key) {
|
||||
if(static::hasPropertyByPath($this->customSettings, $key)) {
|
||||
return static::getPropertyByPath($this->customSettings, $key);
|
||||
}
|
||||
return static::getPropertyByPath($this->defaultSettings, $key);
|
||||
}
|
||||
/**
|
||||
* Fetches the default value for the specified property, specified in dotted notation.
|
||||
* @param string $key The key to fetch.
|
||||
* @return mixed The default value of the property identified by the given dotted notation.
|
||||
*/
|
||||
public function getDefault($key) {
|
||||
return static::getPropertyByPath($this->defaultSettings, $key);
|
||||
}
|
||||
/**
|
||||
* Determines whether the given property has been customised or explicitly specified in the customised settings file.
|
||||
* @param string $key The property, in dotted notation, to check.
|
||||
* @return boolean Whether the specified properties file has been explicitly specified in the custom settings file.
|
||||
*/
|
||||
public function hasChanged($key) {
|
||||
return static::hasPropertyByPath($this->customSettings, $key);
|
||||
}
|
||||
/**
|
||||
* Sets the property identified by the specified dotted path.
|
||||
* Does not re-save the properties back to disk!
|
||||
* @param string $key The dotted path to update.
|
||||
* @param mixed $value The value to set the property to.
|
||||
* @return self This ConfigFile instance, to aid method chaining.
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
static::setPropertyByPath($this->customSettings, $key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCustomSettingsBanner($value) {
|
||||
$this->customSettingsBanner = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the customised settings back to the disk.
|
||||
* @return boolean Whether the save was successful or not.
|
||||
*/
|
||||
public function save() {
|
||||
$output_builder = new TomlBuilder();
|
||||
$toml_builder->addComment($this->customSettingsBanner);
|
||||
|
||||
$this->build($toml_builder, $this->customSettings);
|
||||
|
||||
return file_put_contents($settingsFilePath, $toml_builder->getTomlString());
|
||||
}
|
||||
|
||||
private function build($toml_builder, $customSettingsObject, $subobject_name = null) {
|
||||
if($subobject_name !== null)
|
||||
$toml_builder->addTable($subobject_name);
|
||||
|
||||
foreach($customSettingsObject as $key => $value) {
|
||||
if(!\is_object($value))
|
||||
$toml_builder->addValue($key, $value);
|
||||
else
|
||||
$this->build($toml_builder, $value, "$subobject_name.$key");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Works out whether a given dotted path to a property exists on a given object.
|
||||
* @param stdClass $obj The object to search.
|
||||
* @param string $path The dotted path to search with. e.g. `Network.Firewall.OpenPorts`
|
||||
* @return boolean Whether the given property is present in the given object.
|
||||
*/
|
||||
public static function hasPropertyByPath($obj, $path) {
|
||||
$pathParts = explode(".", $path);
|
||||
$subObj = $obj;
|
||||
foreach($pathParts as $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;
|
||||
}
|
||||
/**
|
||||
* Returns the property identified by the given dotted path.
|
||||
* If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check.
|
||||
* @param stdClass $obj The object to fetch from.
|
||||
* @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, $defaultValue = null) {
|
||||
$pathParts = explode(".", $path);
|
||||
$subObj = $obj;
|
||||
foreach($pathParts as $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;
|
||||
}
|
||||
/**
|
||||
* Returns the property identified by the given dotted path.
|
||||
* If you're not sure whether a property exists or not, use static::hasPropertyByPath() first to check.
|
||||
* @param stdClass $obj The object to set the property on.
|
||||
* @param string $path The path to set on the given object.
|
||||
* @param mixed $value The value to set the given property to.
|
||||
*/
|
||||
public static function setPropertyByPath($obj, $path, $value) {
|
||||
$pathParts = explode(".", $path);
|
||||
$pathPartsCount = count($pathParts);
|
||||
|
||||
$subObj = $obj;
|
||||
for($i = 0; $i < $pathPartsCount; $i++) {
|
||||
// Set the property on the last iteration
|
||||
if($i + 1 == $pathPartsCount) {
|
||||
$subObj[$part] = $value;
|
||||
break;
|
||||
}
|
||||
// Create sub-objects if they dobn't exist already
|
||||
if(!isset($subObj->$part)) {
|
||||
$subObj[$part] = new stdClass();
|
||||
}
|
||||
$subObj = $subObj[$part];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/***********************************************
|
||||
* Comment Key Generation System
|
||||
***********************************************
|
||||
* By Starbeamrainbowlabs <feedback@starbeamrainbowlabs.com>
|
||||
*
|
||||
* A set of functions to protect low-value comment boxes, contact us
|
||||
* forms, etc. from spam.
|
||||
*
|
||||
* It works by recording the time the page the form is on was loaded,
|
||||
* and then checking to make sure that it was loaded for at least
|
||||
* 10 - 15 seconds, and not more than 24 hours.
|
||||
*
|
||||
* This is done by taking the current UNIX timestamp, running it through
|
||||
* a number of (arbitrary) reversible transformations, and then
|
||||
* embedding it in the form as a hidden field. When the form is
|
||||
* submitted, the transformations are reversed and the time the page
|
||||
* was loaded checked.
|
||||
*
|
||||
* Note that this is not suitable for high-value targets, as it can very
|
||||
* easily be bypassed and reverse-engineered by any determined human.
|
||||
*
|
||||
***********************************************
|
||||
* Changelog:
|
||||
* 30th January 2018:
|
||||
* Initial Release.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates a new comment key.
|
||||
* Note that the 'encryption' used is not secure - it's just simple XOR!
|
||||
* It's mainly to better support verification in complex setups and
|
||||
* serve as a nother annoying hurdle for spammers.
|
||||
* TODO: Swap out the XOR for openssl AES symmetric encryption
|
||||
* @param string $pass The password to transform it with. Should be a pre-selected random string.
|
||||
* @return string A new comment key stamped at the current time.
|
||||
*/
|
||||
function key_generate($pass) {
|
||||
$new_key = strval(time());
|
||||
// Repeat the key so that it's long enough to XOR the key with
|
||||
$pass_enc = str_repeat($pass, (strlen($new_key) / strlen($pass)) + 1);
|
||||
$new_key = $new_key ^ $pass_enc;
|
||||
return base64_encode(strrev($new_key));
|
||||
}
|
||||
/**
|
||||
* Decodes the given key.
|
||||
* @param string $key The key to decode.
|
||||
* @param string $pass The password to decode it with.
|
||||
* @return int The time that the key was generated.
|
||||
*/
|
||||
function key_decode($key, $pass) {
|
||||
$key_dec = strrev(base64_decode($key));
|
||||
// Repeat the key so that it's long enough to XOR the key with
|
||||
$pass_dec = str_repeat($pass, (strlen($key_dec) / strlen($pass)) + 1);
|
||||
return intval($key_dec ^ $pass_dec);
|
||||
}
|
||||
/**
|
||||
* Verifies a key.
|
||||
* @param string $key The key to decode and verify.
|
||||
* @param string $pass The password to decode the key with.
|
||||
* @param int $min_age The minimum required age for the key.
|
||||
* @param int $max_age The maximum allowed age for the key.
|
||||
* @return bool Whether the key is valid or not.
|
||||
*/
|
||||
function key_verify($key, $pass, $min_age, $max_age) {
|
||||
$age = time() - key_decode($key, $pass);
|
||||
return $age >= $min_age && $age <= $max_age;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?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 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) {
|
||||
libxml_disable_entity_loader(false);
|
||||
$dom = new DomDocument;
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($simplexml_node->asXML(), LIBXML_NONET);
|
||||
return $dom->saveXML();
|
||||
}
|
Reference in New Issue