aboutsummaryrefslogtreecommitdiff
path: root/r7r_repo
diff options
context:
space:
mode:
Diffstat (limited to 'r7r_repo')
-rw-r--r--r7r_repo/.htaccess4
-rw-r--r--r7r_repo/config.php12
-rw-r--r--r7r_repo/css/common.css54
-rw-r--r--r7r_repo/css/images/error.pngbin0 -> 1072 bytes
-rw-r--r--r7r_repo/css/images/error.svg44
-rw-r--r--r7r_repo/css/images/notice.pngbin0 -> 1320 bytes
-rw-r--r--r7r_repo/css/images/notice.svg106
-rw-r--r--r7r_repo/css/images/success.pngbin0 -> 1518 bytes
-rw-r--r--r7r_repo/css/images/success.svg102
-rw-r--r--r7r_repo/css/main.css114
-rw-r--r--r7r_repo/css/setup.css11
-rw-r--r--r7r_repo/db.php106
-rw-r--r--r7r_repo/main.php201
-rw-r--r--r7r_repo/models.php396
-rw-r--r--r7r_repo/pluginpackage.php288
-rw-r--r--r7r_repo/pwhash.php74
-rw-r--r--r7r_repo/stupid_template_engine.php1297
-rwxr-xr-xr7r_repo/templates/.htaccess4
-rw-r--r--r7r_repo/templates/src/home.html35
-rw-r--r--r7r_repo/templates/src/master.html90
-rw-r--r--r7r_repo/templates/src/setup.html53
-rw-r--r--r7r_repo/urlprocess.php179
-rw-r--r--r7r_repo/utils.php229
23 files changed, 3399 insertions, 0 deletions
diff --git a/r7r_repo/.htaccess b/r7r_repo/.htaccess
new file mode 100644
index 0000000..dec4e25
--- /dev/null
+++ b/r7r_repo/.htaccess
@@ -0,0 +1,4 @@
+<FilesMatch "\.php$">
+ Order Allow,Deny
+ Deny from all
+</FilesMatch>
diff --git a/r7r_repo/config.php b/r7r_repo/config.php
new file mode 100644
index 0000000..7a05f24
--- /dev/null
+++ b/r7r_repo/config.php
@@ -0,0 +1,12 @@
+<?php
+
+define("CONFIG_FILLED_OUT", True);
+define("SKIP_TABLE_CREATION", False);
+
+$config["mysql"]["server"] = "localhost";
+$config["mysql"]["db"] = "s_db_47";
+$config["mysql"]["user"] = "dbuser_47";
+$config["mysql"]["passwd"] = "DfXVQBoVOBPbLlL";
+$config["mysql"]["prefix"] = "repo_";
+
+?>
diff --git a/r7r_repo/css/common.css b/r7r_repo/css/common.css
new file mode 100644
index 0000000..767d017
--- /dev/null
+++ b/r7r_repo/css/common.css
@@ -0,0 +1,54 @@
+* {
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+html {
+ margin: 0px;
+ padding: 0px;
+}
+
+body {
+ margin: 0px;
+ padding: 0px;
+}
+
+#maincontainer {
+ width: 80%;
+ margin: 0px auto 0px;
+ padding: 0px;
+}
+
+div.error {
+ border: 1px solid #8d8d8d;
+ background: #ddd url(images/error.png) no-repeat top left;
+ min-height: 40px;
+ color: #222;
+ padding: 1em;
+ margin: 3mm 0mm 3mm;
+ text-align: center;
+}
+
+div.notice {
+ border: 1px solid #8d8d8d;
+ background: #ddd url(images/notice.png) no-repeat top left;
+ min-height: 40px;
+ color: #222;
+ padding: 1em;
+ margin: 3mm 0mm 3mm;
+ text-align: center;
+}
+
+div.success {
+ border: 1px solid #8d8d8d;
+ background: #ddd url(images/success.png) no-repeat top left;
+ min-height: 40px;
+ color: #222;
+ padding: 1em;
+ margin: 3mm 0mm 3mm;
+ text-align: center;
+}
+
+.fullwidth {
+ width: 100%;
+}
diff --git a/r7r_repo/css/images/error.png b/r7r_repo/css/images/error.png
new file mode 100644
index 0000000..de5e5f9
--- /dev/null
+++ b/r7r_repo/css/images/error.png
Binary files differ
diff --git a/r7r_repo/css/images/error.svg b/r7r_repo/css/images/error.svg
new file mode 100644
index 0000000..6ebe085
--- /dev/null
+++ b/r7r_repo/css/images/error.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ width="50"
+ height="50"
+ id="svg2">
+ <defs
+ id="defs4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-1002.3622)"
+ id="layer1">
+ <path
+ d="M -14.344328,1046.3622 46,1046.3622 15.827836,986.0179 z"
+ id="path3763"
+ style="fill:none;stroke:#000000;stroke-width:5.80233908;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.36078431;stroke-dasharray:none" />
+ <g
+ id="text3765"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:0.75294118;stroke:none;font-family:Tahoma;-inkscape-font-specification:Tahoma">
+ <path
+ d="m 16.069878,1032.1201 c -2.367352,0 -4.316941,1.9034 -4.316941,4.1777 0,2.4602 1.903169,4.4096 4.270521,4.4096 2.367354,0 4.316942,-1.9494 4.316942,-4.3168 0,-2.3671 -1.90317,-4.2705 -4.270522,-4.2705 m 1.903166,-5.5654 c 0.324932,-4.456 0.603443,-6.3594 1.624656,-10.9085 0.928372,-3.899 1.114049,-4.8739 1.114049,-6.1736 0,-3.1099 -1.810333,-5.106 -4.595453,-5.106 -2.645864,0 -4.68829,2.0425 -4.68829,4.6417 0,1.5318 0.185675,2.6462 1.114049,6.9164 0.789118,3.5743 1.206886,6.3133 1.624656,10.63 l 3.806333,0"
+ id="path3770"
+ style="font-size:20px;font-weight:bold;fill:#000000;fill-opacity:0.36078431;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+ </g>
+ </g>
+</svg>
diff --git a/r7r_repo/css/images/notice.png b/r7r_repo/css/images/notice.png
new file mode 100644
index 0000000..30957e8
--- /dev/null
+++ b/r7r_repo/css/images/notice.png
Binary files differ
diff --git a/r7r_repo/css/images/notice.svg b/r7r_repo/css/images/notice.svg
new file mode 100644
index 0000000..1025847
--- /dev/null
+++ b/r7r_repo/css/images/notice.svg
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50"
+ height="50"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 r9760"
+ inkscape:export-filename="/home/skadu42/Desktop/notice.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docname="notice.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#dddddd"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.2"
+ inkscape:cx="13.477603"
+ inkscape:cy="32.102921"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1680"
+ inkscape:window-height="1000"
+ inkscape:window-x="0"
+ inkscape:window-y="26"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ dotted="false" />
+ <sodipodi:guide
+ orientation="-0.70710678,0.70710678"
+ position="30,30"
+ id="guide3755" />
+ <sodipodi:guide
+ orientation="0.70710678,0.70710678"
+ position="0,50"
+ id="guide3757" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="25,25"
+ id="guide3759" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="25,25"
+ id="guide3761" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Ebene 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1002.3622)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#000000;fill-opacity:0.36078431;stroke:#dddddd;stroke-width:4.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none"
+ id="path3862"
+ sodipodi:cx="25"
+ sodipodi:cy="25"
+ sodipodi:rx="25"
+ sodipodi:ry="25"
+ d="M 50,25 A 25,25 0 1 1 0,25 25,25 0 1 1 50,25 z"
+ transform="matrix(1.1227732,0,0,1.1227732,-9,994.06606)" />
+ <g
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#dddddd;fill-opacity:1;stroke:none;font-family:Tahoma;-inkscape-font-specification:Tahoma"
+ id="text3854"
+ transform="matrix(1.2475258,0,0,1.2475258,-13.441193,-256.15217)">
+ <path
+ d="m 30.068001,1034.1142 1.056,0.672 c -1.69601,2.528 -3.104009,4.256 -4.224,5.184 -1.088006,0.896 -2.352005,1.344 -3.792,1.344 -0.992003,0 -1.792002,-0.272 -2.4,-0.816 -0.608001,-0.576 -0.912,-1.312 -0.912,-2.208 0,-0.768 0.207999,-1.872 0.624,-3.312 l 2.784,-10.32 c 0.223996,-0.96 0.335996,-1.6 0.336,-1.92 -4e-6,-0.512 -0.224004,-0.864 -0.672,-1.056 -0.416003,-0.224 -1.136002,-0.336 -2.16,-0.336 l 0,-1.296 c 3.711995,-0.288 6.943992,-0.736 9.696,-1.344 l -4.368,16.032 c -0.288006,1.152 -0.432006,1.984 -0.432,2.496 -6e-6,0.192 0.06399,0.368 0.192,0.528 0.159994,0.128 0.335993,0.192 0.528,0.192 0.767992,0 1.791991,-0.96 3.072,-2.88 l 0.672,-0.96 m -1.056,-26.112 c 0.89599,0 1.663989,0.32 2.304,0.96 0.671987,0.64 1.007987,1.408 1.008,2.304 -1.3e-5,0.896 -0.320013,1.664 -0.96,2.304 -0.640011,0.608 -1.42401,0.912 -2.352,0.912 -0.896009,0 -1.648008,-0.32 -2.256,-0.96 -0.608007,-0.64 -0.912006,-1.424 -0.912,-2.352 -6e-6,-0.832 0.319993,-1.568 0.96,-2.208 0.639992,-0.64 1.375991,-0.96 2.208,-0.96"
+ style="font-size:48px;font-style:italic;font-weight:bold;fill:#dddddd;fill-opacity:1;font-family:FreeSerif;-inkscape-font-specification:FreeSerif Bold Italic"
+ id="path3867"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/r7r_repo/css/images/success.png b/r7r_repo/css/images/success.png
new file mode 100644
index 0000000..dc79180
--- /dev/null
+++ b/r7r_repo/css/images/success.png
Binary files differ
diff --git a/r7r_repo/css/images/success.svg b/r7r_repo/css/images/success.svg
new file mode 100644
index 0000000..39a606d
--- /dev/null
+++ b/r7r_repo/css/images/success.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="50"
+ height="50"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 r9760"
+ inkscape:export-filename="/home/skadu42/Desktop/success.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docname="success.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#dddddd"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.9195959"
+ inkscape:cx="27.664243"
+ inkscape:cy="33.687243"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1680"
+ inkscape:window-height="1000"
+ inkscape:window-x="0"
+ inkscape:window-y="26"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ dotted="false" />
+ <sodipodi:guide
+ orientation="-0.70710678,0.70710678"
+ position="30,30"
+ id="guide3755" />
+ <sodipodi:guide
+ orientation="0.70710678,0.70710678"
+ position="0,50"
+ id="guide3757" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="25,25"
+ id="guide3759" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="25,25"
+ id="guide3761" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Ebene 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1002.3622)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#000000;fill-opacity:0.36078431;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none"
+ id="path3828"
+ sodipodi:cx="25"
+ sodipodi:cy="25"
+ sodipodi:rx="23"
+ sodipodi:ry="23"
+ d="M 48,25 A 23,23 0 1 1 2,25 23,23 0 1 1 48,25 z"
+ transform="matrix(1.2564831,0,0,1.2564831,-12.124007,989.84923)" />
+ <path
+ style="fill:none;stroke:#dddddd;stroke-width:5.65417433;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 5.4667569,1021.2614 11.3083491,16.3342 21.360213,-32.6686"
+ id="path3832"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
diff --git a/r7r_repo/css/main.css b/r7r_repo/css/main.css
new file mode 100644
index 0000000..7061c42
--- /dev/null
+++ b/r7r_repo/css/main.css
@@ -0,0 +1,114 @@
+#heading {
+ text-align: center;
+ border-bottom: 1px solid black;
+ margin: 0px auto 0px;
+ padding: 10mm 3mm 5mm
+}
+
+h1 {
+ font-size: 24pt;
+ font-weight: bold;
+ padding: 0px;
+ margin: 0px auto 2mm;
+}
+
+h2 {
+ font-size: 12pt;
+ font-weight: bold;
+}
+
+#mainmenu {
+ border-bottom: 1px solid black;
+ list-style: none;
+ height: 10mm;
+ padding: 0px;
+ margin: 0px 0px 2mm;
+ overflow: hidden;
+}
+
+#mainmenu li {
+ float: left;
+ margin: 0px 0px 2mm;
+}
+
+#mainmenu li a {
+ color: #444;
+ text-decoration: none;
+ font-size: 12pt;
+ margin: 0px;
+ padding: 2mm 7.5mm 0px;
+ background: white;
+ display: block;
+ height: 10mm;
+}
+
+#mainmenu li.active a {
+ color: black;
+ font-weight: bold;
+}
+
+#mainmenu li a:hover {
+ background: #eee;
+ color: #000;
+}
+
+#metabar {
+ float: right;
+ width: 50mm;
+ margin: 0px;
+ padding: 0px 0px 0px 5mm;
+ border-left: 1px solid black;
+}
+
+div.metabar_module {
+ border-top: 1px solid black;
+ padding: 2mm 0px 0px;
+ margin: 2mm 0px 0px;
+}
+
+div.metabar_module:first-child {
+ border-top: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+div.metabar_module h2 {
+ font-size: 10pt;
+ font-weight: bold;
+ padding: 0px;
+ margin: 0px 0px 2mm;
+}
+
+#content {
+ border-right: 1px solid black;
+ margin: 0px 55mm 0px 0px;
+ padding: 0px 2mm 0px 0px;
+}
+
+#footer {
+ clear: both;
+ height: 5mm;
+}
+
+table.listtab {
+ border-collapse: collapse;
+}
+
+table.listtab th {
+ font-weight: bold;
+ color: black;
+ text-align: left;
+ padding-left: 1ex;
+}
+
+table.listtab td {
+ border-top: 1px solid #eee;
+}
+
+table.listtab tr:first-child td {
+ border-top: 1px solid #666;
+}
+
+table.listtab tbody tr:hover {
+ background: #eee;
+}
diff --git a/r7r_repo/css/setup.css b/r7r_repo/css/setup.css
new file mode 100644
index 0000000..2466a4a
--- /dev/null
+++ b/r7r_repo/css/setup.css
@@ -0,0 +1,11 @@
+body {
+ text-align: center;
+}
+
+h1 {
+ font-weight: bold;
+ font-size: 16pt;
+ border-bottom: 1px solid gray;
+ margin: 0px 0px 1em;
+ padding: 1em 0px 0.5em;
+}
diff --git a/r7r_repo/db.php b/r7r_repo/db.php
new file mode 100644
index 0000000..459a973
--- /dev/null
+++ b/r7r_repo/db.php
@@ -0,0 +1,106 @@
+<?php
+/*
+ * File: ratatoeskr/sys/db.php
+ *
+ * Helper functions for dealing with MySQL.
+ *
+ * License:
+ * This file is part of Ratatöskr.
+ * Ratatöskr is licensed unter the MIT / X11 License.
+ * See "ratatoeskr/licenses/ratatoeskr" for more information.
+ */
+
+require_once(dirname(__FILE__) . "/config.php");
+require_once(dirname(__FILE__) . "/utils.php");
+
+/*
+ * Function: db_connect
+ *
+ * Establish a connection to the MySQL database.
+ */
+function db_connect()
+{
+ global $config;
+ $db_connection = mysql_pconnect(
+ $config["mysql"]["server"],
+ $config["mysql"]["user"],
+ $config["mysql"]["passwd"]);
+ if(!$db_connection)
+ die("Could not connect to database server. " . mysql_error());
+
+ if(!mysql_select_db($config["mysql"]["db"], $db_connection))
+ die("Could not open database. " . mysql_error());
+
+ mysql_query("SET NAMES 'utf8'", $db_connection);
+}
+
+function sqlesc($str)
+{
+ return mysql_real_escape_string($str);
+}
+
+/*
+ * Function: qdb_vfmt
+ * Like <qdb_fmt>, but needs arguments as single array.
+ *
+ * Parameters:
+ * $args - The arguments as an array.
+ *
+ * Returns:
+ * The formatted string.
+ */
+function qdb_vfmt($args)
+{
+ global $config;
+
+ if(count($args) < 1)
+ throw new InvalidArgumentException('Need at least one parameter');
+
+ $query = $args[0];
+
+ $data = array_map(function($x) { return is_string($x) ? sqlesc($x) : $x; }, array_slice($args, 1));
+ $query = str_replace("PREFIX_", $config["mysql"]["prefix"], $query);
+
+ return vsprintf($query, $data);
+}
+
+/*
+ * Function: qdb_fmt
+ * Formats a string like <qdb>, that means it replaces "PREFIX_" and <sqlesc>'s everything before sends everything to vsprintf.
+ *
+ * Returns:
+ * The formatted string.
+ */
+function qdb_fmt()
+{
+ return qdb_vfmt(func_get_args());
+}
+
+
+/*
+ * Function: qdb
+ * Query Database.
+ *
+ * This function replaces mysql_query and should eliminate SQL-Injections.
+ * Use it like this:
+ *
+ * $result = qdb("SELECT `foo` FROM `bar` WHERE `id` = %d AND `baz` = '%s'", 100, "lol");
+ *
+ * It will also replace "PREFIX_" with the prefix defined in 'config.php'.
+ */
+function qdb()
+{
+ $query = qdb_vfmt(func_get_args());
+ $rv = mysql_query($query);
+ if($rv === false)
+ throw new MySQLException(mysql_errno() . ': ' . mysql_error() . (__DEBUG__ ? ("[[FULL QUERY: " . $query . "]]") : "" ));
+ return $rv;
+}
+
+/*
+ * Class: MySQLException
+ * Will be thrown by qdb*, if the query induced an MySQL error.
+ */
+class MySQLException extends Exception { }
+
+?>
diff --git a/r7r_repo/main.php b/r7r_repo/main.php
new file mode 100644
index 0000000..af19397
--- /dev/null
+++ b/r7r_repo/main.php
@@ -0,0 +1,201 @@
+<?php
+
+/* Load and check config */
+require_once(dirname(__FILE__) . "/config.php");
+if(!defined("CONFIG_FILLED_OUT"))
+ die("Config file not filled out.");
+
+/* Include database functions... */
+require_once(dirname(__FILE__) . "/db.php");
+db_connect();
+
+/* create tables */
+if(!SKIP_TABLE_CREATION)
+{
+ $db_structure = "CREATE TABLE IF NOT EXISTS `PREFIX_packages` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` text COLLATE utf8_unicode_ci NOT NULL,
+ `lastversion` int(11) NOT NULL,
+ `user` int(11) NOT NULL,
+ `description` text COLLATE utf8_unicode_ci NOT NULL,
+ `lastupdate` bigint(20) NOT NULL,
+ `txtversion` text COLLATE utf8_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+CREATE TABLE IF NOT EXISTS `PREFIX_settings_kvstorage` (
+ `key` text COLLATE utf8_unicode_ci NOT NULL,
+ `value` text COLLATE utf8_unicode_ci NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+CREATE TABLE IF NOT EXISTS `PREFIX_users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` text COLLATE utf8_unicode_ci NOT NULL,
+ `pwhash` text COLLATE utf8_unicode_ci NOT NULL,
+ `isadmin` tinyint(4) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;";
+ $queries = explode(";", $db_structure);
+ foreach($queries as $q)
+ {
+ if(!empty($q))
+ qdb($q);
+ }
+}
+
+/* Include more files... */
+require_once(dirname(__FILE__) . "/stupid_template_engine.php");
+require_once(dirname(__FILE__) . "/models.php");
+require_once(dirname(__FILE__) . "/urlprocess.php");
+require_once(dirname(__FILE__) . "/pwhash.php");
+require_once(dirname(__FILE__) . "/pluginpackage.php");
+require_once(dirname(__FILE__) . "/utils.php");
+
+/* init STE */
+$tpl_basedir = dirname(__FILE__). "/templates";
+$ste = new \ste\STECore(new \ste\FilesystemStorageAccess("$tpl_basedir/src", "$tpl_basedir/transc"));
+
+$ste->register_tag(
+ "loremipsum",
+ function($ste, $params, $sub)
+ {
+ $repeats = empty($params["repeat"]) ? 1 : $params["repeat"] + 0;
+ return implode("\n\n", array_repeat("<p>Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>\n\n<p>Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.</p>\n\n<p>Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.</p>\n\n<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.</p>\n\n<p>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.</p>\n\n<p>Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>", $repeats));
+ }
+);
+
+/* start session and check auth. */
+session_start();
+$user = NULL;
+if(isset($_SESSION["r7r_repo_login_name"]))
+{
+ try
+ {
+ $user = User::by_name($_SESSION["r7r_repo_login_name"]);
+ }
+ catch(DoesNotExistError $e)
+ {
+ unset($_SESSION["r7r_repo_login_name"]);
+ }
+}
+
+/* url handlers */
+$url_handlers = array(
+ "_prelude" => function(&$data, $url_now, &$url_next)
+ {
+ global $ste, $settings, $user;
+
+ if(@$settings["setup_finished"])
+ $ste->vars["repo"] = array(
+ "name" => $settings["repo_name"],
+ "description" => $settings["repo_description"],
+ "baseurl" => $settings["repo_baseurl"],
+ "public" => ($settings["repo_mode"] == "public")
+ );
+
+ if($user === NULL)
+ $ste->vars["user"] = array(
+ "logged_in" => False,
+ "admin" => False
+ );
+ else
+ $ste->vars["user"] = array(
+ "logged_in" => True,
+ "name" => $user->name,
+ "admin" => $user->isadmin
+ );
+ },
+ "_notfound" => url_action_simple(function($data)
+ {
+ header("HTTP/1.1 404 Not Found");
+ header("Content-Type: text/plain");
+ echo "404 Not Found\nThe resource \"{$_SERVER["REQUEST_URI"]}\" could not be found.\n";
+ }),
+ "_index" => url_action_alias(array("index")),
+ "index" => function(&$data, $url_now, &$url_next)
+ {
+ global $ste;
+ $url_next = array();
+
+ $ste->vars["menu"] = "home";
+
+ $latest = Package::latest();
+ $ste->vars["latest_pkgs"] = array_map(function($pkg) { return array(
+ "name" => $pkg->get_name(),
+ "version" => $pkg->txtversion,
+ "description" => $pkg->description,
+ "last_update" => $pkg->lastversion
+ ); }, $latest);
+
+ echo $ste->exectemplate("home.html");
+ },
+ "setup" => function(&$data, $url_now, &$url_next)
+ {
+ global $settings, $ste;
+
+ /* If initial setup was already finished, nobody should be allowed to access this. */
+ if(@$settings["setup_finished"])
+ throw new NotFoundError();
+
+ $url_next = array();
+
+ /* Test file permissions */
+ $permissions_missing = array_filter(array("/packages", "/packagelist", "/repometa"), function($f) { return !@is_writable(dirname(__FILE__) . "/..$f"); });
+ if(!empty($permissions_missing))
+ $ste->vars["error"] = "No writing permissions on these files/directories: \"" . implode("\", \"", $permissions_missing) . "\"";
+ else
+ {
+ /* Check input */
+ if(!empty($_POST["send_data"]))
+ {
+ if(empty($_POST["admin_name"]) or empty($_POST["admin_password"]) or empty($_POST["repo_name"]) or empty($_POST["repo_description"]) or empty($_POST["repo_baseurl"]) or (($_POST["repo_mode"] != "public") and ($_POST["repo_mode"] != "private")))
+ $ste->vars["error"] = "Form not filled out completely";
+ else
+ {
+ /* Insert data */
+ $admin = User::create($_POST["admin_name"]);
+ $admin->pwhash = PasswordHash::create($_POST["admin_password"]);
+ $admin->isadmin = True;
+ $admin->save();
+ $settings["repo_name"] = $_POST["repo_name"];
+ $settings["repo_description"] = $_POST["repo_description"];
+ $settings["repo_baseurl"] = $_POST["repo_baseurl"];
+ $settings["repo_mode"] = $_POST["repo_mode"];
+
+ update_repometa();
+
+ $settings["setup_finished"] = True;
+
+ $url_next = array("index");
+ return;
+ }
+ }
+ }
+
+ $ste->vars["baseurl_predicted"] = self_url();
+ echo $ste->exectemplate("setup.html");
+ }
+);
+
+/* bootstrapping... */
+$urlpath = explode("/", $_GET["action"]);
+$rel_path_to_root = implode("/", array_merge(array("."), array_repeat("..", count($urlpath) - 1)));
+$GLOBALS["rel_path_to_root"] = $rel_path_to_root;
+$data = array("rel_path_to_root" => $rel_path_to_root);
+$ste->vars["rel_path_to_root"] = $rel_path_to_root;
+/* Enforce setup */
+if(!@$settings["setup_finished"])
+ $urlpath = array("setup");
+/*try
+{*/
+ url_process($urlpath, $url_handlers, $data);
+/*}
+catch(Exception $e)
+{
+ header("HTTP/1.1 500 Internal Server Error");
+ header("Content-Type: text/plain");
+ echo "Internal Server Error\nReason: " . get_class($e) . "(" . $e->getMessage() . ") thrown.\n";
+}*/
+
+/* save settings */
+$settings->save();
+
+?>
diff --git a/r7r_repo/models.php b/r7r_repo/models.php
new file mode 100644
index 0000000..1eb7caf
--- /dev/null
+++ b/r7r_repo/models.php
@@ -0,0 +1,396 @@
+<?php
+
+require_once(dirname(__FILE__) . "/db.php");
+require_once(dirname(__FILE__) . "/utils.php");
+
+/* Exceptions copied from Ratatöskr */
+class DoesNotExistError extends Exception { }
+class AlreadyExistsError extends Exception { }
+class NotAllowedError extends Exception { }
+
+/* copied from Ratatöskr: */
+abstract class BySQLRowEnabled
+{
+ protected function __construct() { }
+
+ abstract protected function populate_by_sqlrow($sqlrow);
+
+ protected static function by_sqlrow($sqlrow)
+ {
+ $obj = new static();
+ $obj->populate_by_sqlrow($sqlrow);
+ return $obj;
+ }
+}
+
+/* SettingsIterator ans Settings copied from Ratatöskr */
+
+class SettingsIterator implements Iterator
+{
+ private $index;
+ private $keys;
+ private $settings_obj;
+
+ public function __construct($settings_obj, $keys)
+ {
+ $this->index = 0;
+ $this->settings_obj = $settings_obj;
+ $this->keys = $keys;
+ }
+
+ /* Iterator implementation */
+ public function current() { return $this->settings_obj[$this->keys[$this->index]]; }
+ public function key() { return $this->keys[$this->index]; }
+ public function next() { ++$this->index; }
+ public function rewind() { $this->index = 0; }
+ public function valid() { return $this->index < count($this->keys); }
+}
+
+/*
+ * Class: Settings
+ * A class that holds the Settings of Ratatöskr.
+ * You can access settings like an array.
+ */
+class Settings implements ArrayAccess, IteratorAggregate, Countable
+{
+ /* Singleton implementation */
+ private function __copy() {}
+ private static $instance = NULL;
+ /*
+ * Constructor: get_instance
+ * Get an instance of this class.
+ * All instances are equal (ie. this is a singleton), so you can also use
+ * the global <$ratatoeskr_settings> instance.
+ */
+ public static function get_instance()
+ {
+ if(self::$instance === NULL)
+ self::$instance = new self;
+ return self::$instance;
+ }
+
+ private $buffer;
+ private $to_be_deleted;
+ private $to_be_created;
+ private $to_be_updated;
+
+ private function __construct()
+ {
+ $this->buffer = array();
+ $result = qdb("SELECT `key`, `value` FROM `PREFIX_settings_kvstorage` WHERE 1");
+ while($sqlrow = mysql_fetch_assoc($result))
+ $this->buffer[$sqlrow["key"]] = unserialize(base64_decode($sqlrow["value"]));
+
+ $this->to_be_created = array();
+ $this->to_be_deleted = array();
+ $this->to_be_updated = array();
+ }
+
+ public function save()
+ {
+ foreach($this->to_be_deleted as $k)
+ qdb("DELETE FROM `PREFIX_settings_kvstorage` WHERE `key` = '%s'", $k);
+ foreach($this->to_be_updated as $k)
+ qdb("UPDATE `PREFIX_settings_kvstorage` SET `value` = '%s' WHERE `key` = '%s'", base64_encode(serialize($this->buffer[$k])), $k);
+ foreach($this->to_be_created as $k)
+ qdb("INSERT INTO `PREFIX_settings_kvstorage` (`key`, `value`) VALUES ('%s', '%s')", $k, base64_encode(serialize($this->buffer[$k])));
+ $this->to_be_created = array();
+ $this->to_be_deleted = array();
+ $this->to_be_updated = array();
+ }
+
+ /* ArrayAccess implementation */
+ public function offsetExists($offset)
+ {
+ return isset($this->buffer[$offset]);
+ }
+ public function offsetGet($offset)
+ {
+ return $this->buffer[$offset];
+ }
+ public function offsetSet ($offset, $value)
+ {
+ if(!$this->offsetExists($offset))
+ {
+ if(in_array($offset, $this->to_be_deleted))
+ {
+ $this->to_be_updated[] = $offset;
+ unset($this->to_be_deleted[array_search($offset, $this->to_be_deleted)]);
+ }
+ else
+ $this->to_be_created[] = $offset;
+ }
+ elseif((!in_array($offset, $this->to_be_created)) and (!in_array($offset, $this->to_be_updated)))
+ $this->to_be_updated[] = $offset;
+ $this->buffer[$offset] = $value;
+ }
+ public function offsetUnset($offset)
+ {
+ if(in_array($offset, $this->to_be_created))
+ unset($this->to_be_created[array_search($offset, $this->to_be_created)]);
+ else
+ $this->to_be_deleted[] = $offset;
+ unset($this->buffer[$offset]);
+ }
+
+ /* IteratorAggregate implementation */
+ public function getIterator() { return new SettingsIterator($this, array_keys($this->buffer)); }
+
+ /* Countable implementation */
+ public function count() { return count($this->buffer); }
+}
+
+$settings = Settings::get_instance();
+
+/* users */
+class User extends BySQLRowEnabled
+{
+ private $name;
+ private $id;
+
+ public $pwhash;
+ public $isadmin;
+
+ protected function __construct() {}
+
+ protected function populate_by_sqlrow($sqlrow)
+ {
+ $this->id = $sqlrow["id"];
+ $this->name = $sqlrow["name"];
+ $this->pwhash = $sqlrow["pwhash"];
+ $this->isadmin = $sqlrow["isadmin"] == 1;
+ }
+
+ function get_id() { return $this->id; }
+ function get_name() { return $this->name; }
+
+ public static function create($name)
+ {
+ try
+ {
+ self::by_name($name);
+ throw new AlreadyExistsError();
+ }
+ catch(DoesNotExistError $e)
+ {
+ $obj = new self;
+ $obj->name = $name;
+ $obj->pwhash = "";
+ $obj->isadmin = False;
+ qdb("INSERT INTO `PREFIX_users` (`name`, `pwhash`, `isadmin`) VALUES ('%s', '', 0)", $name);
+ $obj->id = mysql_insert_id();
+ return $obj;
+ }
+ }
+
+ public static function by_id($id)
+ {
+ $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE `id` = %d", $id);
+ $sqlrow = mysql_fetch_assoc($result);
+ if($sqlrow === False)
+ throw new DoesNotExistError();
+ return self::by_sqlrow($sqlrow);
+ }
+
+ public static function by_name($name)
+ {
+ $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE `name` = '%s'", $name);
+ $sqlrow = mysql_fetch_assoc($result);
+ if($sqlrow === False)
+ throw new DoesNotExistError();
+ return self::by_sqlrow($sqlrow);
+ }
+
+ public static function all()
+ {
+ $rv = array();
+ $result = qdb("SELECT `id`, `name`, `pwhash`, `isadmin` FROM `PREFIX_users` WHERE 1");
+ while($sqlrow = mysql_fetch_assoc($result))
+ $rv[] = self::by_sqlrow($sqlrow);
+ return $rv;
+ }
+
+ public function get_packages()
+ {
+ $rv = array();
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `user` = %d", $this->id);
+ while($sqlrow = mysql_fetch_assoc($result))
+ $rv[] = Package::by_sqlrow($result);
+ return $rv;
+ }
+
+ public function save()
+ {
+ qdb("UPDATE `PREFIX_users` SET `isadmin` = %d, `pwhash` = '%s' WHERE `id` = %d", ($this->isadmin ? 1 : 0), $this->pwhash, $this->id);
+ }
+
+ public function delete()
+ {
+ qdb("DELETE FROM `PREFIX_users` WHERE `id` = %d", $this->id);
+ }
+}
+
+class Package extends BySQLRowEnabled
+{
+ private $id;
+ private $name;
+ private $user;
+
+ public $lastversion;
+ public $description;
+ public $lastupdate;
+ public $txtversion;
+
+ public function get_id() { return $id; }
+ public function get_name() { return $name; }
+ public function get_user() { return $user; }
+
+ protected function __construct() {}
+
+ protected function populate_by_sqlrow($sqlrow)
+ {
+ $this->id = $sqlrow["id"];
+ $this->name = $sqlrow["name"];
+ $this->user = User::by_id($sqlrow["user"]);
+ $this->lastversion = $sqlrow["lastversion"];
+ $this->description = $sqlrow["description"];
+ $this->lastupdate = $sqlrow["lastupdate"];
+ $this->txtversion = $sqlrow["txtversion"];
+ }
+
+ public static function create($name, $user)
+ {
+ if(preg_match("/^[0-9a-zA-Z_\\-]+$/", $name) != 1)
+ throw new InvalidArgumentException("Invalid package name (must be min 1 char, 0-9A-Za-z_-");
+ try
+ {
+ self::by_name($name);
+ throw new AlreadyExistsError();
+ }
+ catch(DoesNotExistError $e)
+ {
+ $obj = new self;
+ $obj->name = $name;
+ $obj->user = $user;
+ $obj->lastupdate = time();
+ $obj->lastversion = 0;
+ $obj->txtversion = "";
+ $obj->description = "";
+
+ qdb("INSERT INTO `PREFIX_packages` (`name`, `user`, `lastupdate`, `lastversion`, `txtversion`, description`, `description`) VALUES ('%s', %d, UNIX_TIMESTAMP(), 0, '', '')", $name, $user->get_id());
+ $obj->id = mysql_insert_id();
+
+ mkdir(dirname(__FILE__) . "/../packages/" . $this->name);
+
+ return $obj;
+ }
+ }
+
+ public static function by_id($id)
+ {
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `id` = %d", $id);
+ $sqlrow = mysql_fetch_assoc($result);
+ if($sqlrow === False)
+ throw new DoesNotExistError();
+ return self::by_sqlrow($sqlrow);
+ }
+
+ public static function by_name($name)
+ {
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `name` = '%s'", $name);
+ $sqlrow = mysql_fetch_assoc($result);
+ if($sqlrow === False)
+ throw new DoesNotExistError();
+ return self::by_sqlrow($sqlrow);
+ }
+
+ public static function update_lists()
+ {
+ $packagelist = array();
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1");
+ while($sqlrow = mysql_fetch_assoc($result))
+ $packagelist[] = array($sqlrow["name"], $sqlrow["lastversion"], $sqlrow["description"]);
+ file_put_contents(dirname(__FILE__) . "/../packagelist", serialize($packagelist));
+ }
+
+ public static function all()
+ {
+ $rv = array();
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1");
+ while($sqlrow = mysql_fetch_assoc($result))
+ $rv[] = self::by_sqlrow($sqlrow);
+ return $rv;
+ }
+
+ public static function latest()
+ {
+ $rv = array();
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE 1 ORDER BY `lastupdate` DESC LIMIT 0,15");
+ while($sqlrow = mysql_fetch_assoc($result))
+ $rv[] = self::by_sqlrow($sqlrow);
+ return $rv;
+ }
+
+ public static function search($search)
+ {
+ $rv = array();
+ $result = qdb("SELECT `id`, `name`, `user`, `lastversion`, `description`, `lastupdate`, `txtversion` FROM `PREFIX_packages` WHERE `name` LIKE '%%%s%%' OR `description` LIKE '%%%s%%'", $search, $search);
+ while($sqlrow = mysql_fetch_assoc($result))
+ $rv[] = self::by_sqlrow($sqlrow);
+ return $rv;
+ }
+
+ public function newversion($pgk)
+ {
+ global $settings;
+ if($pkg->name != $this->name)
+ throw new NotAllowedError("Package name not equal.");
+ if($pkg->versioncount <= $this->lastversion)
+ throw new NotAllowedError("Older or same version.");
+ $pkg->updatepath = $settings["root_url"] . "/packages/" . urlencode($this->name) . "/update";
+
+ $pkg_ser = $pkg->save();
+ file_put_contents(dirname(__FILE__) . "/../packages/" . urlencode($this>name) . "/versions/" . $pkg->versioncount, $pkg_ser);
+ file_put_contents(dirname(__FILE__) . "/../packages/" . urlencode($this>name) . "/versions/current", $pkg_ser);
+ $meta = $pkg->extract_meta();
+ file_put_contents(dirname(__FILE__) . "/../packages/" . urlencode($this>name) . "/meta", serialize($meta));
+
+ $this->lastversion = $pkg->versioncount;
+ $this->txtversion = $pkg->versiontext;
+ $this->description = $pkg->short_description;
+ $this->lastupdate = time();
+ $this->save();
+
+ $update_info = array(
+ "current-version" => $this->lastversion,
+ "dl-path" => $settings["root_url"] . "/packages/" . urlencode($this->name) . "/versions/" . $this->lastversion
+ );
+
+ file_put_contents(dirname(__FILE__) . "/../packages/" . urlencode($this>name) . "/update", serialize($update_info));
+
+ self::update_lists();
+ }
+
+ public function save()
+ {
+ qdb("UPDATE `PREFIX_packages` SET `lastversion` = %d, `lastupdate` = %d, `txtversion` = '%s', `description` = '%s' WHERE `id` = %d", $this->lastversion, $this->lastupdate, $this->txtversion, $this->description, $this->id);
+ }
+
+ public function delete()
+ {
+ qdb("DELETE FROM `PREFIX_packages` WHERE `id` = %d", $this->id);
+ delete_directory(dirname(__FILE__) . "/../packages/" . $this->name);
+ self::update_lists();
+ }
+}
+
+function update_repometa()
+{
+ global $settings;
+ file_put_contents(dirname(__FILE__) . "/../repometa", serialize(array(
+ "name" => $settings["repo_name"],
+ "description" => $settings["repo_description"]
+ )));
+}
+
+?>
diff --git a/r7r_repo/pluginpackage.php b/r7r_repo/pluginpackage.php
new file mode 100644
index 0000000..d8ce170
--- /dev/null
+++ b/r7r_repo/pluginpackage.php
@@ -0,0 +1,288 @@
+<?php
+
+/*
+ * File: ratatoeskr/sys/pluginpackage.php
+ * Handle plugin packages easily.
+ *
+ * License:
+ * This file is part of Ratatöskr.
+ * Unlike the other parts of Ratatöskr, *this* file ist *not* licensed under the
+ * MIT / X11 License, but under the WTFPL, to make it even easier to use this
+ * file in other projects.
+ * See "ratatoeskr/licenses/wtfpl" for more information.
+ */
+
+/*
+ * Function: dir2array
+ * Pack a directory into an array.
+ *
+ * Parameters:
+ * $dir - The directory to pack.
+ *
+ * Returns:
+ * Associative array. Keys are filenames, values are either the file's content as a string or another array, if it's a directory.
+ */
+function dir2array($dir)
+{
+ $rv = array();
+ foreach(scandir($dir) as $fn)
+ {
+ if(($fn == ".") or ($fn == ".."))
+ continue;
+ $fn_new = $dir . "/" . $fn;
+ if(is_dir($fn_new))
+ $rv[$fn] = dir2array($fn_new);
+ elseif(is_file($fn_new))
+ $rv[$fn] = file_get_contents($fn_new);
+ }
+ return $rv;
+}
+
+/*
+ * Function: array2dir
+ * Unpack an array into a directory.
+ *
+ * Parameters:
+ * $a - Array to unpack.
+ * $dir - Directory to unpack to.
+ */
+function array2dir($a, $dir)
+{
+ if(!is_dir($dir))
+ mkdir($dir);
+
+ foreach($a as $k => $v)
+ {
+ $k = "$dir/$k";
+ if(is_array($v))
+ array2dir($v, $k);
+ else
+ file_put_contents($k, $v);
+ }
+}
+
+/*
+ * Class: InvalidPackage
+ * An Exception that <PluginPackage>'s function can throw, if the package is invalid.
+ */
+class InvalidPackage extends Exception {}
+
+/*
+ * Class: PluginPackage
+ * A plugin package representation.
+ */
+class PluginPackage
+{
+ public static $magic = "R7RPLGPACKV001";
+
+ /*
+ * Variables: Mandatory values
+ *
+ * $code - The plugin code
+ * $classname - The name of the plugins main class
+ * $name - Name of the plugin (must be at least one character, allowed chars: a-z A-Z 0-9 - _)
+ * $author - The author of the plugin (preferably in the format: Name<mail@address>)
+ * $versiontext - A text to describe the current version, something like "1.1 Beta"
+ * $versioncount - A number for this version, should be increased with every release
+ * $api - The used API version
+ * $short_description - A short description.
+ */
+ public $code = NULL;
+ public $classname = NULL;
+ public $name = NULL;
+ public $author = NULL;
+ public $versiontext = NULL;
+ public $versioncount = NULL;
+ public $api = NULL;
+ public $short_description = NULL;
+
+ /*
+ * Variables: Optional values
+ *
+ * $updatepath - A URL that points to a update information resource (serialize'd array("current-version" => VERSIONCOUNT, "dl-path" => DOWNLOAD PATH); will get overwritten/set by the default repository software.
+ * $web - A URL to the webpage for the plugin. If left empty, the default repository software will set this to the description page of your plugin.
+ * $license - The license text of your plugin.
+ * $help - A help / manual (formatted in HTML) for your plugin.
+ * $custompub - <dir2array> 'd directory that contains custom public(i.e. can later be accessed from the web) data.
+ * $custompriv - <dir2array> 'd directory that contains custom private data.
+ * $tpls - <dir2array> 'd directory containing custom STE templates.
+ */
+ public $updatepath = NULL;
+ public $web = NULL;
+ public $license = NULL;
+ public $help = NULL;
+ public $custompub = NULL;
+ public $custompriv = NULL;
+ public $tpls = NULL;
+
+ /*
+ * Function: validate
+ * Validate, if the variables are set correctly.
+ * Will throw an <InvalidPackage> exception if invalid.
+ */
+ public function validate()
+ {
+ function validate_url ($u) { return preg_match("/^http[s]{0,1}:\\/\\/.*$/", $u) != 0; }
+ function validate_arraydir($a)
+ {
+ if(!is_array($a))
+ return False;
+ foreach($a as $k=>$v)
+ {
+ if(!is_string($k))
+ return False;
+ if(is_array($v) and (!validate_arraydir($v)))
+ return False;
+ elseif(!is_string($v))
+ return False;
+ }
+ return True;
+ }
+
+ if(!is_string($this->code))
+ throw new InvalidPackage("Invalid code value.");
+ if(!is_string($this->classname))
+ throw new InvalidPackage("Invalid classname value.");
+ if(preg_match("/^[a-zA-Z0-9_\\-]+$/", $this->name) == 0)
+ throw new InvalidPackage("Invalid name value (must be at least 1 character, accepted chars: a-z A-Z 0-9 - _).");
+ if(!is_string($this->author))
+ throw new InvalidPackage("Invalid author value.");
+ if(!is_string($this->versiontext))
+ throw new InvalidPackage("Invalid versiontext value.");
+ if(!is_numeric($this->versioncount))
+ throw new InvalidPackage("Invalid versioncount value. Must be a number.");
+ if(!is_numeric($this->api))
+ throw new InvalidPackage("Invalid api value. Must be a number.");
+ if(!is_string($this->short_description))
+ throw new InvalidPackage("Invalid short_description value.");
+
+ if(($this->updatepath !== NULL) and (!validate_url($this->updatepath)))
+ throw new InvalidPackage("Invalid updatepath value. Must be an URL.");
+ if(($this->web !== NULL) and (!validate_url($this->web)))
+ throw new InvalidPackage("Invalid web value. Must be an URL.");
+ if(($this->license !== NULL) and (!is_string($this->license)))
+ throw new InvalidPackage("Invalid license value.");
+ if(($this->help !== NULL) and (!is_string($this->help)))
+ throw new InvalidPackage("Invalid help value.");
+ if(($this->custompub !== NULL) and (!validate_arraydir($this->custompub)))
+ throw new InvalidPackage("Invalid custompub value.");
+ if(($this->custompriv !== NULL) and (!validate_arraydir($this->custompriv)))
+ throw new InvalidPackage("Invalid custompriv value.");
+ if(($this->tpls !== NULL) and (!validate_arraydir($this->tpls)))
+ throw new InvalidPackage("Invalid tpls value.");
+ return True;
+ }
+
+ /*
+ * Function: load
+ * Load a plugin package from binary data.
+ *
+ * Parameters:
+ * $plugin_raw - The raw package to load.
+ *
+ * Returns:
+ * The <PluginPackage> object.
+ *
+ * Throws:
+ * <InvalidPackage> if package is invalid.
+ */
+ public static function load($plugin_raw)
+ {
+ /* Read and compare magic number */
+ $magic = substr($plugin_raw, 0, strlen(self::$magic));
+ if($magic != self::$magic)
+ throw new InvalidPackage("Wrong magic number");
+
+ /* Read sha1sum and uncompress serialized plugin, then compare the hash */
+ $sha1sum = substr($plugin_raw, strlen(self::$magic), 20);
+ $pluginser = gzuncompress(substr($plugin_raw, strlen(self::$magic) + 20));
+ if(sha1($pluginser, True) != $sha1sum)
+ throw new InvalidPackage("Wrong SHA1 hash");
+
+ $plugin = @unserialize($pluginser);
+ if(!($plugin instanceof self))
+ throw new InvalidPackage("Not the correct class or not unserializeable.");
+
+ $plugin->validate();
+
+ return $plugin;
+ }
+
+ /*
+ * Function: save
+ * Save the plugin.
+ *
+ * Returns:
+ * A binary plugin package.
+ *
+ * Throws:
+ * <InvalidPackage> if package is invalid.
+ */
+ public function save()
+ {
+ $this->validate();
+ $ser = serialize($this);
+ return self::$magic . sha1($ser, True) . gzcompress($ser, 9);
+ }
+
+ /*
+ * Function: extract_meta
+ * Get just the metadata of this package.
+ *
+ * Returns:
+ * A <PluginPackageMeta> object.
+ */
+ public function extract_meta()
+ {
+ $meta = new PluginPackageMeta();
+
+ $meta->name = $this>name;
+ $meta->author = $this>author;
+ $meta->versiontext = $this>versiontext;
+ $meta->versioncount = $this>versioncount;
+ $meta->api = $this>api;
+ $meta->short_description = $this>short_description;
+ $meta->updatepath = $this>updatepath;
+ $meta->web = $this>web;
+ $meta->license = $this>license;
+
+ return $meta;
+ }
+}
+
+/*
+ * Class: PluginPackageMeta
+ * Only the metadata of a <PluginPackage>.
+ */
+class PluginPackageMeta
+{
+ /*
+ * Variables: Mandatory values
+ *
+ * $name - Name of the plugin (must be at least one character, allowed chars: a-z A-Z 0-9 - _)
+ * $author - The author of the plugin (preferably in the format: Name<mail@address>)
+ * $versiontext - A text to describe the current version, something like "1.1 Beta"
+ * $versioncount - A number for this version, should be increased with every release
+ * $api - The used API version
+ * $short_description - A short description.
+ */
+ public $name = NULL;
+ public $author = NULL;
+ public $versiontext = NULL;
+ public $versioncount = NULL;
+ public $api = NULL;
+ public $short_description = NULL;
+
+ /*
+ * Variables: Optional values
+ *
+ * $updatepath - A URL that points to a update information resource (serialize'd array("current-version" => VERSIONCOUNT, "dl-path" => DOWNLOAD PATH); will get overwritten/set by the default repository software.
+ * $web - A URL to the webpage for the plugin. If left empty, the default repository software will set this to the description page of your plugin.
+ * $license - The license text of your plugin.
+ */
+ public $updatepath = NULL;
+ public $web = NULL;
+ public $license = NULL;
+}
+
+?>
diff --git a/r7r_repo/pwhash.php b/r7r_repo/pwhash.php
new file mode 100644
index 0000000..8ec4762
--- /dev/null
+++ b/r7r_repo/pwhash.php
@@ -0,0 +1,74 @@
+<?php
+/*
+ * File: ratatoeskr/sys/pwhash.php
+ *
+ * Hashing passwords
+ *
+ * License:
+ * This file is part of Ratatöskr.
+ * Ratatöskr is licensed unter the MIT / X11 License.
+ * See "ratatoeskr/licenses/ratatoeskr" for more information.
+ */
+
+/*
+ * Class: PasswordHash
+ * Contains static functions for password hashes.
+ * Is just used as a namespace, can not be created.
+ *
+ * It should be fairly difficult to break these salted hashes via bruteforce attacks.
+ */
+class PasswordHash
+{
+ private function __construct() {} /* Prevent construction */
+
+ private static $saltlen_min = 20;
+ private static $saltlen_max = 30;
+ private static $iterations_min = 200;
+ private static $iterations_max = 1000;
+
+ private static function hash($data, $salt, $iterations)
+ {
+ $hash = $data . $salt;
+ for($i = $iterations ;$i--;)
+ $hash = sha1($data . $hash . $salt, (bool) $i);
+ return $iterations . '$' . bin2hex($salt) . '$' . $hash;
+ }
+
+ /*
+ * Function: create
+ * Create a password hash string.
+ *
+ * Parameters:
+ * $password - The password (or other data) to hash.
+ *
+ * Returns:
+ * The salted hash as a string.
+ */
+ public static function create($password)
+ {
+ $salt = "";
+ $saltlen = mt_rand(self::$saltlen_min, self::$saltlen_max);
+ for($i = 0; $i < $saltlen; $i++)
+ $salt .= chr(mt_rand(0,255));
+ return self::hash($password, $salt, mt_rand(self::$iterations_min, self::$iterations_max));
+ }
+
+ /*
+ * Function: validate
+ * Validate a salted hash.
+ *
+ * Parameters:
+ * $password - The password to test.
+ * $pwhash - The hash to test against.
+ *
+ * Returns:
+ * True, if $password was correct, False otherwise.
+ */
+ public static function validate($password, $pwhash)
+ {
+ list($iterations, $hexsalt, $hash) = explode('$', $pwhash);
+ return self::hash($password, pack("H*", $hexsalt), $iterations) == $pwhash;
+ }
+}
+
+?>
diff --git a/r7r_repo/stupid_template_engine.php b/r7r_repo/stupid_template_engine.php
new file mode 100644
index 0000000..0edb880
--- /dev/null
+++ b/r7r_repo/stupid_template_engine.php
@@ -0,0 +1,1297 @@
+<?php
+
+/*
+ * File: stupid_template_engine.php
+ * The implementation of the Stupid Template Engine.
+ *
+ * About: License
+ * This file is licensed under the MIT/X11 License.
+ * See COPYING for more details.
+ */
+
+/*
+ * Namespace: ste
+ * Everything in this file is in this namespace.
+ */
+namespace ste;
+
+abstract class ASTNode
+{
+ public $tpl;
+ public $offset;
+ public function __construct($tpl, $off)
+ {
+ $this->tpl = $tpl;
+ $this->offset = $off;
+ }
+}
+
+class TextNode extends ASTNode
+{
+ public $text;
+}
+
+class TagNode extends ASTNode
+{
+ public $name;
+ public $params;
+ public $sub;
+}
+
+class VariableNode extends ASTNode
+{
+ public $name;
+ public $arrayfields;
+ public function transcompile()
+ {
+ $varaccess = '@$ste->vars[' . (is_numeric($this->name) ? $this->name : '\'' . escape_text($this->name) . '\''). ']';
+ foreach($this->arrayfields as $af)
+ {
+ if((count($af) == 1) and ($af[0] instanceof TextNode) and is_numeric($af->text))
+ $varaccess .= '[' . $af->text . ']';
+ else
+ $varaccess .= '[' . implode(".",
+ array_map(
+ function($node)
+ {
+ if($node instanceof TextNode)
+ return "\"" . escape_text($node->text) . "\"";
+ else if($node instanceof VariableNode)
+ return $node->transcompile();
+ }, $af
+ )
+ ). ']';
+ }
+ return $varaccess;
+ }
+}
+
+class ParseCompileError extends \Exception
+{
+ public $msg;
+ public $tpl;
+ public $off;
+
+ public function __construct($msg, $tpl, $offset, $code = 0, $previous = NULL)
+ {
+ $this->msg = $msg;
+ $this->tpl = $tpl;
+ $this->off = $offset;
+ $this->message = "$msg (Template $tpl, Offset $offset)";
+ }
+
+ public function rewrite($code)
+ {
+ $line = substr_count(str_replace("\r\n", "\n", $code), "\n", 0, $this->off + 1) + 1;
+ $this->message = "{$this->msg} (Template $tpl, Line $line)";
+ $this->is_rewritten = True;
+ }
+}
+
+/* $text must start after the first opening bracket */
+function find_closing_bracket($text, $opening, $closing)
+{
+ $counter = 1;
+ $len = strlen($text);
+ for($i = 0; $i < $len; ++$i)
+ {
+ switch($text[$i])
+ {
+ case $opening:
+ ++$counter;
+ break;
+ case $closing:
+ --$counter;
+ break;
+ }
+ if($counter == 0)
+ break;
+ }
+
+ if($counter > 0)
+ throw new \Exception("Missing closing \"$closing\". Stop.");
+
+ return $i;
+}
+
+function instance_in_array($classname, $a)
+{
+ foreach($a as $v)
+ {
+ if($v instanceof $classname)
+ return True;
+ }
+ return False;
+}
+
+function unescape_text($text)
+{
+ return stripcslashes($text);
+}
+
+function tokenize_text($text, $tpl, $off)
+{
+ $tokens = array();
+ /* Find next non-escaped $-char */
+ if(preg_match("/(?:(?<!\\\\)\\$)/s", $text, $match, PREG_OFFSET_CAPTURE) == 0)
+ {
+ $node = new TextNode($tpl, $off);
+ $node->text = preg_replace("/^\\n\\s*/s", "", unescape_text($text));
+ return (strlen($node->text) == 0) ? array() : array($node);
+ }
+
+ if($match[0][1] > 0)
+ {
+ $node = new TextNode($tpl, $off);
+ $node->text = unescape_text(substr($text, 0, $match[0][1]));
+ $tokens[] = $node;
+ }
+
+ if($text[$match[0][1] + 1] == "{")
+ {
+ try
+ {
+ $varend = find_closing_bracket(substr($text, $match[0][1] + 2), "{", "}") + $match[0][1] + 2;
+ }
+ catch(\Exception $e)
+ {
+ throw new ParseCompileError("Parse Error: Missing closing '}'", $tpl, $off + $match[0][1] + 1);
+ }
+ return array_merge(
+ $tokens,
+ tokenize_text("\$" . substr($text, $match[0][1] + 2, ($varend - 1) - ($match[0][1] + 1)), $tpl, $off + $match[0][1] + 2),
+ tokenize_text(substr($text, $varend + 1), $tpl, $off + $varend + 1)
+ );
+ }
+
+ $text = substr($text, $match[0][1] + 1);
+ $off += $match[0][1] + 1;
+ if(preg_match("/^[a-zA-Z0-9_]+/s", $text, $match, PREG_OFFSET_CAPTURE) == 0)
+ {
+ $nexttokens = tokenize_text($text, $tpl, $off);
+ if($nexttokens[0] instanceof TextNode)
+ $nexttokens[0]->text = "\$" . $nexttokens[0]->text;
+ else
+ {
+ $node = new TextNode($tpl, $off);
+ $node->text = "\$";
+ $tokens[] = $node;
+ }
+ return array_merge($tokens, $nexttokens);
+ }
+
+ $node = new VariableNode($tpl, $off + $match[0][1]);
+ $node->name = $match[0][0];
+ $node->arrayfields = array();
+
+ $text = substr($text, $match[0][1] + strlen($match[0][0]));
+ $off += $match[0][1] + strlen($match[0][0]);
+ while(@$text[0] == "[")
+ {
+ $text = substr($text, 1);
+ $off += 1;
+ try
+ {
+ $fieldend = find_closing_bracket($text, "[", "]");
+ }
+ catch(\Exception $e)
+ {
+ throw new ParseCompileError("Parse Error: Missing closing ']'", $tpl, $off - 1);
+ }
+ $node->arrayfields[] = tokenize_text(substr($text, 0, $fieldend), $tpl, $off);
+ $text = substr($text, $fieldend + 1);
+ $off += $fieldend + 1;
+ }
+
+ $tokens[] = $node;
+
+ return strlen($text) > 0 ? array_merge($tokens, tokenize_text($text, $tpl, $off)) : $tokens;
+}
+
+function mk_ast($code, $tpl, $err_off)
+{
+ $ast = array();
+
+ if(preg_match("/\\<\\s*ste:([a-zA-Z0-9_]*)/s", $code, $matches, PREG_OFFSET_CAPTURE) == 0)
+ return tokenize_text($code, $tpl, $err_off);
+
+ $ast = tokenize_text(substr($code, 0, $matches[0][1]), $tpl, $err_off);
+ $tag = new TagNode($tpl, $err_off + $matches[0][1]);
+ $tag->name = $matches[1][0];
+
+ $code = substr($code, $matches[0][1] + strlen($matches[0][0]));
+ $err_off += $matches[0][1] + strlen($matches[0][0]);
+
+ $tag->params = array();
+
+ while(preg_match("/^\\s+([a-zA-Z0-9_]+)=((?:\"(?:.*?)(?<!\\\\)\")|(?:'(?:.*?)(?<!\\\\)'))/s", $code, $matches, PREG_OFFSET_CAPTURE) > 0)
+ {
+ $paramval = substr($code, $matches[2][1] + 1, strlen($matches[2][0]) - 2);
+ $paramval = str_replace("\\\"", "\"", $paramval);
+ $paramval = str_replace("\\'", "'", $paramval);
+ $tag->params[$matches[1][0]] = tokenize_text($paramval, $tpl, $err_off + $matches[2][1] + 1);
+ $code = substr($code, strlen($matches[0][0]));
+ $err_off += strlen($matches[0][0]);
+ }
+
+ if(preg_match("/^\\s*([\\/]?)\\s*\\>/s", $code, $matches) == 0)
+ throw new ParseCompileError("Parse Error: Missing closing '>' in \"" . $tag->name . "\"-Tag.", $tpl, $tag->offset);
+
+ $code = substr($code, strlen($matches[0]));
+ $err_off += strlen($matches[0]);
+
+ $tag->sub = array();
+
+ if($matches[1][0] != "/")
+ {
+ $off = 0;
+ $last_tag_start = 0;
+ $tagstack = array(array($tag->name, $tag->offset));
+ while(preg_match("/\\<((?:\\s*)|(?:\\s*\\/\\s*))ste:([a-zA-Z0-9_]*)(?:\\s+(?:[a-zA-Z0-9_]+)=(?:(?:\"(?:.*?)(?<!\\\\)\")|(?:'(?:.*?)(?<!\\\\)')))*((?:\\s*)|(?:\\s*\\/\\s*))\\>/s", $code, $matches, PREG_OFFSET_CAPTURE, $off) > 0) /* RegEx from hell! Matches all <ste:> Tags. Opening, closing and self-closing ones. */
+ {
+ if(trim($matches[3][0]) != "/")
+ {
+ $closingtag = trim($matches[1][0]);
+ if($closingtag[0] == "/")
+ {
+ list($matching_opentag, $mo_off) = array_pop($tagstack);
+ if($matching_opentag != $matches[2][0])
+ throw new ParseCompileError("Parse Error: Missing closing \"ste:$matching_opentag\"-Tag.", $tpl, $mo_off + $err_off);
+ }
+ else
+ $tagstack[] = array($matches[2][0], $matches[0][1]);
+ }
+ $last_tag_start = $matches[0][1];
+ $off = $last_tag_start + strlen($matches[0][0]);
+ if(empty($tagstack))
+ break;
+ }
+
+ if((!empty($tagstack)) or ($tag->name != $matches[2][0]))
+ throw new ParseCompileError("Parse Error: Missing closing \"ste:" . $tag->name . "\"-Tag.", $tpl, $tag->offset);
+
+ if($tag->name == "rawtext")
+ {
+ $tag = new TextNode($tpl, $err_off);
+ $tag->text = substr($code, 0, $last_tag_start);
+ }
+ else if($tag->name == "comment")
+ $tag = NULL; /* All this work to remove a comment ... */
+ else
+ $tag->sub = mk_ast(substr($code, 0, $last_tag_start), $tpl, $err_off);
+ $code = substr($code, $off);
+ $err_off += $off;
+ }
+
+ if($tag !== NULL)
+ $ast[] = $tag;
+ return array_merge($ast, strlen($code) > 0 ? mk_ast($code, $tpl, $err_off) : array());
+}
+
+/*
+ * Function: precompile
+ * Precompiling STE T/PL templates.
+ * You only need this function, if you want to manually transcompile a template.
+ *
+ * Parameters:
+ * $code - The input code
+ *
+ * Returns:
+ * The precompiled code.
+ */
+function precompile($code)
+{
+ $code = preg_replace( /* Transform short form of comparison (~{a|op|b}) to long form */
+ "/(?:(?<!\\\\)~)(?:(?<!\\\\)\\{)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\})/s",
+ "<ste:cmp text_a=\"\$1\" op=\"\$2\" text_b=\"\$3\" />",
+ $code
+ );
+ $code = preg_replace( /* Transform short form of if-clause (?{cond|then|else}) to long form */
+ "/(?:(?<!\\\\)\\?)(?:(?<!\\\\)\\{)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\|)(.*?)(?:(?<!\\\\)\\})/s",
+ "<ste:if>\$1<ste:then>\$2</ste:then><ste:else>\$3</ste:else></ste:if>",
+ $code
+ );
+ /* Unescape \? \~ \{ \} \| */
+ $code = preg_replace("/(?:(?<!\\\\)\\\\\\?)/s", "?", $code);
+ $code = preg_replace("/(?:(?<!\\\\)\\\\~)/s", "~", $code);
+ $code = preg_replace("/(?:(?<!\\\\)\\\\\\{)/s", "{", $code);
+ $code = preg_replace("/(?:(?<!\\\\)\\\\\\})/s", "}", $code);
+ $code = preg_replace("/(?:(?<!\\\\)\\\\\\|)/s", "|", $code);
+
+ return $code;
+}
+
+/*
+ * Function: parse
+ * Parsing a STE T/PL template.
+ * You only need this function, if you want to manually transcompile a template.
+ *
+ * Parameters:
+ * $code - The STE T/PL code.
+ * $tpl - The name of the template.
+ *
+ * Returns:
+ * An abstract syntax tree, which can be used with <transcompile>.
+ */
+function parse($code, $tpl)
+{
+ return mk_ast($code, $tpl, 0);
+}
+
+function indent_code($code)
+{
+ return implode(
+ "\n",
+ array_map(
+ function($line) { return "\t$line"; },
+ explode("\n", $code)
+ )
+ );
+}
+
+/* We could also just eval() the $infix_math code, but this is much cooler :-D (Parser inception) */
+function shunting_yard($infix_math)
+{
+ $operators = array(
+ "+" => array("l", 2),
+ "-" => array("l", 2),
+ "*" => array("l", 3),
+ "/" => array("l", 3),
+ "^" => array("r", 4),
+ "_" => array("r", 5),
+ "(" => array("", 0),
+ ")" => array("", 0)
+ );
+
+ preg_match_all("/\s*(?:(?:[+\\-\\*\\/\\^\\(\\)])|(\\d*[\\.]?\\d*))\\s*/s", $infix_math, $tokens, PREG_PATTERN_ORDER);
+ $tokens_raw = array_filter(array_map('trim', $tokens[0]), function($x) { return ($x === "0") or (!empty($x)); });
+ $output_queue = array();
+ $op_stack = array();
+
+ $lastpriority = NULL;
+ /* Make - unary, if neccessary */
+ $tokens = array();
+ foreach($tokens_raw as $token)
+ {
+ $priority = isset($operators[$token]) ? $operators[$token][1] : -1;
+ if(($token == "-") and (($lastpriority === NULL) or ($lastpriority >= 0)))
+ {
+ $priority = $operators["_"][1];
+ $tokens[] = "_";
+ }
+ else
+ $tokens[] = $token;
+ $lastpriority = $priority;
+ }
+
+ while(!empty($tokens))
+ {
+ $token = array_shift($tokens);
+ if(is_numeric($token))
+ $output_queue[] = $token;
+ else if($token == "(")
+ $op_stack[] = $token;
+ else if($token == ")")
+ {
+ $lbr_found = False;
+ while(!empty($op_stack))
+ {
+ $op = array_pop($op_stack);
+ if($op == "(")
+ {
+ $lbr_found = True;
+ break;
+ }
+ $output_queue[] = $op;
+ }
+ if(!$lbr_found)
+ throw new \Exception("Bracket mismatch.");
+ }
+ else if(!isset($operators[$token]))
+ throw new \Exception("Invalid token ($token): Not a number, bracket or operator. Stop.");
+ else
+ {
+ $priority = $operators[$token][1];
+ if($operators[$token][0] == "l")
+ while((!empty($op_stack)) and ($priority <= $operators[$op_stack[count($op_stack)-1]][1]))
+ $output_queue[] = array_pop($op_stack);
+ else
+ while((!empty($op_stack)) and ($priority < $operators[$op_stack[count($op_stack)-1]][1]))
+ $output_queue[] = array_pop($op_stack);
+ $op_stack[] = $token;
+ }
+ }
+
+ while(!empty($op_stack))
+ {
+ $op = array_pop($op_stack);
+ if($op == "(")
+ throw new \Exception("Bracket mismatch...");
+ $output_queue[] = $op;
+ }
+
+ return $output_queue;
+}
+
+function pop2(&$array)
+{
+ $rv = array(array_pop($array), array_pop($array));
+ if(array_search(NULL, $rv, True) !== False)
+ throw new \Exception("Not enough numbers on stack. Invalid formula.");
+ return $rv;
+}
+
+function calc_rpn($rpn)
+{
+ $stack = array();
+ foreach($rpn as $token)
+ {
+ switch($token)
+ {
+ case "+":
+ list($b, $a) = pop2($stack);
+ $stack[] = $a + $b;
+ break;
+ case "-":
+ list($b, $a) = pop2($stack);
+ $stack[] = $a - $b;
+ break;
+ case "*":
+ list($b, $a) = pop2($stack);
+ $stack[] = $a * $b;
+ break;
+ case "/":
+ list($b, $a) = pop2($stack);
+ $stack[] = $a / $b;
+ break;
+ case "^":
+ list($b, $a) = pop2($stack);
+ $stack[] = pow($a, $b);
+ break;
+ case "_":
+ $a = array_pop($stack);
+ if($a === NULL)
+ throw new \Exception("Not enough numbers on stack. Invalid formula.");
+ $stack[] = -$a;
+ break;
+ default:
+ $stack[] = $token;
+ break;
+ }
+ }
+ return array_pop($stack);
+}
+
+function loopbody($code)
+{
+ return "try\n{\n" . indent_code($code) . "\n}\ncatch(\ste\BreakException \$e) { break; }\ncatch(\ste\ContinueException \$e) { continue; }\n";
+}
+
+$ste_builtins = array(
+ "if" => function($ast)
+ {
+ $output = "";
+ $condition = array();
+ $then = NULL;
+ $else = NULL;
+
+ foreach($ast->sub as $node)
+ {
+ if(($node instanceof TagNode) and ($node->name == "then"))
+ $then = $node->sub;
+ else if(($node instanceof TagNode) and ($node->name == "else"))
+ $else = $node->sub;
+ else
+ $condition[] = $node;
+ }
+
+ if($then === NULL)
+ throw new ParseCompileError("Transcompile error: Missing <ste:then> in <ste:if>.", $ast->tpl, $ast->offset);
+
+ $output .= "\$outputstack[] = \"\";\n\$outputstack_i++;\n";
+ $output .= _transcompile($condition);
+ $output .= "\$outputstack_i--;\nif(\$ste->evalbool(array_pop(\$outputstack)))\n{\n";
+ $output .= indent_code(_transcompile($then));
+ $output .= "\n}\n";
+ if($else !== NULL)
+ {
+ $output .= "else\n{\n";
+ $output .= indent_code(_transcompile($else));
+ $output .= "\n}\n";
+ }
+ return $output;
+ },
+ "cmp" => function($ast)
+ {
+ $operators = array(
+ array('eq', '=='),
+ array('neq', '!='),
+ array('lt', '<'),
+ array('lte', '<='),
+ array('gt', '>'),
+ array('gte', '>=')
+ );
+
+ $code = "";
+
+ if(isset($ast->params["var_b"]))
+ $b = '$ste->get_var_by_name(' . _transcompile($ast->params["var_b"], True) . ')';
+ else if(isset($ast->params["text_b"]))
+ $b = _transcompile($ast->params["text_b"], True);
+ else
+ throw new ParseCompileError("Transcompile error: neiter var_b nor text_b set in <ste:cmp>.", $ast->tpl, $ast->offset);
+
+ if(isset($ast->params["var_a"]))
+ $a = '$ste->get_var_by_name(' . _transcompile($ast->params["var_a"], True) . ')';
+ else if(isset($ast->params["text_a"]))
+ $a = _transcompile($ast->params["text_a"], True);
+ else
+ throw new ParseCompileError("Transcompile error: neiter var_a nor text_a set in <ste:cmp>.", $ast->tpl, $ast->offset);
+
+ if(!isset($ast->params["op"]))
+ throw new ParseCompileError("Transcompile error: op not given in <ste:cmp>.", $ast->tpl, $ast->offset);
+ if((count($ast->params["op"]) == 1) and ($ast->params["op"][0] instanceof TextNode))
+ {
+ /* Operator is known at compile time, this saves *a lot* of output code! */
+ $op = trim($ast->params["op"][0]->text);
+ $op_php = NULL;
+ foreach($operators as $v)
+ {
+ if($v[0] == $op)
+ {
+ $op_php = $v[1];
+ break;
+ }
+ }
+ if($op_php === NULL)
+ throw new ParseCompileError("Transcompile Error: Unknown operator in <ste:cmp>", $ast->tpl, $ast->offset);
+ $code .= "\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n";
+ }
+ else
+ {
+ $code .= "switch(trim(" . _transcompile($ast->params["op"], True) . "))\n{\n\t";
+ $code .= implode("", array_map(
+ function($op) use ($a,$b)
+ {
+ list($op_stetpl, $op_php) = $op;
+ return "case '$op_stetpl':\n\t\$outputstack[\$outputstack_i] .= (($a) $op_php ($b)) ? 'yes' : '';\n\tbreak;\n\t";
+ }, $operators
+ ));
+ $code .= "default: throw new \Exception('Runtime Error: Unknown operator in <ste:cmp>.');\n}\n";
+ }
+ return $code;
+ },
+ "not" => function($ast)
+ {
+ $code = "\$outputstack[] = '';\n\$outputstack_i++;\n";
+ $code .= _transcompile($ast->sub);
+ $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= (!\$ste->evalbool(array_pop(\$outputstack))) ? 'yes' : '';\n";
+ return $code;
+ },
+ "even" => function($ast)
+ {
+ $code = "\$outputstack[] = '';\n\$outputstack_i++;\n";
+ $code .= _transcompile($ast->sub);
+ $code .= "\$outputstack_i--;\n\$tmp_even = array_pop(\$outputstack);\n\$outputstack[\$outputstack_i] .= (is_numeric(\$tmp_even) and (\$tmp_even % 2 == 0)) ? 'yes' : '';\n";
+ return $code;
+ },
+ "for" => function($ast)
+ {
+ $code = "";
+ $loopname = "forloop_" . str_replace(".", "_", uniqid("",True));
+ if(empty($ast->params["start"]))
+ throw new ParseCompileError("Transcompile error: Missing 'start' parameter in <ste:for>.", $ast->tpl, $ast->offset);
+ $code .= "\$${loopname}_start = " . _transcompile($ast->params["start"], True) . ";\n";
+
+ if(empty($ast->params["stop"]))
+ throw new ParseCompileError("Transcompile error: Missing 'end' parameter in <ste:for>.", $ast->tpl, $ast->offset);
+ $code .= "\$${loopname}_stop = " . _transcompile($ast->params["stop"], True) . ";\n";
+
+ $step = NULL; /* i.e. not known at compilation time */
+ if(empty($ast->params["step"]))
+ $step = 1;
+ else if((count($ast->params["step"]) == 1) and ($ast->params["step"][0] instanceof TextNode))
+ $step = $ast->params["step"][0]->text + 0;
+ else
+ $code .= "\$${loopname}_step = " . _transcompile($ast->params["step"], True) . ";\n";
+
+ if(!empty($ast->params["counter"]))
+ $code .= "\$${loopname}_countername = " . _transcompile($ast->params["counter"], True) . ";\n";
+
+ $loopbody = empty($ast->params["counter"]) ? "" : "\$ste->set_var_by_name(\$${loopname}_countername, \$${loopname}_counter);\n";
+ $loopbody .= _transcompile($ast->sub);
+ $loopbody = indent_code("{\n" . loopbody(indent_code($loopbody)) . "\n}\n");
+
+ if($step === NULL)
+ {
+ $code .= "if(\$${loopname}_step == 0)\n\tthrow new \Exception('Runtime Error: step can not be 0 in <ste:for>.');\n";
+ $code .= "if(\$${loopname}_step > 0)\n{\n";
+ $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n";
+ $code .= $loopbody;
+ $code .= "\n}\nelse\n{\n";
+ $code .= "\tfor(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += \$${loopname}_step)\n";
+ $code .= $loopbody;
+ $code .= "\n}\n";
+ }
+ else if($step == 0)
+ throw new ParseCompileError("Transcompile Error: step can not be 0 in <ste:for>.", $ast->tpl, $ast->offset);
+ else if($step > 0)
+ $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter <= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n";
+ else
+ $code .= "for(\$${loopname}_counter = \$${loopname}_start; \$${loopname}_counter >= \$${loopname}_stop; \$${loopname}_counter += $step)\n$loopbody\n";
+
+ return $code;
+ },
+ "foreach" => function($ast)
+ {
+ $loopname = "foreachloop_" . str_replace(".", "_", uniqid("",True));
+ $code = "";
+
+ if(empty($ast->params["array"]))
+ throw new ParseCompileError("Transcompile Error: array not given in <ste:foreach>.", $ast->tpl, $ast->offset);
+ $code .= "\$${loopname}_arrayvar = " . _transcompile($ast->params["array"], True) . ";\n";
+
+ if(empty($ast->params["value"]))
+ throw new ParseCompileError("Transcompile Error: value not given in <ste:foreach>.", $ast->tpl, $ast->offset);
+ $code .= "\$${loopname}_valuevar = " . _transcompile($ast->params["value"], True) . ";\n";
+
+ if(!empty($ast->params["key"]))
+ $code .= "\$${loopname}_keyvar = " . _transcompile($ast->params["key"], True) . ";\n";
+
+ if(!empty($ast->params["counter"]))
+ $code .= "\$${loopname}_countervar = " . _transcompile($ast->params["counter"], True) . ";\n";
+
+ $loopbody = "";
+ $code .= "\$${loopname}_array = \$ste->get_var_by_name(\$${loopname}_arrayvar);\n";
+ $code .= "if(!is_array(\$${loopname}_array))\n\t\$${loopname}_array = array();\n";
+ if(!empty($ast->params["counter"]))
+ {
+ $code .= "\$${loopname}_counter = -1;\n";
+ $loopbody .= "\$${loopname}_counter++;\n\$ste->set_var_by_name(\$${loopname}_countervar, \$${loopname}_counter);\n";
+ }
+
+ $loopbody .= "\$ste->set_var_by_name(\$${loopname}_valuevar, \$${loopname}_value);\n";
+ if(!empty($ast->params["key"]))
+ $loopbody .= "\$ste->set_var_by_name(\$${loopname}_keyvar, \$${loopname}_key);\n";
+ $loopbody .= "\n";
+ $loopbody .= _transcompile($ast->sub);
+ $loopbody = "{\n" . loopbody(indent_code($loopbody)) . "\n}\n";
+
+ $code .= "foreach(\$${loopname}_array as \$${loopname}_key => \$${loopname}_value)\n$loopbody\n";
+
+ return $code;
+ },
+ "infloop" => function($ast)
+ {
+ return "while(True)\n{\n" . loopbody(indent_code(_transcompile($ast->sub)) . "\n}") . "\n";
+ },
+ "break" => function($ast)
+ {
+ return "throw new \\ste\\BreakException();\n";
+ },
+ "continue" => function($ast)
+ {
+ return "throw new \\ste\\ContinueException();\n";
+ },
+ "block" => function($ast)
+ {
+ if(empty($ast->params["name"]))
+ throw new ParseCompileError("Transcompile Error: name missing in <ste:block>.", $ast->tpl, $ast->offset);
+
+ $blknamevar = "blockname_" . str_replace(".", "_", uniqid("", True));
+
+ $code = "\$${blknamevar} = " . _transcompile($ast->params["name"], True) . ";\n";
+
+ $tmpblk = uniqid("", True);
+ $code .= "\$ste->blocks['$tmpblk'] = array_pop(\$outputstack);\n\$ste->blockorder[] = '$tmpblk';\n\$outputstack = array('');\n\$outputstack_i = 0;\n";
+
+ $code .= _transcompile($ast->sub);
+
+ $code .= "\$ste->blocks[\$${blknamevar}] = array_pop(\$outputstack);\n";
+ $code .= "if(array_search(\$${blknamevar}, \$ste->blockorder) === FALSE)\n\t\$ste->blockorder[] = \$${blknamevar};\n\$outputstack = array('');\n\$outputstack_i = 0;\n";
+
+ return $code;
+ },
+ "load" => function($ast)
+ {
+ if(empty($ast->params["name"]))
+ throw new ParseCompileError("Transcompile Error: name missing in <ste:load>.", $ast->tpl, $ast->offset);
+
+ return "\$outputstack[\$outputstack_i] .= \$ste->load(" . _transcompile($ast->params["name"], True) . ");\n";
+ },
+ "mktag" => function($ast)
+ {
+ if(empty($ast->params["name"]))
+ throw new ParseCompileError("Transcompile Error: name missing in <ste:mktag>.", $ast->tpl, $ast->offset);
+
+ $tagname = _transcompile($ast->params["name"], True);
+
+ $fxbody = "\$outputstack = array(); \$outputstack_i = 0;\$ste->vars['_tag_parameters'] = \$params;\n";
+
+ if(!empty($ast->params["mandatory"]))
+ {
+ $code .= "\$outputstack[] = '';\n\$outputstack_i++;\n";
+ $code .= _transcompile($ast->params["mandatory"]);
+ $code .= "\$outputstack_i--;\n\$mandatory_params = explode('|', array_pop(\$outputstack));\n";
+
+ $fxbody .= "foreach(\$mandatory_params as \$mp)\n{\n\tif(!isset(\$params[\$mp]))\n\t\tthrow new \Exception(\"Runtime Error: \$mp missing in <ste:\" . $tagname . \">. Stop.\");\n}";
+ }
+
+ $fxbody .= _transcompile($ast->sub);
+ $fxbody .= "return array_pop(\$outputstack);";
+
+ $code .= "\$tag_fx = function(\$ste, \$params, \$sub) use (\$mandatory_params)\n{\n" . indent_code($fxbody) . "\n};\n";
+ $code .= "\$ste->register_tag($tagname, \$tag_fx);\n";
+
+ return $code;
+ },
+ "tagcontent" => function($ast)
+ {
+ return "\$outputstack[\$outputstack_i] .= \$sub(\$ste);";
+ },
+ "set" => function($ast)
+ {
+ if(empty($ast->params["var"]))
+ throw new ParseCompileError("Transcompile Error: var missing in <ste:set>.", $ast->tpl, $ast->offset);
+
+ $code = "\$outputstack[] = '';\n\$outputstack_i++;\n";
+ $code .= _transcompile($ast->sub);
+ $code .= "\$outputstack_i--;\n";
+
+ $code .= "\$ste->set_var_by_name(" . _transcompile($ast->params["var"], True) . ", array_pop(\$outputstack));\n";
+
+ return $code;
+ },
+ "calc" => function($ast)
+ {
+ $code = "\$outputstack[] = '';\n\$outputstack_i++;\n";
+ $code .= _transcompile($ast->sub);
+ $code .= "\$outputstack_i--;\n\$outputstack[\$outputstack_i] .= \$ste->calc(array_pop(\$outputstack));\n";
+
+ return $code;
+ }
+);
+
+function escape_text($text)
+{
+ return addcslashes($text, "\r\n\t\$\0..\x1f\\'\"\x7f..\xff");
+}
+
+function _transcompile($ast, $no_outputstack = False) /* The real transcompile function, does not add boilerplate code. */
+{
+ $code = "";
+ global $ste_builtins;
+
+ $text_and_var_buffer = array();
+
+ foreach($ast as $node)
+ {
+ if($node instanceof TextNode)
+ $text_and_var_buffer[] = '"' . escape_text($node->text) . '"';
+ else if($node instanceof VariableNode)
+ $text_and_var_buffer[] = $node->transcompile();
+ else if($node instanceof TagNode)
+ {
+ if(!empty($text_and_var_buffer))
+ {
+ $code .= "\$outputstack[\$outputstack_i] .= " . implode (" . ", $text_and_var_buffer) . ";\n";
+ $text_and_var_buffer = array();
+ }
+ if(isset($ste_builtins[$node->name]))
+ $code .= $ste_builtins[$node->name]($node);
+ else
+ {
+ $paramarray = "parameters_" . str_replace(".", "_", uniqid("", True));
+ $code .= "\$$paramarray = array();\n";
+
+ foreach($node->params as $pname => $pcontent)
+ $code .= "\$${paramarray}['" . escape_text($pname) . "'] = " . _transcompile($pcontent, True) . ";\n";
+
+ $code .= "\$outputstack[\$outputstack_i] .= \$ste->call_tag('" . escape_text($node->name) . "', \$${paramarray}, ";
+ $code .= empty($node->sub) ? "function(\$ste) { return ''; }" : transcompile($node->sub);
+ $code .= ");\n";
+ }
+ }
+ }
+
+ if(!empty($text_and_var_buffer))
+ {
+ if(!$no_outputstack)
+ $code .= "\$outputstack[\$outputstack_i] .= ";
+ $code .= implode (" . ", $text_and_var_buffer);
+ if(!$no_outputstack)
+ $code .= ";\n";
+ $text_and_var_buffer = array();
+ }
+ else if($no_outputstack)
+ {
+ $code = "\"\"";
+ }
+
+ return $code;
+}
+
+$ste_transc_boilerplate = "\$outputstack = array('');\n\$outputstack_i = 0;\n";
+
+/*
+ * Function: transcompile
+ * Transcompiles an abstract syntax tree to PHP.
+ * You only need this function, if you want to manually transcompile a template.
+ *
+ * Parameters:
+ * $ast - The abstract syntax tree to transcompile.
+ *
+ * Returns:
+ * PHP code. The PHP code is an anonymous function expecting a <STECore> instance as its parameter and returns a string (everything that was not pached into a section).
+ */
+function transcompile($ast) /* Transcompile and add some boilerplate code. */
+{
+ global $ste_transc_boilerplate;
+ return "function(\$ste)\n{\n" . indent_code($ste_transc_boilerplate . _transcompile($ast) . "return array_pop(\$outputstack);") . "\n}";
+}
+
+/*
+ * Constants: Template modes
+ *
+ * MODE_SOURCE - The Templates source
+ * MODE_TRANSCOMPILED - The transcompiled template
+ */
+const MODE_SOURCE = 0;
+const MODE_TRANSCOMPILED = 1;
+
+/*
+ * Class: StorageAccess
+ * An interface.
+ * A StorageAccess implementation is used to access the templates from any storage.
+ * This means, that you are not limited to store the Templates inside directories, you can also use a database or something else.
+ */
+interface StorageAccess
+{
+ /*
+ * Function: load
+ * Loading a template.
+ *
+ * Parameters:
+ * $tpl - The name of the template.
+ * &$mode - Which mode is preferred? One of the <Template modes>.
+ * If <MODE_SOURCE>, the raw sourcecode is expected, if <MODE_TRANSCOMPILED> the transcompiled template *as a callable function* (expecting an <STECore> instance as first parameter) is expected.
+ * If the transcompiled version is not available or older than the source, you can set this parameter to <MODE_SOURCE> and return the source.
+ *
+ * Returns:
+ * Either the sourcecode or a callable function (first, and only parameter: an <STECore> instance).
+ */
+ public function load($tpl, &$mode);
+
+ /*
+ * Function: save
+ * Saves a template.
+ *
+ * Parameters:
+ * $tpl -The name of the template.
+ * $data - The data to be saved.
+ * $mode - A <Template mode> constant.
+ */
+ public function save($tpl, $data, $mode);
+}
+
+/*
+ * Class: FilesystemStorageAccess
+ * The default <StorageAccess> implementation for loading / saving templates into a directory structure.
+ */
+class FilesystemStorageAccess implements StorageAccess
+{
+ protected $sourcedir;
+ protected $transcompileddir;
+
+ /*
+ * Constructor: __construct
+ *
+ * Parameters:
+ * $src - The directory with the sources (Writing permissions are not mandatory, because STE does not save template sources).
+ * $transc - The directory with the transcompiled templates (the PHP instance / the HTTP Server needs writing permissions to this directory).
+ */
+ public function __construct($src, $transc)
+ {
+ $this->sourcedir = $src;
+ $this->transcompileddir = $transc;
+ }
+
+ public function load($tpl, &$mode)
+ {
+ $src_fn = $this->sourcedir . "/" . $tpl;
+ $transc_fn = $this->transcompileddir . "/" . $tpl . ".php";
+
+ if($mode == MODE_SOURCE)
+ {
+ $content = @file_get_contents($src_fn);
+ if($content === False)
+ throw new \Exception("Template not found.");
+ return $content;
+ }
+
+ $src_stat = @stat($src_fn);
+ $transc_stat = @stat($transc_fn);
+
+ if(($src_stat === False) and ($transc_stat === False))
+ throw new \Exception("Template not found.");
+ else if($transc_stat === False)
+ {
+ $mode = MODE_SOURCE;
+ return file_get_contents($src_fn);
+ }
+ else if($src_stat === False)
+ {
+ include($transc_fn);
+ return $transcompile_fx;
+ }
+ else
+ {
+ if($src_stat["mtime"] > $transc_stat["mtime"])
+ {
+ $mode = MODE_SOURCE;
+ return file_get_contents($src_fn);
+ }
+ else
+ {
+ include($transc_fn);
+ return $transcompile_fx;
+ }
+ }
+ }
+
+ public function save($tpl, $data, $mode)
+ {
+ $fn = (($mode == MODE_SOURCE) ? $this->sourcedir : $this->transcompileddir) . "/" . $tpl . (($mode == MODE_TRANSCOMPILED) ? ".php" : "");
+ @mkdir(dirname($fn), 0777, True);
+ file_put_contents($fn, "<?php \$transcompile_fx = $data; ?>");
+ }
+}
+
+class BreakException extends \Exception { }
+class ContinueException extends \Exception { }
+
+/*
+ * Class: STECore
+ * The Core of STE
+ */
+class STECore
+{
+ private $tags;
+ private $storage_access;
+ private $cur_tpl_dir;
+
+ /*
+ * Variables: Public variables
+ *
+ * $blocks - Associative array of blocks (see the language definition).
+ * $blockorder - The order of the blocks (an array)
+ * $vars - Associative array of all template variables. Use this to pass data to your templates.
+ */
+ public $blocks;
+ public $blockorder;
+ public $vars;
+
+ /*
+ * Constructor: __construct
+ *
+ * Parameters:
+ * $storage_access - An Instance of a <StorageAccess> implementation.
+ */
+ public function __construct($storage_access)
+ {
+ $this->storage_access = $storage_access;
+ $this->cur_tpl_dir = "/";
+ STEStandardLibrary::_register_lib($this);
+ $this->vars = array();
+ $this->blockorder = array();
+ $this->blocks = array();
+ }
+
+ /*
+ * Function: register_tag
+ * Register a custom tag.
+ *
+ * Parameters:
+ * $name - The name of the tag.
+ * $callback - A callable function (This must tage three parameters: The <STECore> instance, an associative array of parameters, and a function representing the tags content(This expects the <STECore> instance as its only parameter and returns its text result, i.e to get the text, you neeed to call this function with the <STECore> instance as a parameter)).
+ */
+ public function register_tag($name, $callback)
+ {
+ if(!is_callable($callback))
+ throw new \Exception("Can not register tag \"$name\", not callable.");
+ if(empty($name))
+ throw new \Exception("Can not register tag, empty name.");
+ $this->tags[$name] = $callback;
+ }
+
+ /*
+ * Function: call_tag
+ * Calling a custom tag (builtin ones can not be called)
+ *
+ * Parameters:
+ * $name - The Tag's name
+ * $params - Associative array of parameters
+ * $sub - A callable function (expecting an <STECore> instance as it's parameter) that represents the tag's content.
+ *
+ * Returns:
+ * The output of the tag.
+ */
+ public function call_tag($name, $params, $sub)
+ {
+ if(!isset($this->tags[$name]))
+ throw new \Exception("Can not call tag \"$name\": Does not exist.");
+ return call_user_func($this->tags[$name], $this, $params, $sub);
+ }
+
+ public function calc($expression)
+ {
+ return calc_rpn(shunting_yard($expression));
+ }
+
+ /*
+ * Function: exectemplate
+ * Executes a template and returns the result. The huge difference to <load> is that this function will also output all blocks.
+ *
+ * Parameters:
+ * $tpl - The name of the template to execute.
+ *
+ * Returns:
+ * The output of the template.
+ */
+ public function exectemplate($tpl)
+ {
+ $output = "";
+ $lastblock = $this->load($tpl);
+
+ foreach($this->blockorder as $blockname)
+ $output .= $this->blocks[$blockname];
+
+ return $output . $lastblock;
+ }
+
+ /*
+ * Function: get_var_reference
+ * Get a reference to a template variable using a variable name.
+ * This can be used,if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ * $create_if_not_exist - Should the variable be created, if it does not exist? Otherwise NULL will be returned, if the variable does not exist.
+ *
+ * Returns:
+ * A Reference to the variable.
+ */
+
+ public function &get_var_reference($name, $create_if_not_exist)
+ {
+ $ref = $this->_get_var_reference($this->vars, $name, $create_if_not_exist);
+ return $ref;
+ }
+
+ private function &_get_var_reference(&$from, $name, $create_if_not_exist)
+ {
+ $bracket_open = strpos($name, "[");
+ if($bracket_open === False)
+ {
+ if(isset($from[$name]) or $create_if_not_exist)
+ {
+ $ref = &$from[$name];
+ return $ref;
+ }
+ else
+ return NULL;
+ }
+ else
+ {
+ $old_varname = $varname;
+ $bracket_close = strpos($name, "]", $bracket_open);
+ if($bracket_close === FALSE)
+ throw new Excpeption("Runtime Error: Invalid varname \"$varname\". Missing closing \"]\".");
+ $varname = substr($name, 0, $bracket_open);
+ $name = substr($name, $bracket_open + 1, $bracket_close - $bracket_open - 1) . substr($name, $bracket_close + 1);
+ if(!is_array($from[$varname]))
+ {
+ if($create_if_not_exist)
+ $from[$varname] = array();
+ else
+ return NULL;
+ }
+ try
+ {
+ $ref = &$this->_get_var_reference($from[$varname], $name, $create_if_not_exist);
+ return $ref;
+ }
+ catch(Exception $e)
+ {
+ throw new Excpeption("Runtime Error: Invalid varname \"$old_varname\". Missing closing \"]\".");
+ }
+ }
+ }
+
+ /*
+ * Function: set_var_by_name
+ * Set a template variable by its name.
+ * This can be used,if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ * $val - The new value.
+ */
+ public function set_var_by_name($name, $val)
+ {
+ $ref = &$this->_get_var_reference($this->vars, $name, True);
+ $ref = $val;
+ }
+
+ /*
+ * Function: get_var_by_name
+ * Get a template variable by its name.
+ * This can be used,if your custom tag takes a variable name as a parameter.
+ *
+ * Parameters:
+ * $name - The variables name.
+ *
+ * Returns:
+ * The variables value.
+ */
+ public function get_var_by_name($name)
+ {
+ $ref = $this->_get_var_reference($this->vars, $name, False);
+ return $ref === NULL ? "" : $ref;
+ }
+
+ /*
+ * Function: load
+ * Load a template and return its result (blocks not included, use <exectemplate> for this).
+ *
+ * Parameters:
+ * $tpl - The name of the template to be loaded.
+ * $quiet - If true, do not output anything and do notmodify the blocks. This can be useful to load custom tags that are programmed in STE T/PL. Default: false.
+ *
+ * Returns:
+ * The result of the template (if $quiet == false).
+ */
+ public function load($tpl, $quiet=False)
+ {
+ $tpldir_b4 = $this->cur_tpl_dir;
+
+ /* Resolve ".", ".." and protect from possible LFI */
+ $tpl = str_replace("\\", "/", $tpl);
+ if($tpl[0] != "/")
+ $tpl = $this->cur_tpl_dir . "/" . $tpl;
+ $pathex = array_filter(explode("/", $tpl), function($s) { return ($s != ".") and (!empty($s)); });
+ $pathex = array_merge($pathex);
+ while(($i = array_search("..", $pathex)) !== False)
+ {
+ if($i == 0)
+ $pathex = array_slice($pathex, 1);
+ else
+ $pathex = array_merge(array_slice($pathex, 0, $i), array_slice($pathex, $i + 2));
+ }
+ $tpl = implode("/", $pathex);
+ $this->cur_tpl_dir = dirname($tpl);
+
+ if($quiet)
+ {
+ $blocks_back = clone $this->blocks;
+ $blockorder_back = clone $this->blockorder;
+ }
+
+ $mode = MODE_TRANSCOMPILED;
+ $content = $this->storage_access->load($tpl, $mode);
+ if($mode == MODE_SOURCE)
+ {
+ $content = precompile($content);
+ try
+ {
+ $ast = parse($content, $tpl, 0);
+ $transc = transcompile($ast);
+ }
+ catch(ParseCompileError $e)
+ {
+ $e->rewrite($content);
+ throw $e;
+ }
+ $this->storage_access->save($tpl, $transc, MODE_TRANSCOMPILED);
+ eval("\$content = $transc;");
+ }
+
+ $output = $content($this);
+
+ $this->cur_tpl_dir = $tpldir_b4;
+
+ if($quiet)
+ {
+ $this->blocks = $blocks_back;
+ $this->blockorder = $blockorder_back;
+ }
+ else
+ return $output;
+ }
+
+ /*
+ * Function: evalbool
+ * Test, if a text represents false (an empty / only whitespace text) or true (everything else).
+ *
+ * Parameters:
+ * $txt - The text to test.
+ *
+ * Returns:
+ * true/false.
+ */
+ public function evalbool($txt)
+ {
+ return trim($txt) != "";
+ }
+}
+
+class STEStandardLibrary
+{
+ static public function _register_lib($ste)
+ {
+ foreach(get_class_methods(__CLASS__) as $method)
+ if($method[0] != "_")
+ $ste->register_tag($method, array(__CLASS__, $method));
+ }
+
+ static public function escape($ste, $params, $sub)
+ {
+ return htmlentities($sub($ste), ENT_QUOTES, "UTF-8");
+ }
+
+ static public function strlen($ste, $params, $sub)
+ {
+ return strlen($sub($ste));
+ }
+
+ static public function arraylen($ste, $params, $sub)
+ {
+ if(empty($params["array"]))
+ throw new \Exception("Runtime Error: missing array parameter in <ste:arraylen>.");
+ $a = $ste->get_var_by_name($params["array"]);
+ return (is_array($a)) ? count($a) : "";
+ }
+
+ static public function inc($ste, $params, $sub)
+ {
+ if(empty($params["var"]))
+ throw new \Exception("Runtime Error: missing var parameter in <ste:inc>.");
+ $ref = $ste->_get_var_reference($ste->vars, $params["var"]);
+ $ref++;
+ }
+
+ static public function dec($ste, $params, $sub)
+ {
+ if(empty($params["var"]))
+ throw new \Exception("Runtime Error: missing var parameter in <ste:dec>.");
+ $ref = $ste->_get_var_reference($ste->vars, $params["var"]);
+ $ref--;
+ }
+
+ static public function date($ste, $params, $sub)
+ {
+ return @strftime($sub($ste), empty($params["timestamp"]) ? @time() : (int) $params["timestamp"]);
+ }
+}
+
+?>
diff --git a/r7r_repo/templates/.htaccess b/r7r_repo/templates/.htaccess
new file mode 100755
index 0000000..19469bf
--- /dev/null
+++ b/r7r_repo/templates/.htaccess
@@ -0,0 +1,4 @@
+<Files *>
+ Order Allow,Deny
+ Deny from all
+</Files>
diff --git a/r7r_repo/templates/src/home.html b/r7r_repo/templates/src/home.html
new file mode 100644
index 0000000..bf9623a
--- /dev/null
+++ b/r7r_repo/templates/src/home.html
@@ -0,0 +1,35 @@
+<ste:load name="master.html" />
+<ste:block name="content">
+ <h2>Latest Packages</h2>
+ <table class="listtab fullwidth">
+ <thead>
+ <tr>
+ <th>Package Name</th>
+ <th>Version</th>
+ <th>Description</th>
+ <th>Last update</th>
+ </tr>
+ </thead>
+ <tbody>
+ <ste:if>
+ <ste:set var="pkgs_n"><ste:arraylen array="latest_pkgs" /></ste:set>
+ ~{$pkgs_n|gt|0}
+ <ste:then>
+ <ste:foreach array="latest_pkgs" value="pkg">
+ <tr>
+ <td><ste:escape>$pkg[name]</ste:escape></td>
+ <td><ste:escape>$pkg[version]</ste:escape></td>
+ <td><ste:escape>$pkg[description]</ste:escape></td>
+ <td><ste:date format="%d. %h. %Y, %H:%M:%S">$pkg[last_update]</ste:date></td>
+ </tr>
+ </ste:foreach>
+ </ste:then>
+ <ste:else>
+ <tr>
+ <td style="text-align: center; font-style: italic;" colspan="4">No Packages available.</td>
+ </tr>
+ </ste:else>
+ </ste:if>
+ </tbody>
+ </table>
+</ste:block>
diff --git a/r7r_repo/templates/src/master.html b/r7r_repo/templates/src/master.html
new file mode 100644
index 0000000..a2dbd58
--- /dev/null
+++ b/r7r_repo/templates/src/master.html
@@ -0,0 +1,90 @@
+<ste:mktag name="menu_elem" mandatory="name|path">
+ <li?{~{$menu|eq|$_tag_parameters[name]}| class="active"|}>
+ <a href="${rel_path_to_root}${_tag_parameters[path]}"><ste:tagcontent /></a>
+ </li>
+</ste:mktag>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title><ste:escape>?{$title|$title - $repo[name]|$repo[name]}</ste:escape></title>
+ <link rel="stylesheet" type="text/css" media="screen" href="$rel_path_to_root/r7r_repo/css/common.css" />
+ <link rel="stylesheet" type="text/css" media="screen" href="$rel_path_to_root/r7r_repo/css/main.css" />
+</head>
+<body>
+ <div id="maincontainer">
+ <div id="heading">
+ <h1><ste:escape>$repo[name]</ste:escape></h1>
+ <span id="subheading"><ste:escape>$repo[description]</ste:escape></span>
+ </div>
+ <ul id="mainmenu">
+ <ste:menu_elem name="home" path="">Home</ste:menu_elem>
+ <ste:if>
+ $user[logged_in]
+ <ste:then>
+ <ste:if>
+ $user[admin]
+ <ste:then>
+ <ste:menu_elem name="admin" path="/admin">Administration</ste:menu_elem>
+ <ste:menu_elem name="upload" path="/upload">Upload Package</ste:menu_elem>
+ </ste:then>
+ <ste:else>
+ ?{$repo[public]|<ste:menu_elem name="upload" path="/upload">Upload Package</ste:menu_elem>|}
+ </ste:else>
+ </ste:if>
+ <ste:menu_elem name="my_plugins" path="/my_plugins">My Plugins</ste:menu_elem>
+ <ste:menu_elem name="account" path="/account">My Account</ste:menu_elem>
+ <ste:menu_elem name="logout" path="/logout">Logout</ste:menu_elem>
+ </ste:then>
+ <ste:else>
+ ?{$repo[public]|<ste:menu_elem name="register" path="/register">Register</ste:menu_elem>|}
+ </ste:else>
+ </ste:if>
+ </ul>
+ <div id="contentwrapper">
+ <div id="metabar">
+ <div class="metabar_module">
+ <h2>Repository Base URL</h2>
+ <input type="text" readonly="readonly" value="<ste:escape>$repo[baseurl]</ste:escape>" />
+ </div>
+ <div class="metabar_module">
+ <form action="$rel_path_to_root/search" method="POST" accept-charset="UTF-8">
+ <h2>Search</h2>
+ <input type="text" name="searchterm" /><br />
+ <input type="submit" />
+ </form>
+ </div>
+ <div class="metabar_module">
+ <ste:if>
+ $user[logged_in]
+ <ste:then>
+ Welcome, <strong><ste:escape>$user[name]</ste:escape></strong>!
+ </ste:then>
+ <ste:else>
+ <form action="$rel_path_to_root/login" method="POST" accept-charset="UTF-8">
+ <h2>Login</h2>
+ <p>
+ <strong>Name:</strong><br />
+ <input type="text" name="username" />
+ </p>
+ <p>
+ <strong>Password:</strong><br />
+ <input type="password" name="password" />
+ </p>
+ <p><input type="submit" name="login" /></p>
+ ?{$repo[public]|No account? <a href="$rel_path_to_root/register">Register for a new account.</a>|}
+ </form>
+ </ste:else>
+ </ste:if>
+ </div>
+ <ste:block name="additional_metabar_modules" />
+ </div>
+ <div id="content">
+ <ste:block name="content" />
+ </div>
+ </div>
+ <div id="footer"></div>
+ </div>
+</body>
+</html>
diff --git a/r7r_repo/templates/src/setup.html b/r7r_repo/templates/src/setup.html
new file mode 100644
index 0000000..c73abb4
--- /dev/null
+++ b/r7r_repo/templates/src/setup.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Setup</title>
+ <link rel="stylesheet" type="text/css" media="screen" href="$rel_path_to_root/r7r_repo/css/common.css" />
+ <link rel="stylesheet" type="text/css" media="screen" href="$rel_path_to_root/r7r_repo/css/setup.css" />
+</head>
+<body>
+ <div id="maincontainer">
+ <h1>Setup</h1>
+ <ste:if>
+ $error
+ <ste:then>
+ <div class="error">
+ <ste:escape>$error</ste:escape>
+ </div>
+ </ste:then>
+ </ste:if>
+ <form action="$rel_path_to_root/setup" method="POST" accept-charset="UTF-8">
+ <p>
+ <strong>Admin username</strong><br />
+ <input type="text" name="admin_name" value="" />
+ </p>
+ <p>
+ <strong>Admin initial password</strong><br />
+ <input type="password" name="admin_password" />
+ </p>
+ <p>
+ <strong>Repository name</strong><br />
+ <input type="text" name="repo_name" />
+ </p>
+ <p>
+ <strong>Repository description</strong><br />
+ <input type="text" name="repo_description" />
+ </p>
+ <p>
+ <strong>Repostory Base URL</strong><br />
+ <input type="text" name="repo_baseurl" value="<ste:escape>$baseurl_predicted</ste:escape>"/>
+ </p>
+ <p>
+ <strong>Repository mode</strong><br />
+ <select name="repo_mode">
+ <option value="public" selected="selected">Public - Everyone (ie. registered users) is allowed to publish packages.</option>
+ <option value="private"> Private - Only administrators are allowed to publish packages.</option>
+ </select>
+ </p>
+ <input type="submit" name="send_data" />
+ </form>
+ </div>
+</body>
+</html>
diff --git a/r7r_repo/urlprocess.php b/r7r_repo/urlprocess.php
new file mode 100644
index 0000000..14d67c1
--- /dev/null
+++ b/r7r_repo/urlprocess.php
@@ -0,0 +1,179 @@
+<?php
+/*
+ * File: ratatoeskr/sys/urlprocess.php
+ *
+ * Providing functions / classes to handle URLs
+ *
+ * License:
+ * This file is part of Ratatöskr.
+ * Ratatöskr is licensed unter the MIT / X11 License.
+ * See "ratatoeskr/licenses/ratatoeskr" for more information.
+ */
+
+/*
+ * Function: url_action_simple
+ * Generate an action in a more simple way.
+ *
+ * Parameters:
+ * $function - A callback that gets the $data var as an input and returns the new $data var. Can throw an <Redirect> Exception.
+ *
+ * Returns:
+ * A callback that can be used as an url action.
+ */
+function url_action_simple($function)
+{
+ return function(&$data, $url_now, &$url_next) use ($function)
+ {
+ try
+ {
+ $data = call_user_func($function, $data);
+ $url_next = array();
+ }
+ catch(Redirect $e)
+ {
+ $url_next = $e->nextpath;
+ }
+ };
+}
+
+/*
+ * Function: url_action_subactions
+ * Generate an action that contains subactions. Subactions can redirect to ".." to go to the level above.
+ *
+ * Parameters:
+ * $actions - Associative array of actions.
+ *
+ * Returns:
+ * A callback that can be used as an url action.
+ */
+function url_action_subactions($actions)
+{
+ return function(&$data, $url_now, &$url_next) use ($actions)
+ {
+ $result = url_process($url_next, $actions, $data);
+ if($result !== NULL)
+ $url_next = $result;
+ else
+ $url_next = array();
+ };
+}
+
+/*
+ * Function: url_action_alias
+ * Generate an action that is an alias for another one (i.e. redirects).
+ *
+ * Parameters:
+ * $for - Path (array) of the action this one should be an alias of.
+ *
+ * Returns:
+ * A callback that can be used as an url action.
+ */
+function url_action_alias($for)
+{
+ return function(&$data, $url_now, &$url_next) use($for)
+ {
+ $url_next = array_merge($for, $url_next);
+ };
+}
+
+/*
+ * Function: url_process
+ * Choose an appropiate action for the given URL.
+ *
+ * Parameters:
+ * $url - Either an array containing the URL components or the URL (both relative).
+ * $actions - Associative array of actions.
+ * Key is the name (anything alphanumeric, should usually not start with '_', reserved for special URL names, see beneath).
+ * Value is a callback of the form: function(&$data, $url_now, &$url_next). $data can be used for shared data between subactions. $url_next can be modified in order to redirect to another action / stop the routing.
+ *
+ * Special actions:
+ * _index - If name is empty, the index will be called.
+ * _default - If nothing was found, this is the default.
+ * _notfound - If not even _default exists or NotFoundError was thrown.
+ * _prelude - If existant, will be executed before everything else.
+ * _epilog - If existant, will be executed after evrything else.
+ */
+function url_process($url, $actions, &$data)
+{
+ $epilog_running = 0;
+ if(is_string($url))
+ $url = explode("/", $url);
+ $url = array_filter($url, function($x) { return !empty($x); });
+ if(count($url) == 0)
+ $url = array("_index");
+
+ if(isset($actions["_prelude"]))
+ $url = array_merge(array("_prelude"), $url);
+
+ $url_now = $url[0];
+ $url_next = array_slice($url, 1);
+
+ while(is_string($url_now) and ($url_now != "") and ($url_now != ".."))
+ {
+ $cb = NULL;
+ if(empty($url_now))
+ $url_now = "_index";
+ if(isset($actions[$url_now]))
+ $cb = $actions[$url_now];
+ else if(isset($actions["_default"]))
+ $cb = $actions["_default"];
+ else if(isset($actions["_notfound"]))
+ $cb = $actions["_notfound"];
+ else
+ throw new NotFoundError();
+
+ try
+ {
+ $cb($data, $url_now, $url_next);
+ }
+ catch(NotFoundError $e)
+ {
+ if(isset($actions["_notfound"]))
+ $url_next = array("_notfound");
+ else
+ throw $e;
+ }
+
+ if(count($url_next) > 0)
+ {
+ $url_now = $url_next[0];
+ $url_next = array_slice($url_next, 1);
+ }
+ else if(isset($actions["_epilog"]) and ($epilog_running <= 0))
+ {
+ $epilog_running = 2;
+ $url_now = "_epilog";
+ }
+ else
+ $url_now = "";
+
+ --$epilog_running;
+ }
+
+ if($url_now == "..")
+ return $url_next;
+ else
+ return NULL;
+}
+
+/*
+ * Class: Redirect
+ * Exception that can be thrown inside an <url_action_simple>.
+ * throw new Redirect(array("..", "foo")); will redirect to "../foo" and won't touch $data.
+ */
+class Redirect extends Exception
+{
+ public $nextpath;
+ public function __construct($nextpath)
+ {
+ $this->nextpath = $nextpath;
+ parent::__construct();
+ }
+}
+/*
+ * Class: NotFoundError
+ * An Exception
+ */
+class NotFoundError extends Exception { }
+
+?>
diff --git a/r7r_repo/utils.php b/r7r_repo/utils.php
new file mode 100644
index 0000000..d6093ca
--- /dev/null
+++ b/r7r_repo/utils.php
@@ -0,0 +1,229 @@
+<?php
+/*
+ * File: ratatoeskr/sys/utils.php
+ *
+ * Various useful helper functions.
+ *
+ * License:
+ * This file is part of Ratatöskr.
+ * Ratatöskr is licensed unter the MIT / X11 License.
+ * See "ratatoeskr/licenses/ratatoeskr" for more information.
+ */
+
+/*
+ * Function: array_repeat
+ *
+ * Parameters:
+ *
+ * $val -
+ * $n -
+ *
+ * Returns:
+ *
+ * An array with $val $n-times repeated.
+ */
+function array_repeat($val, $n)
+{
+ $rv = array();
+ for($i = 0; $i < $n; ++$i)
+ array_push($rv, $val);
+ return $rv;
+}
+
+/*
+ * Function: array_blend
+ *
+ * Blend multiple arrays together.
+ *
+ * Example:
+ *
+ * array_blend(array(1,2,3), array(4,5,6), array(7,8,9));
+ * will return array(1,4,7,2,5,8,3,6,9)
+ */
+function array_blend()
+{
+ $arrays = array_filter(func_get_args(), "is_array");
+
+ switch(count($arrays))
+ {
+ case 0: return array(); break;
+ case 1: return $arrays[0]; break;
+ default:
+ $rv = array();
+ while(array_sum(array_map("count", $arrays)) > 0)
+ {
+ for($i = 0; $i < count($arrays); ++$i)
+ {
+ $val = array_shift($arrays[$i]);
+ if($val === NULL)
+ continue;
+ array_push($rv, $val);
+ }
+ }
+ return $rv;
+ break;
+ }
+}
+
+/*
+ * Function: array_filter_empty
+ *
+ * Filters all empty elements out of an array.
+ *
+ * Parameters:
+ *
+ * $input - The input array
+ *
+ * Returns:
+ *
+ * The $input without its empty elements.
+ */
+function array_filter_empty($input)
+{
+ return array_filter($input, function($x){return !empty($x);});
+}
+
+/*
+ * Function: array_filter_keys
+ *
+ * Like PHPs `array_filter`, but callback will get the key, not the value of the array element.
+ */
+function array_filter_keys($input, $callback)
+{
+ if(!is_array($input))
+ throw new InvalidArgumentException("Argument 1 must be an array");
+ if(empty($input))
+ return array();
+ $delete_keys = array_filter(array_keys($input), function ($x) use ($callback) { return !$callback($x);});
+ foreach($delete_keys as $key)
+ unset($input[$key]);
+ return $input;
+}
+
+/*
+ * Function: array_kvpairs_to_assoc
+ * Convert array of key-value pairs to an associative array.
+ *
+ * Parameters:
+ * $input - Array of key-value pairs
+ *
+ * Returns:
+ * An associative array.
+ */
+function array_kvpairs_to_assoc($input)
+{
+ $rv = array();
+ foreach($input as $kvpair)
+ $rv[$kvpair[0]] = $kvpair[1];
+ return $rv;
+}
+
+/*
+ * Function: intcmp
+ * Compare integers (equavilent to strcmp)
+ */
+function intcmp($a, $b)
+{
+ return ($a == $b) ? 0 : (($a < $b) ? -1 : 1);
+}
+
+/*
+ * Function: ucount
+ *
+ * Count elements of an array matching unser-defined rules.
+ *
+ * Parameters:
+ * $array - The input array.
+ * $callback - A callback function. It will be called with the current value as the only parameter. The value is counted, if callback returns TRUE.
+ *
+ * Returns:
+ *
+ * Number of elements where $callback returned TRUE.
+ */
+function ucount($array, $callback)
+{
+ return count(array_filter($array, $callback));
+}
+
+/*
+ * Function: vcount
+ *
+ * Counts how often $value appears in $array.
+ *
+ * Parameters:
+ *
+ * $array -
+ * $value -
+ *
+ * Returns:
+ *
+ * How often $value appears in $array.
+ */
+function vcount($array, $value)
+{
+ return ucount($array, function($x){return $x===$value;});
+}
+
+/*
+ * Function: self_url
+ *
+ * Gets current URL.
+ *
+ * From: http://dev.kanngard.net/Permalinks/ID_20050507183447.html
+ */
+function self_url() {
+ $s = empty($_SERVER["HTTPS"]) ? ''
+ : ($_SERVER["HTTPS"] == "on") ? "s"
+ : "";
+ $protocol = strleft(strtolower($_SERVER["SERVER_PROTOCOL"]), "/").$s;
+ $port = ($_SERVER["SERVER_PORT"] == "80") ? ""
+ : (":".$_SERVER["SERVER_PORT"]);
+ return $protocol."://".$_SERVER['SERVER_NAME'].$port.$_SERVER['REQUEST_URI'];
+}
+function strleft($s1, $s2) {
+ return substr($s1, 0, strpos($s1, $s2));
+}
+
+/*
+ * Function: htmlesc
+ * Escape HTML (shorter than htmlentities($text, ENT_QUOTES, "UTF-8"))
+ *
+ * Parameters:
+ * $text - Input text.
+ *
+ * Returns:
+ * HTML
+ */
+function htmlesc($text)
+{
+ return htmlentities($text, ENT_QUOTES, "UTF-8");
+}
+
+/*
+ * Function: delete_directory
+ * Delete a directory and all of its content.
+ */
+function delete_directory($dir)
+{
+ $dir_content = scandir($dir);
+ foreach($dir_content as $f)
+ {
+ if(($f == "..") or ($f == "."))
+ continue;
+
+ $f = "$dir/$f";
+
+ if(is_dir($f))
+ delete_directory($f);
+ else
+ unlink($f);
+ }
+ rmdir($dir);
+}
+
+/*
+ * Constant: SITE_BASE_PATH
+ * The Base path of this ratatoeskr site.
+ */
+define("SITE_BASE_PATH", dirname(dirname(dirname(__FILE__))));
+?>