diff options
author | Malf Furious <m@lfurio.us> | 2015-12-18 23:18:33 -0500 |
---|---|---|
committer | Malf Furious <m@lfurio.us> | 2015-12-18 23:18:33 -0500 |
commit | 9068e6916ad68194fce2518ab5841af1c8949f3d (patch) | |
tree | 2f91e9e3be00c1492cdf4ce88d8e77a909c5c287 /app/class | |
parent | 2ebdbaa48f10d6a6f5a1b78f4ef2c5433e50c8cf (diff) | |
parent | b21251ef971d262dc414869fed83f52d0098bfe6 (diff) | |
download | scrott-9068e6916ad68194fce2518ab5841af1c8949f3d.tar.gz scrott-9068e6916ad68194fce2518ab5841af1c8949f3d.zip |
Merge branch 'framework' into dev
Diffstat (limited to 'app/class')
-rw-r--r-- | app/class/controller.class.php | 37 | ||||
-rw-r--r-- | app/class/database.iface.php | 13 | ||||
-rw-r--r-- | app/class/form.class.php | 186 | ||||
-rw-r--r-- | app/class/framework.class.php | 79 | ||||
-rw-r--r-- | app/class/model.class.php | 77 | ||||
-rw-r--r-- | app/class/mysql.class.php | 63 | ||||
-rw-r--r-- | app/class/object.class.php | 227 |
7 files changed, 682 insertions, 0 deletions
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); + } +} + +?> |