summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMalf Furious <m@lfurio.us>2015-12-18 23:18:33 -0500
committerMalf Furious <m@lfurio.us>2015-12-18 23:18:33 -0500
commit9068e6916ad68194fce2518ab5841af1c8949f3d (patch)
tree2f91e9e3be00c1492cdf4ce88d8e77a909c5c287
parent2ebdbaa48f10d6a6f5a1b78f4ef2c5433e50c8cf (diff)
parentb21251ef971d262dc414869fed83f52d0098bfe6 (diff)
downloadscrott-9068e6916ad68194fce2518ab5841af1c8949f3d.tar.gz
scrott-9068e6916ad68194fce2518ab5841af1c8949f3d.zip
Merge branch 'framework' into dev
Diffstat (limited to '')
-rw-r--r--app/assets/img/.gitkeep0
-rw-r--r--app/assets/img/amy.pngbin69547 -> 0 bytes
-rw-r--r--app/assets/img/eggman.pngbin102190 -> 0 bytes
-rw-r--r--app/assets/img/knuckles.pngbin77600 -> 0 bytes
-rw-r--r--app/assets/img/sonic.pngbin169301 -> 0 bytes
-rw-r--r--app/assets/img/tails.pngbin185686 -> 0 bytes
-rw-r--r--app/class/controller.class.php37
-rw-r--r--app/class/database.iface.php13
-rw-r--r--app/class/form.class.php186
-rw-r--r--app/class/framework.class.php79
-rw-r--r--app/class/model.class.php77
-rw-r--r--app/class/mysql.class.php63
-rw-r--r--app/class/object.class.php227
-rw-r--r--app/controller/root.control.php67
-rw-r--r--app/controller/sysconf.control.php43
-rw-r--r--app/index.php16
-rw-r--r--app/model/master.mod.php42
-rw-r--r--app/model/sysconf.mod.php60
-rw-r--r--app/view/master/foot.view.php10
-rw-r--r--app/view/master/head.view.php5
-rw-r--r--app/view/master/topp.view.php44
-rw-r--r--app/view/sysconf/default.view.php124
-rw-r--r--app/view/sysconf/group.modal.view.php31
-rw-r--r--app/view/sysconf/issue.modal.view.php34
-rw-r--r--app/view/sysconf/message.modal.view.php37
-rw-r--r--app/view/sysconf/pad.modal.view.php50
-rw-r--r--app/view/sysconf/stage.modal.view.php34
-rw-r--r--app/view/sysconf/user.modal.view.php30
-rw-r--r--examples/example.html (renamed from app/index.html)9
29 files changed, 1309 insertions, 9 deletions
diff --git a/app/assets/img/.gitkeep b/app/assets/img/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/assets/img/.gitkeep
diff --git a/app/assets/img/amy.png b/app/assets/img/amy.png
deleted file mode 100644
index 493fb13..0000000
--- a/app/assets/img/amy.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/img/eggman.png b/app/assets/img/eggman.png
deleted file mode 100644
index 6fea441..0000000
--- a/app/assets/img/eggman.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/img/knuckles.png b/app/assets/img/knuckles.png
deleted file mode 100644
index 7c7b7e2..0000000
--- a/app/assets/img/knuckles.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/img/sonic.png b/app/assets/img/sonic.png
deleted file mode 100644
index db3559f..0000000
--- a/app/assets/img/sonic.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/img/tails.png b/app/assets/img/tails.png
deleted file mode 100644
index 8639790..0000000
--- a/app/assets/img/tails.png
+++ /dev/null
Binary files differ
diff --git a/app/class/controller.class.php b/app/class/controller.class.php
new file mode 100644
index 0000000..fabd7e7
--- /dev/null
+++ b/app/class/controller.class.php
@@ -0,0 +1,37 @@
+<?php
+
+require_once "class/framework.class.php";
+
+/*
+ * Abstract controller -- Contains app security constraints and provides access to
+ * framework internals from concrete controllers
+ */
+abstract class Controller extends Framework
+{
+ /*
+ * Abstract function for concrete controller to handle the page request
+ */
+ abstract function handle($argv);
+
+ /*
+ * Security check
+ * Assert that the current connection to this server is secure. Redirects if not.
+ */
+ function sec_require_https()
+ {
+ if (!isset($_SERVER['HTTPS']))
+ $this->redirectTo("https://" . $_SERVER['SERVER_NAME'] . $this->ap());
+ }
+
+ /*
+ * Security check
+ * Assert that the current connection to this server is NOT secure. Redirects if not.
+ */
+ function sec_forbid_https()
+ {
+ if (isset($_SERVER['HTTPS']))
+ $this->redirectTo("http://" . $_SERVER['SERVER_NAME'] . $this->ap());
+ }
+}
+
+?>
diff --git a/app/class/database.iface.php b/app/class/database.iface.php
new file mode 100644
index 0000000..dcd64ba
--- /dev/null
+++ b/app/class/database.iface.php
@@ -0,0 +1,13 @@
+<?php
+
+/*
+ * Generic interface for the various database drivers Scrott may implement support for
+ */
+interface Database
+{
+ function close();
+ function query($query);
+ function esc($string);
+}
+
+?>
diff --git a/app/class/form.class.php b/app/class/form.class.php
new file mode 100644
index 0000000..808de27
--- /dev/null
+++ b/app/class/form.class.php
@@ -0,0 +1,186 @@
+<?php
+
+/*
+ * Model web-forms and simplify the process of accepting, validating, and sanitizing input
+ */
+class Form
+{
+ /*
+ * Constructor
+ */
+ function __construct()
+ {
+ $this->textFields = array();
+ $this->numbFields = array();
+ $this->enumFields = array();
+
+ $this->errorlist = array();
+ }
+
+ /*
+ * Log an error
+ */
+ function logError($str)
+ {
+ $this->errorlist[] = $str;
+ }
+
+ /*
+ * Add new text field to the form
+ */
+ function field_text($name, $deflt = null, $req = true)
+ {
+ if ($req !== true)
+ $req = false;
+
+ $this->textFields[] = array(
+ 'name' => $name,
+ 'deflt' => $deflt,
+ 'req' => $req
+ );
+ }
+
+ /*
+ * Add new numeric field to the form
+ */
+ function field_numeric($name, $min = null, $max = null, $deflt = null, $integer = true, $req = true)
+ {
+ if ($req !== true)
+ $req = false;
+
+ if ($integer !== true)
+ $integer = false;
+
+ $this->numbFields[] = array(
+ 'name' => $name,
+ 'min' => $min,
+ 'max' => $max,
+ 'deflt' => $deflt,
+ 'int' => $integer,
+ 'req' => $req
+ );
+ }
+
+ /*
+ * Add new enumeration field to the form
+ */
+ function field_enum($name, $values, $deflt = null, $req = true)
+ {
+ if ($req !== true)
+ $req = false;
+
+ $this->enumFields[] = array(
+ 'name' => $name,
+ 'vals' => $values,
+ 'deflt' => $deflt,
+ 'req' => $req
+ );
+ }
+
+ /*
+ * Add new boolean field to the form
+ */
+ function field_bool($name)
+ {
+ $this->field_enum($name, array("true", "false"), "false");
+ }
+
+ /*
+ * Populate the form with input data from web page
+ */
+ function populate($input)
+ {
+ /* detect duplicate names */
+ $names = array();
+ foreach ($this->textFields as $fld)
+ $names[] = $fld['name'];
+ foreach ($this->numbFields as $fld)
+ $names[] = $fld['name'];
+ foreach ($this->enumFields as $fld)
+ $names[] = $fld['name'];
+
+ if (count(array_unique($names)) != count($names))
+ {
+ $this->logError("Internal error: Duplicate field names defined in form");
+ return false;
+ }
+
+ /* init text fields */
+ foreach ($this->textFields as $fld)
+ {
+ if (isset($input[$fld['name']]) && $input[$fld['name']] != "")
+ $this->$fld['name'] = htmlEntities($input[$fld['name']], ENT_QUOTES);
+
+ else if (!is_null($fld['deflt']))
+ $this->$fld['name'] = $fld['deflt'];
+
+ else if ($fld['req'])
+ $this->logError($fld['name'] . " is required");
+ }
+
+ /* init numeric fields */
+ foreach ($this->numbFields as $fld)
+ {
+ if (isset($input[$fld['name']]) && $input[$fld['name']] != "")
+ {
+ if (!is_numeric($input[$fld['name']]))
+ {
+ $this->logError($fld['name'] . " must be numeric");
+ continue;
+ }
+
+ if ($fld['int'] && (floor($input[$fld['name']]) != $input[$fld['name']]))
+ {
+ $this->logError($fld['name'] . " must be an integer");
+ continue;
+ }
+
+ if (!is_null($fld['min']) && ($input[$fld['name']] < $fld['min']))
+ {
+ $this->logError($fld['name'] . " must be no less than " . $fld['min']);
+ continue;
+ }
+
+ if (!is_null($fld['max']) && ($input[$fld['name']] > $fld['max']))
+ {
+ $this->logError($fld['name'] . " must be no more than " . $fld['max']);
+ continue;
+ }
+
+ $this->$fld['name'] = $input[$fld['name']];
+ }
+
+ else if (!is_null($fld['deflt']))
+ $this->$fld['name'] = $fld['deflt'];
+
+ else if ($fld['req'])
+ $this->logError($fld['name'] . " is required");
+ }
+
+ /* init enum fields */
+ foreach ($this->enumFields as $fld)
+ {
+ if (isset($input[$fld['name']]) && $input[$fld['name']] != "")
+ {
+ if (array_search($input[$fld['name']], $fld['vals']) === false)
+ {
+ $this->logError($fld['name'] . " is not an appropriate value");
+ continue;
+ }
+
+ $this->$fld['name'] = $input[$fld['name']];
+ }
+
+ else if (!is_null($fld['deflt']))
+ $this->$fld['name'] = $fld['deflt'];
+
+ else if ($fld['req'])
+ $this->logError($fld['name'] . " is required");
+ }
+
+ /* return */
+ return count($this->errorlist) == 0;
+ }
+}
+
+?>
diff --git a/app/class/framework.class.php b/app/class/framework.class.php
new file mode 100644
index 0000000..eea6c25
--- /dev/null
+++ b/app/class/framework.class.php
@@ -0,0 +1,79 @@
+<?php
+
+/* Include the Scrott system-level configuration file if it exists */
+is_file("scrott.conf.php") &&
+ require_once "scrott.conf.php";
+
+require_once "class/mysql.class.php";
+
+/*
+ * Global functions / operations and access to contextual or session-based information
+ */
+abstract class Framework
+{
+ static $dbobj = null;
+
+ /*
+ * Check for the existence of Scrott's system-level config
+ */
+ function scrottConfExists()
+ {
+ global $_SCROTT;
+ return isset($_SCROTT['conf']);
+ }
+
+ /*
+ * Get the absolute path on this server for the root of this app
+ */
+ function ar()
+ {
+ return substr($_SERVER['PHP_SELF'], 0, -10); // 10 = length of "/index.php"
+ }
+
+ /*
+ * Get the absolute path to the current page
+ */
+ function ap()
+ {
+ return $this->ar() . $_REQUEST['path'];
+ }
+
+ /*
+ * Redirect to the given URL and die
+ */
+ function redirectTo($url)
+ {
+ header("Location: " . $url);
+ exit;
+ }
+
+ /*
+ * Get or create the app's database connection object (this is a singleton object and dependent on system-level config)
+ */
+ function getDbConnection()
+ {
+ global $_SCROTT;
+
+ if (self::$dbobj != null)
+ return self::$dbobj;
+
+ switch ($_SCROTT['dbEngine'])
+ {
+ case "mysql":
+ $host = $_SCROTT['dbAddress'];
+ $username = $_SCROTT['dbUser'];
+ $password = $_SCROTT['dbPass'];
+ $dbName = $_SCROTT['dbName'];
+ self::$dbobj = new Mysql($host, $username, $password, $dbName);
+ break;
+
+ default:
+ throw new Exception("Problem with Scrott Configuration. Invalid database engine specified.");
+ break;
+ }
+
+ return self::$dbobj;
+ }
+}
+
+?>
diff --git a/app/class/model.class.php b/app/class/model.class.php
new file mode 100644
index 0000000..85bcf54
--- /dev/null
+++ b/app/class/model.class.php
@@ -0,0 +1,77 @@
+<?php
+
+require_once "class/framework.class.php";
+
+/*
+ * Abstract model class - defines logic common to all app MVC models
+ */
+abstract class Model extends Framework
+{
+ /*
+ * Constructor
+ */
+ function __construct()
+ {
+ $this->errorlist = array();
+ $this->warninglist = array();
+ $this->noticelist = array();
+ }
+
+ /*
+ * Check for error
+ */
+ function isError()
+ {
+ return count($this->errorlist) > 0;
+ }
+
+ /*
+ * Check for warning
+ */
+ function isWarning()
+ {
+ return count($this->warninglist) > 0;
+ }
+
+ /*
+ * Check for notice
+ */
+ function isNotice()
+ {
+ return count($this->noticelist) > 0;
+ }
+
+ /*
+ * Log an error
+ */
+ function logError($str)
+ {
+ $this->errorlist[] = $str;
+ }
+
+ /*
+ * Log a warning
+ */
+ function logWarning($str)
+ {
+ $this->warninglist[] = $str;
+ }
+
+ /*
+ * Log a notice
+ */
+ function logNotice($str)
+ {
+ $this->noticelist[] = $str;
+ }
+
+ /*
+ * Log errors from a Form
+ */
+ function logFormErrors($obj)
+ {
+ $this->errorlist = array_merge($this->errorlist, $obj->errorlist);
+ }
+}
+
+?>
diff --git a/app/class/mysql.class.php b/app/class/mysql.class.php
new file mode 100644
index 0000000..317468c
--- /dev/null
+++ b/app/class/mysql.class.php
@@ -0,0 +1,63 @@
+<?php
+
+require_once "class/database.iface.php";
+
+/*
+ * MySQL support for Scrott
+ */
+class Mysql implements Database
+{
+ /*
+ * Constructor
+ */
+ function __construct($host, $username, $password, $dbName)
+ {
+ $this->db = new mysqli($host, $username, $password, $dbName);
+
+ if ($this->db->connect_error)
+ throw new Exception("Can not connect to Mysql database. Please check your Scrott configuration.");
+ }
+
+ /*
+ * Destructor
+ */
+ function __destruct()
+ {
+ $this->close();
+ }
+
+ /*
+ * Close connection to DB
+ */
+ function close()
+ {
+ $this->db->close();
+ }
+
+ /*
+ * Make a query of the database. Return data as an array of arrays
+ */
+ function query($query)
+ {
+ $arr = array();
+ $res = $this->db->query($query);
+
+ if ($res === true || $res === false)
+ return $arr;
+
+ while ($r = $res->fetch_assoc())
+ $arr[] = $r;
+
+ return $arr;
+ }
+
+ /*
+ * Escape a string for use in a query
+ */
+ function esc($string)
+ {
+ return $this->db->real_escape_string($string);
+ }
+}
+
+?>
diff --git a/app/class/object.class.php b/app/class/object.class.php
new file mode 100644
index 0000000..bcd8dfa
--- /dev/null
+++ b/app/class/object.class.php
@@ -0,0 +1,227 @@
+<?php
+
+require_once "class/framework.class.php";
+
+/*
+ * Base class for Scrott database objects
+ */
+abstract class Object extends Framework
+{
+ /*
+ * Constructor
+ */
+ function __construct($childTable = "object", $childCols = null)
+ {
+ $this->db = $this->getDbConnection();
+
+ $this->table = "object";
+ $this->cols = array(
+ "guid",
+ "perms",
+ "owner",
+ "parent",
+ "name",
+ "timeCreated",
+ "timeUpdated",
+ "type"
+ );
+
+ $this->childTable = $this->db->esc($childTable);
+ $this->childCols = array();
+
+ if (is_array($childCols))
+ {
+ foreach ($childCols as $col)
+ $this->childCols[] = $this->db->esc($col);
+ }
+ }
+
+ /*
+ * Populate this object with data from the DB with a given GUID
+ */
+ function loadObj($guid = null)
+ {
+ if (is_null($guid))
+ return;
+
+ if (!$this->isGUID($guid))
+ return;
+
+ $escdGuid = $this->db->esc($guid);
+
+ /* Base fields */
+ $query = "SELECT * FROM `" . $this->table . "` WHERE `guid` = '" . $escdGuid . "'";
+ $result = $this->db->query($query)[0];
+
+ foreach ($this->cols as $col)
+ {
+ if (isset($result[$col]))
+ $this->$col = $result[$col];
+ }
+
+ /* Child Table fields */
+ $query = "SELECT * FROM `" . $this->childTable . "` WHERE `guid` = '" . $escdGuid . "'";
+ $result = $this->db->query($query)[0];
+
+ foreach ($this->childCols as $col)
+ {
+ if (isset($result[$col]))
+ $this->$col = $result[$col];
+ }
+ }
+
+ /*
+ * Write this object to the database
+ */
+ function saveObj()
+ {
+ if (isset($this->guid))
+ {
+ /* Update Base */
+ $updateStr = "";
+
+ foreach ($this->cols as $col)
+ {
+ if (!isset($this->$col))
+ continue;
+
+ $updateStr .= "`" . $col . "` = '" . $this->db->esc($this->$col) . "', ";
+ }
+
+ if (strlen($updateStr) > 0)
+ {
+ $updateStr = substr($updateStr, 0, -2); // remove ", " from the end
+ $query = "UPDATE `" . $this->table . "` SET " . $updateStr . " WHERE `guid` = '" . $this->db->esc($this->guid) . "'";
+ $this->db->query($query);
+ }
+
+ /* Update Child */
+ $updateStr = "";
+
+ foreach ($this->childCols as $col)
+ {
+ if (!isset($this->$col))
+ continue;
+
+ $updateStr .= "`" . $col . "` = '" . $this->db->esc($this->$col) . "', ";
+ }
+
+ if (strlen($updateStr) > 0)
+ {
+ $updateStr = substr($updateStr, 0, -2); // remove ", " from the end
+ $query = "UPDATE `" . $this->childTable . "` SET " . $updateStr . " WHERE `guid` = '" . $this->db->esc($this->guid) . "'";
+ $this->db->query($query);
+ }
+ }
+
+ else
+ {
+ $this->guid = $this->getNewGUID();
+
+ /* Insert Base */
+ $colsStr = "";
+ $valsStr = "";
+
+ foreach ($this->cols as $col)
+ {
+ if (!isset($this->$col))
+ continue;
+
+ $colsStr .= "`" . $col . "`, ";
+ $valsStr .= "'" . $this->db->esc($this->$col) . "', ";
+ }
+
+ if (strlen($colsStr) > 0)
+ {
+ $colsStr = substr($colsStr, 0, -2); // remove ", "
+ $valsStr = substr($valsStr, 0, -2);
+ $query = "INSERT INTO `" . $this->table . "` (" . $colsStr . ") VALUES (" . $valsStr . ")";
+ $this->db->query($query);
+ }
+
+ /* Insert Child */
+ $colsStr = "";
+ $valsStr = "";
+
+ foreach ($this->childCols as $col)
+ {
+ if (!isset($this->$col))
+ continue;
+
+ $colsStr .= "`" . $col . "`, ";
+ $valsStr .= "'" . $this->db->esc($this->$col) . "', ";
+ }
+
+ if (strlen($colsStr) > 0)
+ {
+ $colsStr = substr($colsStr, 0, -2); // remove ", "
+ $valsStr = substr($valsStr, 0, -2);
+ $query = "INSERT INTO `" . $this->childTable . "` (" . $colsStr . ") VALUES (" . $valsStr . ")";
+ $this->db->query($query);
+ }
+ }
+ }
+
+ /*
+ * Remove this object from the database
+ */
+ function delObj()
+ {
+ if (!isset($this->guid))
+ return;
+
+ /* Delete Base */
+ $query = "DELETE FROM `" . $this->table . "` WHERE `guid` = '" . $this->db->esc($this->guid) . "'";
+ $this->db->query($query);
+
+ /* Delete Child */
+ $query = "DELETE FROM `" . $this->childTable . "` WHERE `guid` = '" . $this->db->esc($this->guid) . "'";
+ $this->db->query($query);
+ }
+
+ /*
+ * Check whether given GUID exists
+ */
+ function isGUID($guid)
+ {
+ $query = "SELECT `guid` FROM `object` WHERE `guid` = '" . $this->db->esc($guid) . "'";
+ $result = $this->db->query($query);
+
+ if (count($result) > 0)
+ return true;
+
+ return false;
+ }
+
+ /*
+ * Get a new, unique GUID for a new system object
+ */
+ function getNewGUID()
+ {
+ do
+ {
+ $sha = hash("sha256", rand());
+ $guid = substr($sha, 0, 8);
+ }
+ while ($this->isGUID($guid));
+
+ return $guid;
+ }
+}
+
+/*
+ * Concrete Database Object which can be used in a polymorphic way
+ */
+class DBObject extends Object
+{
+ /*
+ * Constructor
+ */
+ function __construct($guid = null)
+ {
+ parent::__construct();
+ $this->loadObj($guid);
+ }
+}
+
+?>
diff --git a/app/controller/root.control.php b/app/controller/root.control.php
new file mode 100644
index 0000000..437cae1
--- /dev/null
+++ b/app/controller/root.control.php
@@ -0,0 +1,67 @@
+<?php
+
+require_once "class/controller.class.php";
+require_once "controller/sysconf.control.php";
+
+/*
+ * Root-level controller for Scrott app. This object will delegate the page request to the
+ * appropriate controller or handle it with an error message page.
+ */
+class Root extends Controller
+{
+ /*
+ * Controller implementation
+ */
+ function handle($argv)
+ {
+ /* TODO -- Catch app exceptions here and display a special view to communicate them to user */
+ /* TODO -- Authentication (login / logout / register) MVC */
+
+ $argv = $this->normalizeArgv($argv);
+
+ /* First, make sure the system configuration file has been included */
+ if (!$this->scrottConfExists())
+ {
+ $ctrl = new Sysconf();
+ $ctrl->handle($argv);
+ }
+
+ /* TODO */
+ else
+ echo "Configuration is present!";
+ }
+
+ /*
+ * Get a useful path string by normalizeing the $argv array received from the main function.
+ * This will remove directory names that appear in the $this->ar() string and the initial
+ * and trailing (if present) empty strings
+ */
+ function normalizeArgv($argv)
+ {
+ $argv = array_values(array_filter($argv));
+ $ar = array_values(array_filter(explode("/", $this->ar())));
+ $i = 0;
+ $trunc = true;
+
+ if (count($ar) == 0)
+ return $argv;
+
+ foreach ($ar as $elem)
+ {
+ if ($elem != $argv[$i])
+ {
+ $trunc = false;
+ break;
+ }
+
+ $i++;
+ }
+
+ if (!$trunc)
+ return $argv;
+
+ return array_values(array_slice($argv, count($ar)));
+ }
+}
+
+?>
diff --git a/app/controller/sysconf.control.php b/app/controller/sysconf.control.php
new file mode 100644
index 0000000..a7db1e8
--- /dev/null
+++ b/app/controller/sysconf.control.php
@@ -0,0 +1,43 @@
+<?php
+
+require_once "class/controller.class.php";
+require_once "model/sysconf.mod.php";
+
+/*
+ * SysConf is the interface for creating an install's "scrott.conf.php" file if it doesn't exist
+ */
+class Sysconf extends Controller
+{
+ /*
+ * Controller implementation
+ */
+ function handle($argv)
+ {
+ $mod = new SysconfModel();
+
+ switch ($_REQUEST['input']['action'])
+ {
+ case "save":
+ $this->action_save($mod);
+ break;
+
+ default:
+ $this->action_default($mod);
+ break;
+ }
+ }
+
+ function action_default($mod)
+ {
+ $mod->deflt();
+ include "view/sysconf/default.view.php";
+ }
+
+ function action_save($mod)
+ {
+ $mod->save($_REQUEST['input']);
+ $this->action_default($mod);
+ }
+}
+
+?>
diff --git a/app/index.php b/app/index.php
new file mode 100644
index 0000000..37258d7
--- /dev/null
+++ b/app/index.php
@@ -0,0 +1,16 @@
+<?php
+
+require_once "controller/root.control.php";
+
+/*
+ * Entry-point to app. Grab the requested path and pass it to the root app controller.
+ */
+function main($argv)
+{
+ $app = new Root();
+ $app->handle($argv);
+}
+
+main(explode("/", $_REQUEST['path'])); // Start rendering web page for the requested path.
+
+?>
diff --git a/app/model/master.mod.php b/app/model/master.mod.php
new file mode 100644
index 0000000..aebcaaa
--- /dev/null
+++ b/app/model/master.mod.php
@@ -0,0 +1,42 @@
+<?php
+
+require_once "class/model.class.php";
+
+class MasterModel extends Model
+{
+ /*
+ * Get the appropriate alert class to use when showing the notice modal
+ */
+ function getNoticeModalAlertClass()
+ {
+ if ($this->isError())
+ return "alert-danger";
+
+ if ($this->isWarning())
+ return "alert-warning";
+
+ if ($this->isNotice())
+ return "alert-info";
+
+ return "";
+ }
+
+ /*
+ * Get the appropriate glyphicon to use when showing the notice modal
+ */
+ function getNoticeModalGlyphicon()
+ {
+ if ($this->isError())
+ return "glyphicon glyphicon-remove-sign";
+
+ if ($this->isWarning())
+ return "glyphicon glyphicon-exclamation-sign";
+
+ if ($this->isNotice())
+ return "glyphicon glyphicon-info-sign";
+
+ return "";
+ }
+}
+
+?>
diff --git a/app/model/sysconf.mod.php b/app/model/sysconf.mod.php
new file mode 100644
index 0000000..30ebd58
--- /dev/null
+++ b/app/model/sysconf.mod.php
@@ -0,0 +1,60 @@
+<?php
+
+require_once "model/master.mod.php";
+require_once "class/form.class.php";
+
+class SysconfModel extends MasterModel
+{
+ var $CONF_FILE = "scrott.conf.php";
+
+ /*
+ * Default action
+ */
+ function deflt()
+ {
+ }
+
+ /*
+ * Save the submitted data to the config file
+ */
+ function save($input)
+ {
+ $form = new Form();
+ $form->field_text("dbAddress");
+ $form->field_text("dbName");
+ $form->field_text("dbUser");
+ $form->field_text("dbPass", null, false);
+ $form->field_enum("settSSL", array("force", "neither", "forbid"));
+
+ if (!$form->populate($input))
+ {
+ $this->logFormErrors($form);
+ return;
+ }
+
+ /* TODO -- test database connection before proceeding */
+
+ $f = fopen($this->CONF_FILE, "w");
+
+ if (!$f)
+ {
+ $this->logError("Can not create configuration file");
+ return;
+ }
+
+ fwrite($f, "<?php\n");
+ fwrite($f, "\$_SCROTT['conf'] = 'conf';\n");
+ fwrite($f, "\$_SCROTT['dbEngine'] = 'mysql';\n");
+ fwrite($f, "\$_SCROTT['dbAddress'] = '" . $form->dbAddress . "';\n");
+ fwrite($f, "\$_SCROTT['dbName'] = '" . $form->dbName . "';\n");
+ fwrite($f, "\$_SCROTT['dbUser'] = '" . $form->dbUser . "';\n");
+ fwrite($f, "\$_SCROTT['dbPass'] = '" . $form->dbPass . "';\n");
+ fwrite($f, "\$_SCROTT['settSSL'] = '" . $form->settSSL . "';\n");
+ fwrite($f, "?>\n");
+
+ fclose($f);
+ $this->redirectTo($this->ar());
+ }
+}
+
+?>
diff --git a/app/view/master/foot.view.php b/app/view/master/foot.view.php
new file mode 100644
index 0000000..6220f89
--- /dev/null
+++ b/app/view/master/foot.view.php
@@ -0,0 +1,10 @@
+<script type="text/javascript" src="<?=$mod->ar()?>/assets/js/jquery.min.js"></script>
+<script type="text/javascript" src="<?=$mod->ar()?>/assets/js/bootstrap.min.js"></script>
+
+<?php if ($mod->isError() || $mod->isWarning() || $mod->isNotice()) { ?>
+ <script type="text/javascript">
+ $(window).load(function() {
+ $("#noticeModal").modal("show");
+ });
+ </script>
+<?php } ?>
diff --git a/app/view/master/head.view.php b/app/view/master/head.view.php
new file mode 100644
index 0000000..54294b8
--- /dev/null
+++ b/app/view/master/head.view.php
@@ -0,0 +1,5 @@
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+
+<link rel="stylesheet" type="text/css" href="<?=$mod->ar()?>/assets/css/bootstrap.min.css" />
diff --git a/app/view/master/topp.view.php b/app/view/master/topp.view.php
new file mode 100644
index 0000000..fe430ff
--- /dev/null
+++ b/app/view/master/topp.view.php
@@ -0,0 +1,44 @@
+<?php if ($mod->isError() || $mod->isWarning() || $mod->isNotice()) { ?>
+ <div id="noticeModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+
+ <div class="modal-body alert <?=$mod->getNoticeModalAlertClass()?>" style="margin: 0;">
+ <h1 class="text-center"><span class="<?=$mod->getNoticeModalGlyphicon()?>"></span></h1>
+ <h5 class="text-center">Something Happened</h5>
+
+ <?php if ($mod->isError()) { ?>
+ <p>
+ <?php foreach ($mod->errorlist as $err) { ?>
+ <span class="label label-danger">Error</span> <?=$err?><br />
+ <?php } ?>
+ </p>
+ <?php } ?>
+
+ <?php if ($mod->isWarning()) { ?>
+ <p>
+ <?php foreach ($mod->warninglist as $warn) { ?>
+ <span class="label label-warning">Warning</span> <?=$warn?><br />
+ <?php } ?>
+ </p>
+ <?php } ?>
+
+ <?php if ($mod->isNotice()) { ?>
+ <p>
+ <?php foreach ($mod->noticelist as $note) { ?>
+ <span class="label label-info">Notice</span> <?=$note?><br />
+ <?php } ?>
+ </p>
+ <?php } ?>
+
+ <div class="text-center">
+ <button type="button" class="btn btn-default" data-dismiss="modal">
+ <span class="glyphicon glyphicon-ok"></span> Got it
+ </button>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ </div>
+<?php } ?>
diff --git a/app/view/sysconf/default.view.php b/app/view/sysconf/default.view.php
new file mode 100644
index 0000000..1ecedd8
--- /dev/null
+++ b/app/view/sysconf/default.view.php
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+
+<html lang="en">
+ <head>
+ <?php include "view/master/head.view.php"; ?>
+ <title>Scrott - System-level configuration missing</title>
+
+ <style type="text/css">
+ body { padding-top: 50px; }
+ a { color: inherit; }
+ </style>
+ </head>
+
+ <body>
+ <?php include "view/master/topp.view.php"; ?>
+
+ <?php include "view/sysconf/user.modal.view.php"; ?>
+ <?php include "view/sysconf/group.modal.view.php"; ?>
+ <?php include "view/sysconf/pad.modal.view.php"; ?>
+ <?php include "view/sysconf/stage.modal.view.php"; ?>
+ <?php include "view/sysconf/issue.modal.view.php"; ?>
+ <?php include "view/sysconf/message.modal.view.php"; ?>
+
+ <div class="container">
+ <div class="jumbotron">
+ <h2 class="text-center">
+ <a href="#" data-toggle="modal" data-target="#userModal"><span class="glyphicon glyphicon-user"></span></a>&nbsp;&nbsp;&nbsp;
+ <a href="#" data-toggle="modal" data-target="#groupModal"><span class="glyphicon glyphicon-th"></span></a>&nbsp;&nbsp;&nbsp;
+ <a href="#" data-toggle="modal" data-target="#padModal"><span class="glyphicon glyphicon-edit"></span></a>&nbsp;&nbsp;&nbsp;
+ <a href="#" data-toggle="modal" data-target="#stageModal"><span class="glyphicon glyphicon-tasks"></span></a>&nbsp;&nbsp;&nbsp;
+ <a href="#" data-toggle="modal" data-target="#issueModal"><span class="glyphicon glyphicon-inbox"></span></a>&nbsp;&nbsp;&nbsp;
+ <a href="#" data-toggle="modal" data-target="#messageModal"><span class="glyphicon glyphicon-envelope"></span></a>
+ </h2>
+
+ <h1 class="text-center">Welcome to Scrott!</h1>
+ <hr />
+
+ <p class="text-center">You're seeing this page because the file "scrott.conf.php" is missing.<br />Please fill out the form below to create a config automatically!</p>
+ <p class="text-center">Click the icons above to learn more about core Scrott features and constructs!</p>
+ <hr />
+
+ <p class="text-center">This form will initialize Scrott's system-level configuration.<br />These are things the app needs before it can begin functioning at all!</p>
+ <h5 class="text-center">It is presumed that you are the administrator for this Scrott install.<br />There is a security risk involved with exposing this page to the public!</h5>
+
+ <div class="row">
+ <div class="col-md-2"></div>
+
+ <div class="col-md-8">
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <form method="post" action="<?=$mod->ap()?>">
+ <input type="hidden" name="input[action]" value="save" />
+ <legend>Database Connection</legend>
+ <div class="form-group">
+ <label for="inputDBEngine">Engine</label>
+ <input type="text" id="inputDBEngine" class="form-control" value="Mysql" disabled />
+ </div>
+
+ <div class="form-group">
+ <label for="inputDBAddress">Server Address</label>
+ <input type="text" name="input[dbAddress]" id="inputDBAddress" class="form-control" placeholder="localhost" />
+ </div>
+
+ <div class="form-group">
+ <label for="inputDBName">Database Name</label>
+ <input type="text" name="input[dbName]" id="inputDBName" class="form-control" placeholder="db_scrott" />
+ </div>
+
+ <div class="form-group">
+ <label for="inputDBUser">Username</label>
+ <input type="text" name="input[dbUser]" id="inputDBUser" class="form-control" placeholder="root" />
+ </div>
+
+ <div class="form-group">
+ <label for="inputDBPass">Password</label>
+ <input type="password" name="input[dbPass]" id="inputDBPass" class="form-control" />
+ </div>
+
+ <legend>Application Installation</legend>
+ <div class="form-group">
+ <label for="inputAppPath">Install Location</label>
+ <input type="text" id="inputAppPath" class="form-control" value="<?=$mod->ar()?>/" disabled />
+ <h6 class="pull-right">Detected from location of files in web document root</h6>
+ </div>
+
+ <legend>Settings</legend>
+ <div class="form-group">
+ <label>HTTP(S)</label>
+ <div class="radio">
+ <label>
+ <input type="radio" name="input[settSSL]" value="force" />
+ Always Force SSL
+ </label>
+ </div>
+
+ <div class="radio">
+ <label>
+ <input type="radio" name="input[settSSL]" value="neither" checked />
+ Neither (Application can override)
+ </label>
+ </div>
+
+ <div class="radio">
+ <label>
+ <input type="radio" name="input[settSSL]" value="forbid" />
+ Always Forbid SSL
+ </label>
+ </div>
+ </div>
+
+ <button type="submit" class="btn btn-primary pull-right">Use these settings</button>
+ </form>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-2"></div>
+ </div>
+ </div>
+ </div>
+
+ <?php include "view/master/foot.view.php"; ?>
+ </body>
+</html>
diff --git a/app/view/sysconf/group.modal.view.php b/app/view/sysconf/group.modal.view.php
new file mode 100644
index 0000000..cd05642
--- /dev/null
+++ b/app/view/sysconf/group.modal.view.php
@@ -0,0 +1,31 @@
+<div id="groupModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">User Groups</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>Groups make sharing objects and collaborating easier. Instead of creating your
+ own pad and adding each of your team members to it individualy, create a
+ group-owned pad. With the appropriate permissions set, all group members will be
+ able to use the pad. This also solves issues where a shared pad is owned by a
+ single user whose account gets closed. Instead pads and other objects are safe as
+ long as the group exists. Groups are owned and managed by a single user, who must
+ stick around or transfer ownership for the group to continue functioning.</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/view/sysconf/issue.modal.view.php b/app/view/sysconf/issue.modal.view.php
new file mode 100644
index 0000000..a77c1e3
--- /dev/null
+++ b/app/view/sysconf/issue.modal.view.php
@@ -0,0 +1,34 @@
+<div id="issueModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">Issues</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>Issues represent work. This work could be anything from a software project's bug
+ needing fixed or a new feature to implement to a record of needing to order more
+ celing panels for the new home office build. Work is done by someone, for someone,
+ and this is reflected in Scrott's internal data structures. Every issues has a
+ distinct owner (someone requesting work be done) and assignee (someone who needs to
+ do the work). If others are interested in the work or have valuable input on the
+ work, they may be included (cc'd) on the issue to receive updates on it or to be able
+ to post their own updates. Scrott tracks issues using pads which are representative
+ of a high-level project or some similar effort. So related issues are tracked
+ together.</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/view/sysconf/message.modal.view.php b/app/view/sysconf/message.modal.view.php
new file mode 100644
index 0000000..715bb06
--- /dev/null
+++ b/app/view/sysconf/message.modal.view.php
@@ -0,0 +1,37 @@
+<div id="messageModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">Messages</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>Although Scrott is formost an issue-tracking tool, it can also be a useful
+ communication tool for team and client relationships. Scrott includes a lightweight
+ messaging system that is aparent:</p>
+ <ul>
+ <li>In pads: where users can start new general discussion threads and post
+ replys</li>
+ <li>In issues: where users can post comments or updates on work status</li>
+ <li>In general: where users can send other users direct private messages</li>
+ </ul>
+ <p>With email integration, Scrott also allows communication with non-scrott user
+ clients or owners of issues.</p>
+ <p>Scrott's built-in logging system utilizes this messaging framework and is used
+ by object owners and site administrators to monitor usage and activity on Scrott</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/view/sysconf/pad.modal.view.php b/app/view/sysconf/pad.modal.view.php
new file mode 100644
index 0000000..88c62e7
--- /dev/null
+++ b/app/view/sysconf/pad.modal.view.php
@@ -0,0 +1,50 @@
+<div id="padModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">Pads</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>Use pads to track tasks, issues, bugs, client tickets, etc. Within Scrott, these
+ are collectively referred to as 'issues'. Use a pad to track your progress on a
+ project's completion, to manage user issues for a help-desk, to keep a shopping list,
+ bucket list, lists to track your progress on a television series (or series you'd
+ like to start), or books to read, or anything inbetween.</p>
+
+ <p>In addition to issue tracking, pads enable users to communicate with one another.
+ Each pad consists of a lightweight message board (forum) and users can also post
+ messages directly to issues if they are relevant. With email integration, even
+ non-scrott users may be clued in to the progress of their open issues. Pads even
+ allow for message threads to be converted (elevated) to a new open issue if need be.
+ (for example: Imagine you started a thread to discuss a potential new website design.
+ Once your team had arrived at something the majority liked, the thread can be elevated
+ to a new issue (preserving all messaging history), assigned to one of the team
+ members, and tracked just like all of your other issues.)</p>
+
+ <p>If you would like to allow the public to open issues in your pad (ie: if your pad
+ is allowing bug reports/feature requests from users of your project or if your pad
+ is the issue tracker for a help-desk), you can enable an issue portal for your pad.
+ A portal is a page accessible to the public where anonymous internet users (with a
+ verified email address) may submit new issues to your pad. Their email address
+ becomes the issue owner so the requester is still able to receive updates on the
+ issue as well as respond with their own messages.</p>
+
+ <p>Pads can be used by a single user or many; and pads can be owned by a user or a
+ user group</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/view/sysconf/stage.modal.view.php b/app/view/sysconf/stage.modal.view.php
new file mode 100644
index 0000000..4eaab43
--- /dev/null
+++ b/app/view/sysconf/stage.modal.view.php
@@ -0,0 +1,34 @@
+<div id="stageModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">Pad Stages</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>The issue-tracking aspect of pads is divided up using pad stages. A stage is
+ representative of the level of completion or high-level status of an issue. For
+ example, a pad may have an initial stage named "To Do", an intermediate stage named
+ "In Progress", and another stage "In Review". Stages form a sequence in practice.
+ When the appropriate amount of work has been done on an issue, it is advanced to the
+ next stage in the pipeline. This allows other pad members and the issue's owner to
+ at a glance get an idea of the high-level progress of the issue.</p>
+
+ <p>Once an issue is closed, it is removed from this stage pipeline, but retained in
+ the pad itself for archival purposes and for generating reports.</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/view/sysconf/user.modal.view.php b/app/view/sysconf/user.modal.view.php
new file mode 100644
index 0000000..b3650b0
--- /dev/null
+++ b/app/view/sysconf/user.modal.view.php
@@ -0,0 +1,30 @@
+<div id="userModal" class="modal fade" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+
+ <button type="button" class="close" data-dismiss="modal">
+ <span aria-hidden="true">&times;</span>
+ </button>
+
+ <h4 class="modal-title">User Accounts</h4>
+
+ </div>
+
+ <div class="modal-body">
+ <p>Scrott implements a very flexible permissions and access-control system. It all
+ starts with users' accounts. Users can create and maintain objects and share them
+ with other users if they choose. This is done with a mechanism similar to unix
+ file permission modes. Scrott objects form a hierarchy, so permissions from 'higher'
+ objects will cascade down. This means that a non-trusted user may control an issue
+ in your pad, but have no access to the pad itself.</p>
+ </div>
+
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" data-dismiss="modal">
+ Close
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/index.html b/examples/example.html
index 15b8602..44c91f2 100644
--- a/app/index.html
+++ b/examples/example.html
@@ -2,14 +2,8 @@
<html lang="en">
<head>
- <meta charset="utf-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
-
<title>Scrott - Save the World</title>
- <link rel="stylesheet" type="text/css" href="assets/css/bootstrap.min.css" />
-
<style type="text/css">
body
{
@@ -223,8 +217,5 @@ body
</div>
</div>
- <!-- JS -->
- <script type="text/javascript" src="assets/js/jquery.min.js"></script>
- <script type="text/javascript" src="assets/js/bootstrap.min.js"></script>
</body>
</html>