diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..737e695
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/plugins/
+/*.db
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..774c733
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,4 @@
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^/?(.*)$ index.php?path=/$1 [L,QSA]
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..9552eb8
--- /dev/null
+++ b/index.php
@@ -0,0 +1,175 @@
+ $v)
+ if (!defined($k))
+ define($k, $v);
+
+ ob_start();
+ ob_clean();
+ header("Content-Type: text/html");
+
+ $strResource = $_SERVER["REQUEST_URI"];
+ if (strlen($strResource) > 0)
+ $strResource = substr($strResource, 1);
+
+ $strPluginsDirectory = "plugins";
+ $varPaths = array(".");
+
+ if (is_dir($strPluginsDirectory))
+ foreach (scandir($strPluginsDirectory) as $strPluginName)
+ {
+ if ($strPluginName == ".") continue;
+ if ($strPluginName == "..") continue;
+
+ $strPluginDirectory = "{$strPluginsDirectory}/{$strPluginName}";
+ if (is_dir($strPluginDirectory))
+ $varPaths[] = "{$strPluginDirectory}";
+ }
+
+ foreach ($varPaths as $strPath)
+ {
+ $strTargetFilePath = "{$strPath}/{$strResource}";
+
+ if (is_file($strTargetFilePath))
+ {
+ $varMimeTypes = array(
+ array("/\.css$/", "text/css"),
+ array("/\.js$/", "application/javascript"),
+ );
+
+ foreach ($varMimeTypes as $varMimeType)
+ if (preg_match($varMimeType[0], $strTargetFilePath))
+ header("Content-Type: {$varMimeType[1]}");
+
+ ob_clean();
+ echo file_get_contents($strTargetFilePath);
+ ob_end_flush();
+ exit;
+ }
+ }
+
+ $varCollection = array(
+ "lib" => array(),
+ "init.php" => array(),
+ "head.php" => array(),
+ "header.php" => array(),
+ "footer.php" => array(),
+ );
+
+ foreach ($varPaths as $strPath)
+ {
+ $x = null;
+
+ if (is_dir($x = "{$strPath}/lib"))
+ $varCollection["lib"][] = $x;
+
+ foreach (["init.php", "head.php", "header.php", "footer.php"] as $strScript)
+ if (is_file($x = "{$strPath}/{$strScript}"))
+ $varCollection[$strScript][] = $x;
+ }
+
+ foreach ($varCollection["lib"] as $strLibraryDirPath)
+ foreach (scandir($strLibraryDirPath) as $strLibraryFileName)
+ {
+ $strLibraryFilePath = "{$strLibraryDirPath}/{$strLibraryFileName}";
+ if (preg_match("/\.php$/", $strLibraryFilePath))
+ if (is_file($strLibraryFilePath))
+ require $strLibraryFilePath;
+ }
+
+ $strBodyTagAttributes = "";
+
+ function requireAll($strScriptName)
+ {
+ global $varCollection;
+ foreach ($varCollection[$strScriptName] as $strScript)
+ {
+ if (is_file($strScript))
+ {
+ error_log($strScript);
+ require $strScript;
+ }
+ }
+ }
+
+ // Require all init scripts found:
+ requireAll("init.php");
+?>
+
+
+
+
+
+
+
+
+
+
+ getMessage();
+ echo $strMessage;
+ echo "\n\n";
+
+ $strFile = $x->getFile();
+ $intLine = $x->getLine();
+
+ echo "#-1 {$strFile}({$intLine}): {$strMessage}\n";
+ echo $x->getTraceAsString();
+
+ ob_end_flush();
+ exit;
+ }
+ ?>
+
+
+
+
+
+
+
diff --git a/lib/Cookie.php b/lib/Cookie.php
new file mode 100644
index 0000000..0e8d7bc
--- /dev/null
+++ b/lib/Cookie.php
@@ -0,0 +1,43 @@
+ 0)
+ return $_COOKIE[$strKey];
+
+ return null;
+ }
+
+ public static function set($strKey, $strValue = null)
+ {
+ if ($strValue == null || strlen($strValue) < 1)
+ {
+ if (isset($_COOKIE[$strKey]))
+ {
+ unset($_COOKIE[$strKey]);
+ setcookie(
+ $strKey,
+ "",
+ time() - 3600,
+ "/");
+ }
+
+ return null;
+ }
+
+ $_COOKIE[$strKey] = $strValue;
+
+ setcookie(
+ $strKey,
+ $strValue,
+ time() + 60 * 60 * 24 * 30,
+ "/");
+
+ return $strValue;
+ }
+ }
+?>
diff --git a/lib/DatabaseConnection.php b/lib/DatabaseConnection.php
new file mode 100644
index 0000000..cfb21d2
--- /dev/null
+++ b/lib/DatabaseConnection.php
@@ -0,0 +1,150 @@
+strEngine = $strEngine;
+
+ switch ($strEngine)
+ {
+ case "sqlsrv":
+ $this->pdo = new PDO(
+ "sqlsrv:Server={$strHost};Database={$strDatabaseName}",
+ $strUsername,
+ $strPassword);
+ break;
+
+ case "mysql":
+ $this->pdo = new PDO(
+ "mysql:host={$strHost};dbname={$strDatabaseName}",
+ $strUsername,
+ $strPassword);
+ break;
+
+ case "sqlite":
+ $strFileName = $strHost;
+ if ($strFileName == null || strlen($strFileName) < 1)
+ $strFileName = ":memory:";
+
+ $this->pdo = new PDO("sqlite:{$strFileName}");
+ break;
+
+ default:
+ throw new Exception("Unknown database engine {$strEngine}.");
+ }
+
+ $this->pdo->setAttribute(
+ PDO::ATTR_ERRMODE,
+ PDO::ERRMODE_EXCEPTION);
+ }
+
+ public function query($input)
+ {
+ $varArgs = self::flatten(func_get_args());
+
+ if (count($varArgs) < 1)
+ throw new Exception("query takes at least one argument, the query!");
+
+ $strQuery = array_shift($varArgs);
+ $strQuery = file_exists("db/{$strQuery}")?
+ file_get_contents("db/{$strQuery}") :
+ $strQuery;
+
+ if ($this->strEngine == "sqlsrv")
+ $strQuery = "set nocount on; {$strQuery}";
+
+ $varStatement = $this->pdo->prepare($strQuery);
+
+ if (count($varArgs) > 0)
+ $varStatement->execute($varArgs);
+ else $varStatement->execute();
+
+ $varTemp = array();
+ $varOutput = array();
+
+ // Engines that do not support multiple rowsets:
+ if (in_array($this->strEngine, array("sqlite")))
+ {
+ while ($varRow = $varStatement->fetch())
+ {
+ $varNewRow = array();
+
+ foreach ($varRow as $k => $v)
+ if (!is_numeric($k))
+ $varNewRow[$k] = $v;
+
+ $varOutput[] = $varNewRow;
+ }
+
+ return $varOutput;
+ }
+
+ do
+ {
+ try { $varTemp[] = $varStatement->fetchAll(); }
+ catch (Exception $x) {}
+ }
+ while ($varStatement->nextRowset());
+
+ foreach ($varTemp as $i => $varSet)
+ foreach ($varSet as $j => $varRow)
+ {
+ $varNewRow = array();
+
+ foreach ($varRow as $k => $v)
+ if (!is_numeric($k))
+ $varNewRow[$k] = $v;
+
+ $varOutput[] = $varNewRow;
+ }
+
+ return $varOutput;
+ }
+
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+
+ // Used to combine a mixture of values and arrays of values as one flat array of values in the order they arrive in. For example: self::flatten("a", ["b", "c", ["d", "e", "f"], "g"], "h") should return an array of ["a", "b", "c", "d", "e", "f", "g", "h"].
+ private static function flatten(...$args)
+ {
+ if (is_null($args))
+ return array();
+
+ if (count($args) > 0)
+ {
+ $varOutput = array();
+ $varFirst = array_shift($args);
+
+ if (is_array($varFirst))
+ foreach ($varFirst as $varItem)
+ $varOutput = array_merge($varOutput, self::flatten($varItem));
+ else
+ array_push($varOutput, $varFirst);
+
+ if (count($args) > 0)
+ $varOutput = array_merge($varOutput, self::flatten($args));
+
+ return $varOutput;
+ }
+ else
+ return array();
+ }
+ }
+?>
diff --git a/lib/Request.php b/lib/Request.php
new file mode 100644
index 0000000..04c8d74
--- /dev/null
+++ b/lib/Request.php
@@ -0,0 +1,142 @@
+ 0)
+ $strParam = $_SERVER["PATH_INFO"];
+ }
+
+ $strPath = $strParam;
+ $strPath = preg_replace("/^\//", "", $strPath);
+ $strPath = preg_replace("/\/$/", "", $strPath);
+
+ // /test/action/a/b/c
+
+ $fncIsFile = function($strScriptPath)
+ {
+ $varSearchPaths = [];
+
+ if (is_dir("plugins"))
+ foreach (scandir("plugins") as $strPluginName)
+ {
+ if ($strPluginName == ".") continue;
+ if ($strPluginName == "..") continue;
+
+ $strNewPath = "plugins/{$strPluginName}";
+ if (is_dir($strNewPath))
+ $varSearchPaths[] = "{$strNewPath}";
+ }
+
+ // Try the framework's directory last:
+ $varSearchPaths[] = ".";
+
+ foreach ($varSearchPaths as $strSearchPath)
+ if (is_file(Request::$strScriptPath = "{$strSearchPath}/{$strScriptPath}"))
+ return true;
+
+ Request::$strScriptPath = null;
+ return false;
+ };
+
+ while (true)
+ {
+ if (strlen($strPath) < 1)
+ if ($fncIsFile("pages/index.php")) break;
+
+ if ($fncIsFile("pages/{$strPath}/index.php")) break;
+ if ($fncIsFile("pages/{$strPath}.php")) break;
+
+ if (preg_match("/\//", $strPath))
+ $strPath = preg_replace("/\/[^\/]{1,}$/", "", $strPath);
+ else $strPath = "";
+ }
+
+ $strArgs = str_replace("/{$strPath}", "", $strParam);
+ $varArgs = explode("/", $strArgs);
+
+ array_shift($varArgs);
+
+ Request::$varArgs = $varArgs;
+ Request::$strResourcePath = $strPath;
+ }
+
+ public static function getScript()
+ {
+ return Request::$strScriptPath;
+ }
+
+ public static function getArgs()
+ {
+ return Request::$varArgs;
+ }
+
+ // Safely returns a request argument by its index or null if it doesn't exist (without error)
+ public static function getArg($intIndex)
+ {
+ if (count(Request::$varArgs) >= $intIndex + 1)
+ return Request::$varArgs[$intIndex];
+ else return null;
+ }
+
+ // Safely returns the value of a request parameter or null if not defined (without error)
+ // e.g. 12345 from getParam("id") when resource is like /users/get?id=12345
+ public static function getParam($strKey)
+ {
+ if (is_array($_GET))
+ if (array_key_exists($strKey, $_GET))
+ if (strlen($_GET[$strKey]) > 0)
+ return $_GET[$strKey];
+
+ return null;
+ }
+
+ // Returns true if all of the arguments are in the POST request
+ // To be used like:
+ // if (Request::posts("param1", "param2")) {
+ public static function posts()
+ {
+ if (is_array($_POST))
+ if (func_num_args() > 0)
+ {
+ foreach (func_get_args() as $strKey)
+ if (!array_key_exists($strKey, $_POST))
+ return false;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // Returns the value of the posted field
+ public static function getPosted($strKey)
+ {
+ if (is_array($_POST))
+ if (array_key_exists($strKey, $_POST))
+ if (strlen($_POST[$strKey]) > 0)
+ return $_POST[$strKey];
+
+ return null;
+ }
+ }
+
+ // Call this no matter what to understand the request:
+ Request::process();
+?>
diff --git a/lib/Respond.php b/lib/Respond.php
new file mode 100644
index 0000000..2b47738
--- /dev/null
+++ b/lib/Respond.php
@@ -0,0 +1,39 @@
+
diff --git a/pages/index.php b/pages/index.php
new file mode 100644
index 0000000..9897287
--- /dev/null
+++ b/pages/index.php
@@ -0,0 +1,4 @@
+
diff --git a/php-webapp-framework.sh b/php-webapp-framework.sh
new file mode 100644
index 0000000..1eee669
--- /dev/null
+++ b/php-webapp-framework.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+php -S 0.0.0.0:8080 index.php