From 4c64f99cedb71cff25d4fbce9fa9a0bf2ef07daf Mon Sep 17 00:00:00 2001 From: mgfcf Date: Sun, 8 Nov 2020 15:21:51 +0100 Subject: [PATCH] A LOT of improvements --- .gitignore | 8 +- juggl-server/{ => api}/config/config.php | 20 +- .../{ => api}/config/config.php.sample | 14 +- juggl-server/{ => api}/endRecord.php | 55 +- juggl-server/api/getProjects.php | 29 + juggl-server/{ => api}/getRecord.php | 67 +- .../{ => api}/services/apiBranch.inc.php | 74 +-- .../{ => api}/services/authenticator.inc.php | 38 +- .../{ => api}/services/basicEnum.inc.php | 64 +- .../{ => api}/services/dbOperations.inc.php | 582 +++++++++--------- .../{ => api}/services/jsonBuilder.inc.php | 116 ++-- .../{ => api}/services/jugglDbApi.inc.php | 373 ++++++----- .../{ => api}/services/paramCleaner.inc.php | 98 +-- .../{ => api}/services/requestTypes.inc.php | 36 +- .../{ => api}/services/responses.inc.php | 48 +- juggl-server/{ => api}/startRecord.php | 99 ++- juggl-server/assets/logo.ico | Bin 0 -> 105109 bytes juggl-server/assets/logo_title.png | Bin 0 -> 68938 bytes juggl-server/css/style.css | 91 +++ juggl-server/index.html | 61 ++ juggl-server/js/api.js | 32 + juggl-server/js/auth.js | 58 ++ juggl-server/js/helper.js | 59 ++ juggl-server/js/umbrella.min.js | 3 + juggl-server/js/visibility.js | 55 ++ juggl_documentation.txt | 88 +-- 26 files changed, 1307 insertions(+), 861 deletions(-) rename juggl-server/{ => api}/config/config.php (59%) rename juggl-server/{ => api}/config/config.php.sample (94%) rename juggl-server/{ => api}/endRecord.php (94%) create mode 100644 juggl-server/api/getProjects.php rename juggl-server/{ => api}/getRecord.php (95%) rename juggl-server/{ => api}/services/apiBranch.inc.php (96%) rename juggl-server/{ => api}/services/authenticator.inc.php (96%) rename juggl-server/{ => api}/services/basicEnum.inc.php (97%) rename juggl-server/{ => api}/services/dbOperations.inc.php (96%) rename juggl-server/{ => api}/services/jsonBuilder.inc.php (63%) rename juggl-server/{ => api}/services/jugglDbApi.inc.php (76%) rename juggl-server/{ => api}/services/paramCleaner.inc.php (96%) rename juggl-server/{ => api}/services/requestTypes.inc.php (95%) rename juggl-server/{ => api}/services/responses.inc.php (95%) rename juggl-server/{ => api}/startRecord.php (96%) create mode 100644 juggl-server/assets/logo.ico create mode 100644 juggl-server/assets/logo_title.png create mode 100644 juggl-server/css/style.css create mode 100644 juggl-server/index.html create mode 100644 juggl-server/js/api.js create mode 100644 juggl-server/js/auth.js create mode 100644 juggl-server/js/helper.js create mode 100644 juggl-server/js/umbrella.min.js create mode 100644 juggl-server/js/visibility.js diff --git a/.gitignore b/.gitignore index 262f0db..64c4fad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -juggl/config/config.txt -juggl/config/config.path -juggl/config/config.php -graphics +juggl/config/config.txt +juggl/config/config.path +juggl/config/config.php +graphics .vscode \ No newline at end of file diff --git a/juggl-server/config/config.php b/juggl-server/api/config/config.php similarity index 59% rename from juggl-server/config/config.php rename to juggl-server/api/config/config.php index 4a9693c..3ae9a01 100644 --- a/juggl-server/config/config.php +++ b/juggl-server/api/config/config.php @@ -1,8 +1,12 @@ - "localhost", - "dbname" => "juggl", - "username" => "juggl", - "password" => "?=5,}f_F&){;@xthx-[i", - "table_prefix" => "ju_" -]; + "localhost", + "dbname" => "juggl", + "username" => "juggl", + "password" => "?=5,}f_F&){;@xthx-[i", + "table_prefix" => "ju_" +]; diff --git a/juggl-server/config/config.php.sample b/juggl-server/api/config/config.php.sample similarity index 94% rename from juggl-server/config/config.php.sample rename to juggl-server/api/config/config.php.sample index e7fa63d..f0688ee 100644 --- a/juggl-server/config/config.php.sample +++ b/juggl-server/api/config/config.php.sample @@ -1,8 +1,8 @@ - "", - "dbname" => "", - "username" => "", - "password" => "", - "table_prefix" => "ju_" + "", + "dbname" => "", + "username" => "", + "password" => "", + "table_prefix" => "ju_" ]; \ No newline at end of file diff --git a/juggl-server/endRecord.php b/juggl-server/api/endRecord.php similarity index 94% rename from juggl-server/endRecord.php rename to juggl-server/api/endRecord.php index c6acb86..c28cc46 100644 --- a/juggl-server/endRecord.php +++ b/juggl-server/api/endRecord.php @@ -1,28 +1,27 @@ -get("user_id"); - $params->select("request"); - - if ($params->exists(["end_time", "record_id"]) == false) { - respondStatus(400, "Missing parameter"); - } - - updateEndRecord($user_id, $params); - } -} - -$branch = new EndRecordBranch(); -$branch->execute(); +get("user_id"); + + if ($params->exists(["end_time", "record_id"]) == false) { + respondStatus(400, "Missing parameter"); + } + + updateEndRecord($user_id, $params); + } +} + +$branch = new EndRecordBranch(); +$branch->execute(); diff --git a/juggl-server/api/getProjects.php b/juggl-server/api/getProjects.php new file mode 100644 index 0000000..636bcdb --- /dev/null +++ b/juggl-server/api/getProjects.php @@ -0,0 +1,29 @@ +get("user_id"); + + $projects = getProjects($user_id); + + $json = new JsonBuilder(); + $json->addProjects($projects); + + respondJson($json); + } +} + +$branch = new GetProjectsBranch(); +$branch->execute(); diff --git a/juggl-server/getRecord.php b/juggl-server/api/getRecord.php similarity index 95% rename from juggl-server/getRecord.php rename to juggl-server/api/getRecord.php index aa9617c..5178ba2 100644 --- a/juggl-server/getRecord.php +++ b/juggl-server/api/getRecord.php @@ -1,34 +1,33 @@ -get("user_id"); - $params->select("request"); - - if ($params->exists(["record_id"]) == false) { - respondStatus(400, "Missing parameter"); - } - - $record = getTimeRecord($user_id, $params->get("record_id")); - - $json = new JsonBuilder(); - $json->addRecords([$record]); - - respondJson($json); - } -} - -$branch = new GetRecordBranch(); -$branch->execute(); +get("user_id"); + + if ($params->exists(["record_id"]) == false) { + respondStatus(400, "Missing parameter"); + } + + $record = getTimeRecord($user_id, $params->get("record_id")); + + $json = new JsonBuilder(); + $json->addRecords([$record]); + + respondJson($json); + } +} + +$branch = new GetRecordBranch(); +$branch->execute(); diff --git a/juggl-server/services/apiBranch.inc.php b/juggl-server/api/services/apiBranch.inc.php similarity index 96% rename from juggl-server/services/apiBranch.inc.php rename to juggl-server/api/services/apiBranch.inc.php index a84618e..6e91e72 100644 --- a/juggl-server/services/apiBranch.inc.php +++ b/juggl-server/api/services/apiBranch.inc.php @@ -1,38 +1,38 @@ -getParams(); - - if ($authenticationRequired) { - $auth = new Authenticator(); - if (!$auth->isAuthenticated($params)) { - $this->authenticationMissing($params); - return; - } - } - - $currentType = currentRequestType(); - if($currentType === RequestType::GET) { - $this->get($params); - } else if ($currentType === RequestType::POST) { - $this->post($params); - } - } - - private function getParams() { - $content = json_decode(file_get_contents('php://input'), true); - return new ParamCleaner(array_merge($content, $_REQUEST, $_SESSION, $_FILES)); - } -} +getParams(); + + if ($authenticationRequired) { + $auth = new Authenticator(); + if (!$auth->isAuthenticated($params)) { + $this->authenticationMissing($params); + return; + } + } + + $currentType = currentRequestType(); + if($currentType === RequestType::GET) { + $this->get($params); + } else if ($currentType === RequestType::POST) { + $this->post($params); + } + } + + private function getParams() { + $content = json_decode(file_get_contents('php://input'), true); + return new ParamCleaner(array_merge($content, $_REQUEST, $_SESSION, $_FILES)); + } +} ?> \ No newline at end of file diff --git a/juggl-server/services/authenticator.inc.php b/juggl-server/api/services/authenticator.inc.php similarity index 96% rename from juggl-server/services/authenticator.inc.php rename to juggl-server/api/services/authenticator.inc.php index 39b94e5..4a5daf8 100644 --- a/juggl-server/services/authenticator.inc.php +++ b/juggl-server/api/services/authenticator.inc.php @@ -1,20 +1,20 @@ -select("api_keys", ["enabled"]); - $db->where("api_key", Comparison::EQUAL, $api_key); - $db->where("user_id", Comparison::EQUAL, $user_id); - - $result = $db->execute(); - - return count($result) == 1 && $result[0]['enabled']; - } - - function isAuthenticated($params) { - return $this->isApiKeyAuthenticated($params->get('api_key'), $params->get('user_id')); - } -} +select("api_keys", ["enabled"]); + $db->where("api_key", Comparison::EQUAL, $api_key); + $db->where("user_id", Comparison::EQUAL, $user_id); + + $result = $db->execute(); + + return count($result) == 1 && $result[0]['enabled']; + } + + function isAuthenticated($params) { + return $this->isApiKeyAuthenticated($params->get('api_key'), $params->get('user_id')); + } +} ?> \ No newline at end of file diff --git a/juggl-server/services/basicEnum.inc.php b/juggl-server/api/services/basicEnum.inc.php similarity index 97% rename from juggl-server/services/basicEnum.inc.php rename to juggl-server/api/services/basicEnum.inc.php index 5805eea..45683a0 100644 --- a/juggl-server/services/basicEnum.inc.php +++ b/juggl-server/api/services/basicEnum.inc.php @@ -1,33 +1,33 @@ -getConstants(); - } - return self::$constCacheArray[$calledClass]; - } - - public static function isValidName($name, $strict = false) { - $constants = self::getConstants(); - - if ($strict) { - return array_key_exists($name, $constants); - } - - $keys = array_map('strtolower', array_keys($constants)); - return in_array(strtolower($name), $keys); - } - - public static function isValidValue($value, $strict = true) { - $values = array_values(self::getConstants()); - return in_array($value, $values, $strict); - } -} +getConstants(); + } + return self::$constCacheArray[$calledClass]; + } + + public static function isValidName($name, $strict = false) { + $constants = self::getConstants(); + + if ($strict) { + return array_key_exists($name, $constants); + } + + $keys = array_map('strtolower', array_keys($constants)); + return in_array(strtolower($name), $keys); + } + + public static function isValidValue($value, $strict = true) { + $values = array_values(self::getConstants()); + return in_array($value, $values, $strict); + } +} ?> \ No newline at end of file diff --git a/juggl-server/services/dbOperations.inc.php b/juggl-server/api/services/dbOperations.inc.php similarity index 96% rename from juggl-server/services/dbOperations.inc.php rename to juggl-server/api/services/dbOperations.inc.php index 445ee7b..e2ea954 100644 --- a/juggl-server/services/dbOperations.inc.php +++ b/juggl-server/api/services/dbOperations.inc.php @@ -1,291 +1,291 @@ -resetQuery(); - $this->tablePrefix = $tablePrefix; - - require(__DIR__ . "/../config/config.php"); - $this->config = $config; - - if ($this->tablePrefix == null) { - $this->tablePrefix = $this->config["table_prefix"]; - } - } - - function resetQuery() - { - $this->query = ""; - $this->data = array(); - $this->table = ""; - } - - private function openConnection() - { - $host = $this->config['host']; - $dbname = $this->config['dbname']; - $dsn = "mysql:host=$host;dbname=$dbname"; - - $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC); - - $this->pdo = new PDO($dsn, $this->config['username'], $this->config['password'], $options); - } - - function select(string $table, array $attributes = array()) - { - $this->table = $this->tablePrefix . $table; - if (count($attributes) == 0) - $formattedAttributes = "*"; - else { - for ($i = 0; $i < count($attributes); $i++) { - $a = $attributes[$i]; - if (strpos($a, ".") === false) { - $attributes[$i] = "$this->table.$a"; - } - } - $formattedAttributes = implode(', ', $attributes); - } - - $this->addToQuery("SELECT $formattedAttributes FROM $this->tablePrefix$table"); - - return $this; - } - - - function orderBy(string $attribute, string $order = Order::ASC) - { - $this->addToQuery("ORDER BY $attribute $order"); - - return $this; - } - - static function getLatestIdInTable(string $table, string $attribute = "id") - { - $db = new DbOperations(); - $db->select($table, array($attribute)); - $db->orderBy($attribute, Order::DESC); - - return $db->execute()[0][$attribute]; - } - - function insert(string $table, array $data) - { - $this->table = $this->tablePrefix . $table; - - $attributes = implode(", ", array_keys($data)); - $valuesIds = array(); - foreach ($data as $attribute => $value) { - $valuesIds[] = $this->addData($value, $attribute); - } - $values = implode(" , ", $valuesIds); - - $this->addToQuery("INSERT INTO $this->tablePrefix$table ( $attributes ) VALUES ( $values )"); - - return $this; - } - - function insertMultiple(string $table, array $attributes, array $data) - { - $this->table = $this->tablePrefix . $table; - - $attributesString = implode(", ", $attributes); - $valueGroups = array(); - $groupIndex = 0; // To avoid same value ids - foreach ($data as $dataGroup) { - if (sizeof($attributes) != sizeof($dataGroup)) { - continue; - } - - $valueIds = array(); - // Indexed for used, so that attributes can easily be assigned to the according values - for ($i = 0; $i < sizeof($dataGroup); $i++) { - $valueIds[] = $this->addData($dataGroup[$i], $attributes[$i] . "_" . (string) $groupIndex); - } - - $valueGroups[] = "(" . implode(", ", $valueIds) . ")"; - $groupIndex++; - } - $values = implode(", ", $valueGroups); - - $this->addToQuery("INSERT INTO $this->tablePrefix$table ( $attributesString ) VALUES $values"); - - return $this; - } - - function update(string $table, array $data) - { - $this->table = $this->tablePrefix . $table; - - $sets = array(); - foreach ($data as $attribute => $value) { - $valueId = $this->addData($value, $attribute); - $sets[] = "$attribute = $valueId"; - } - $setString = implode(", ", $sets); - - $this->addToQuery("UPDATE $this->tablePrefix$table SET $setString"); - - return $this; - } - - function delete(string $table) - { - $this->table = $this->tablePrefix . $table; - - $this->addToQuery("DELETE FROM $this->tablePrefix$table"); - - return $this; - } - - function limit(int $limit, int $offset = null) - { - $this->addToQuery("LIMIT $limit"); - - if ($offset != null) { - $this->addToQuery("OFFSET $offset"); - } - - return $this; - } - - private function addToQuery(string $phrase) - { - $delimeter = " "; - $this->query = implode($delimeter, array($this->query, $phrase)); - } - - function where(string $attribute, string $comparison, $value, string $connector = Combination::AND) - { - if (Comparison::isValidValue($comparison) == false) - return; - - $keyWord = "WHERE"; - if (!(strpos($this->query, $keyWord) === false)) - $keyWord = $connector; - - $valueId = $this->addData($value, $attribute); - $this->addToQuery("$keyWord $attribute $comparison $valueId"); - - return $this; - } - - function whereOneOf(string $attribute, string $comparison, $values, string $connector = Combination::AND) - { - if (Comparison::isValidValue($comparison) == false) - return; - - $keyWord = "WHERE"; - if (!(strpos($this->query, $keyWord) === false)) - $keyWord = $connector; - - $whereClause = "$keyWord ( "; - for ($i = 0; $i < sizeof($values); $i++) { - if ($i > 0) { - $whereClause .= " OR "; - } - - $valueId = $this->addData($values[$i], $attribute . '_' . $i); - $whereClause .= "$attribute $comparison $valueId"; - } - $whereClause .= " )"; - - $this->addToQuery($whereClause); - - return $this; - } - - function innerJoin(string $table, string $externAttribute, string $internAttribute = "", string $internTable = "") - { - if ($internTable === "") { - $internTable = substr($this->table, sizeof($this->tablePrefix)); - } - if ($internAttribute === "") { - $internAttribute = $externAttribute; - } - - $innerJoin = "INNER JOIN $this->tablePrefix$table ON $this->tablePrefix$table.$externAttribute = $this->tablePrefix$internTable.$internAttribute"; - - $this->addToQuery($innerJoin); - - return $this; - } - - private function addData($data, $attribute) - { - $name = str_replace(".", "", $attribute); - - $this->data[$name] = $data; - return ":" . $name; - } - - function addSql($sql) - { - $this->addToQuery($sql); - } - - function addValue($value) - { - $identifier = "customIdentifier" . $this->customValueId; - $this->customValueId += 1; - - $this->addToQuery($this->addData($value, $identifier)); - } - - function execute() - { - try { - $this->openConnection(); - - $pdoQuery = $this->pdo->prepare($this->query); - $pdoQuery->execute($this->data); - - $results = array(); - while ($row = $pdoQuery->fetch()) { - $results[] = $row; - } - - $this->resetQuery(); - - return $results; - } catch (PDOException $e) { - // TODO: Hide errors from user and log them - print($e); - return array(); - } - } - - function sql(string $sqlStatement, array $data) - { - $this->query = $sqlStatement; - - foreach ($data as $attribute => $value) { - $this->addData($value, $attribute); - } - } -} - -abstract class Comparison extends BasicEnum -{ - const EQUAL = "="; - const GREATER_THAN = ">"; - const GREATER_THAN_OR_EQUAL = ">="; - const LESS_THAN = "<"; - const LESS_THAN_OR_EQUAL = "<="; - const UNEQUAL = "!="; - const LIKE = "LIKE"; -} - -abstract class Combination extends BasicEnum -{ - const AND = "AND"; - const OR = "OR"; -} - -abstract class Order extends BasicEnum -{ - const ASC = "ASC"; - const DESC = "DESC"; -} +resetQuery(); + $this->tablePrefix = $tablePrefix; + + require(__DIR__ . "/../config/config.php"); + $this->config = $config; + + if ($this->tablePrefix == null) { + $this->tablePrefix = $this->config["table_prefix"]; + } + } + + function resetQuery() + { + $this->query = ""; + $this->data = array(); + $this->table = ""; + } + + private function openConnection() + { + $host = $this->config['host']; + $dbname = $this->config['dbname']; + $dsn = "mysql:host=$host;dbname=$dbname"; + + $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC); + + $this->pdo = new PDO($dsn, $this->config['username'], $this->config['password'], $options); + } + + function select(string $table, array $attributes = array()) + { + $this->table = $this->tablePrefix . $table; + if (count($attributes) == 0) + $formattedAttributes = "*"; + else { + for ($i = 0; $i < count($attributes); $i++) { + $a = $attributes[$i]; + if (strpos($a, ".") === false) { + $attributes[$i] = "$this->table.$a"; + } + } + $formattedAttributes = implode(', ', $attributes); + } + + $this->addToQuery("SELECT $formattedAttributes FROM $this->tablePrefix$table"); + + return $this; + } + + + function orderBy(string $attribute, string $order = Order::ASC) + { + $this->addToQuery("ORDER BY $attribute $order"); + + return $this; + } + + static function getLatestIdInTable(string $table, string $attribute = "id") + { + $db = new DbOperations(); + $db->select($table, array($attribute)); + $db->orderBy($attribute, Order::DESC); + + return $db->execute()[0][$attribute]; + } + + function insert(string $table, array $data) + { + $this->table = $this->tablePrefix . $table; + + $attributes = implode(", ", array_keys($data)); + $valuesIds = array(); + foreach ($data as $attribute => $value) { + $valuesIds[] = $this->addData($value, $attribute); + } + $values = implode(" , ", $valuesIds); + + $this->addToQuery("INSERT INTO $this->tablePrefix$table ( $attributes ) VALUES ( $values )"); + + return $this; + } + + function insertMultiple(string $table, array $attributes, array $data) + { + $this->table = $this->tablePrefix . $table; + + $attributesString = implode(", ", $attributes); + $valueGroups = array(); + $groupIndex = 0; // To avoid same value ids + foreach ($data as $dataGroup) { + if (sizeof($attributes) != sizeof($dataGroup)) { + continue; + } + + $valueIds = array(); + // Indexed for used, so that attributes can easily be assigned to the according values + for ($i = 0; $i < sizeof($dataGroup); $i++) { + $valueIds[] = $this->addData($dataGroup[$i], $attributes[$i] . "_" . (string) $groupIndex); + } + + $valueGroups[] = "(" . implode(", ", $valueIds) . ")"; + $groupIndex++; + } + $values = implode(", ", $valueGroups); + + $this->addToQuery("INSERT INTO $this->tablePrefix$table ( $attributesString ) VALUES $values"); + + return $this; + } + + function update(string $table, array $data) + { + $this->table = $this->tablePrefix . $table; + + $sets = array(); + foreach ($data as $attribute => $value) { + $valueId = $this->addData($value, $attribute); + $sets[] = "$attribute = $valueId"; + } + $setString = implode(", ", $sets); + + $this->addToQuery("UPDATE $this->tablePrefix$table SET $setString"); + + return $this; + } + + function delete(string $table) + { + $this->table = $this->tablePrefix . $table; + + $this->addToQuery("DELETE FROM $this->tablePrefix$table"); + + return $this; + } + + function limit(int $limit, int $offset = null) + { + $this->addToQuery("LIMIT $limit"); + + if ($offset != null) { + $this->addToQuery("OFFSET $offset"); + } + + return $this; + } + + private function addToQuery(string $phrase) + { + $delimeter = " "; + $this->query = implode($delimeter, array($this->query, $phrase)); + } + + function where(string $attribute, string $comparison, $value, string $connector = Combination::AND) + { + if (Comparison::isValidValue($comparison) == false) + return; + + $keyWord = "WHERE"; + if (!(strpos($this->query, $keyWord) === false)) + $keyWord = $connector; + + $valueId = $this->addData($value, $attribute); + $this->addToQuery("$keyWord $attribute $comparison $valueId"); + + return $this; + } + + function whereOneOf(string $attribute, string $comparison, $values, string $connector = Combination::AND) + { + if (Comparison::isValidValue($comparison) == false) + return; + + $keyWord = "WHERE"; + if (!(strpos($this->query, $keyWord) === false)) + $keyWord = $connector; + + $whereClause = "$keyWord ( "; + for ($i = 0; $i < sizeof($values); $i++) { + if ($i > 0) { + $whereClause .= " OR "; + } + + $valueId = $this->addData($values[$i], $attribute . '_' . $i); + $whereClause .= "$attribute $comparison $valueId"; + } + $whereClause .= " )"; + + $this->addToQuery($whereClause); + + return $this; + } + + function innerJoin(string $table, string $externAttribute, string $internAttribute = "", string $internTable = "") + { + if ($internTable === "") { + $internTable = substr($this->table, sizeof($this->tablePrefix)); + } + if ($internAttribute === "") { + $internAttribute = $externAttribute; + } + + $innerJoin = "INNER JOIN $this->tablePrefix$table ON $this->tablePrefix$table.$externAttribute = $this->tablePrefix$internTable.$internAttribute"; + + $this->addToQuery($innerJoin); + + return $this; + } + + private function addData($data, $attribute) + { + $name = str_replace(".", "", $attribute); + + $this->data[$name] = $data; + return ":" . $name; + } + + function addSql($sql) + { + $this->addToQuery($sql); + } + + function addValue($value) + { + $identifier = "customIdentifier" . $this->customValueId; + $this->customValueId += 1; + + $this->addToQuery($this->addData($value, $identifier)); + } + + function execute() + { + try { + $this->openConnection(); + + $pdoQuery = $this->pdo->prepare($this->query); + $pdoQuery->execute($this->data); + + $results = array(); + while ($row = $pdoQuery->fetch()) { + $results[] = $row; + } + + $this->resetQuery(); + + return $results; + } catch (PDOException $e) { + // TODO: Hide errors from user and log them + print($e); + return array(); + } + } + + function sql(string $sqlStatement, array $data) + { + $this->query = $sqlStatement; + + foreach ($data as $attribute => $value) { + $this->addData($value, $attribute); + } + } +} + +abstract class Comparison extends BasicEnum +{ + const EQUAL = "="; + const GREATER_THAN = ">"; + const GREATER_THAN_OR_EQUAL = ">="; + const LESS_THAN = "<"; + const LESS_THAN_OR_EQUAL = "<="; + const UNEQUAL = "!="; + const LIKE = "LIKE"; +} + +abstract class Combination extends BasicEnum +{ + const AND = "AND"; + const OR = "OR"; +} + +abstract class Order extends BasicEnum +{ + const ASC = "ASC"; + const DESC = "DESC"; +} diff --git a/juggl-server/services/jsonBuilder.inc.php b/juggl-server/api/services/jsonBuilder.inc.php similarity index 63% rename from juggl-server/services/jsonBuilder.inc.php rename to juggl-server/api/services/jsonBuilder.inc.php index b83d9e3..80d2db0 100644 --- a/juggl-server/services/jsonBuilder.inc.php +++ b/juggl-server/api/services/jsonBuilder.inc.php @@ -1,49 +1,67 @@ -jsonData = array(); - } - - function getJson() - { - return json_encode($this->jsonData, JSON_FORCE_OBJECT); - } - - function getArray() - { - return $this->jsonData; - } - - function addRecords(array $records) - { - $columns = array( - "record_id" => "", - "start_time" => "", - "end_time" => "", - "duration" => "", - "user_id" => "", - "project_id" => "", - "start_device_id" => "" - ); - - foreach ($records as $record) { - $this->jsonData['records'] = array(); - $this->jsonData['records'][] = $this->createJsonArray($record, $columns); - } - return $this; - } - - private function createJsonArray(array $data, array $columns) - { - $jsonArray = array(); - foreach ($columns as $key => $column) { - if ($column === "") { - $column = $key; - } - $jsonArray[$key] = $data[$column]; - } - return $jsonArray; - } -} +jsonData = array(); + } + + function getJson() + { + return json_encode($this->jsonData, JSON_FORCE_OBJECT); + } + + function getArray() + { + return $this->jsonData; + } + + function addRecords(array $records) + { + $columns = array( + "record_id" => "", + "start_time" => "", + "end_time" => "", + "duration" => "", + "user_id" => "", + "project_id" => "", + "start_device_id" => "" + ); + + $this->jsonData['records'] = array(); + foreach ($records as $record) { + $this->jsonData['records'][] = $this->createJsonArray($record, $columns); + } + return $this; + } + + function addProjects(array $projects) + { + $columns = array( + "project_id" => "", + "name" => "", + "user_id" => "", + "start_date" => "", + "duration" => "", + "record_count" => "" + ); + + $this->jsonData['projects'] = array(); + foreach ($projects as $project) { + $this->jsonData['projects'][] = $this->createJsonArray($project, $columns); + } + return $this; + } + + private function createJsonArray(array $data, array $columns) + { + $jsonArray = array(); + foreach ($columns as $key => $column) { + if ($column === "") { + $column = $key; + } + $jsonArray[$key] = $data[$column]; + } + return $jsonArray; + } +} diff --git a/juggl-server/services/jugglDbApi.inc.php b/juggl-server/api/services/jugglDbApi.inc.php similarity index 76% rename from juggl-server/services/jugglDbApi.inc.php rename to juggl-server/api/services/jugglDbApi.inc.php index 5853ffc..6612972 100644 --- a/juggl-server/services/jugglDbApi.inc.php +++ b/juggl-server/api/services/jugglDbApi.inc.php @@ -1,167 +1,206 @@ - $user_id, - "start_time" => $params->get("start_time"), - "project_id" => $project_id, - "start_device_id" => $start_device_id - ]; - - $db = new DbOperations(); - $db->insert("time_records", $data); - $db->execute(); -} - -function addTimeRecord($user_id, $params, $project_id = null, $start_device_id = null) -{ - $data = [ - "user_id" => $user_id, - "start_time" => $params->get("start_time"), - "end_time" => $params->get("end_time"), - "duration" => $params->get("duration"), - "project_id" => $project_id, - "start_device_id" => $start_device_id - ]; - - $db = new DbOperations(); - $db->insert("time_records", $data); - $db->execute(); -} - -function getTimeRecord($user_id, $record_id) -{ - $db = new DbOperations(); - $db->select("time_records"); - $db->where("user_id", Comparison::EQUAL, $user_id); - $db->where("record_id", Comparison::EQUAL, $record_id); - $result = $db->execute(); - - if (count($result) <= 0) { - return null; - } - $result = $result[0]; - - // Is still running? - if ($result["end_time"] == null) { - $result["duration"] = calcDuration($result["start_time"]); - } - - return $result; -} - -function getProjectRecord($user_id, $project_id, $finished = null) -{ - $db = new DbOperations(); - $db->select("time_records"); - $db->where("user_id", Comparison::EQUAL, $user_id); - $db->where("project_id", Comparison::EQUAL, $project_id); - - if ($finished != null) { - $comp = Comparison::UNEQUAL; - if ($finished == false) { - $comp = Comparison::EQUAL; - } - - $db->where("end_time", $comp, null); - } - - $db->orderBy("start_time", Order::DESC); - $result = $db->execute(); - - if (count($result) <= 0) { - return null; - } - $result = $result[0]; - - // Is still running? - if ($result["end_time"] == null) { - $result["duration"] = calcDuration($result["start_time"]); - } - - return $result; -} - -function updateEndRecord($user_id, $params) -{ - $record_id = $params->get("record_id"); - - // Get start instance to calculate duration - $start_time = getTimeRecord($user_id, $record_id)[0]["start_time"]; - - $data = [ - "end_time" => $params->get("end_time"), - "duration" => calcDuration($start_time, $params->get("end_time")) - ]; - - $db = new DbOperations(); - $db->update("time_records", $data); - $db->where("user_id", Comparison::EQUAL, $user_id); - $db->where("record_id", Comparison::EQUAL, $record_id); - $db->execute(); -} - -function updateTimeRecord($user_id, $params) -{ - $data = []; - - - $anythingUpdated = false; - if ($params->exists(["start_time"])) { - $data["start_time"] = $params->get("start_time"); - $anythingUpdated = true; - } - if ($params->exists(["end_time"])) { - $data["end_time"] = $params->get("end_time"); - $anythingUpdated = true; - } - if ($params->exists(["duration"])) { - $data["duration"] = $params->get("duration"); - $anythingUpdated = true; - } - if ($params->exists(["project_id"])) { - $data["project_id"] = $params->get("project_id"); - $anythingUpdated = true; - } - if ($params->exists(["start_device_id"])) { - $data["start_device_id"] = $params->get("start_device_id"); - $anythingUpdated = true; - } - - if ($anythingUpdated == false) { - return; - } - - $db = new DbOperations(); - $db->update("time_records", $data); - $db->where("user_id", Comparison::EQUAL, $user_id); - $db->where("record_id", Comparison::EQUAL, $params->get("record_id")); - $db->execute(); -} - -function isProjectValid($project_id, $user_id) -{ - $db = new DbOperations(); - $db->select("projects"); - $db->where("project_id", Comparison::EQUAL, $project_id); - $db->where("user_id", Comparison::EQUAL, $user_id); - - return count($db->execute()) == 1; -} - -function isDeviceValid($start_device_id, $user_id) -{ - $db = new DbOperations(); - $db->select("devices"); - $db->where("start_device_id", Comparison::EQUAL, $start_device_id); - $db->where("user_id", Comparison::EQUAL, $user_id); - - return count($db->execute()) == 1; -} - -function calcDuration($start_time, $end_time = "NOW") -{ - return (int)((new DateTime($start_time))->diff(new DateTime($end_time))->format("%s")); -} + $user_id, + "start_time" => $params->get("start_time"), + "project_id" => $project_id, + "start_device_id" => $start_device_id + ]; + + $db = new DbOperations(); + $db->insert("time_records", $data); + $db->execute(); +} + +function addTimeRecord($user_id, $params, $project_id = null, $start_device_id = null) +{ + $data = [ + "user_id" => $user_id, + "start_time" => $params->get("start_time"), + "end_time" => $params->get("end_time"), + "duration" => $params->get("duration"), + "project_id" => $project_id, + "start_device_id" => $start_device_id + ]; + + $db = new DbOperations(); + $db->insert("time_records", $data); + $db->execute(); +} + +function getTimeRecord($user_id, $record_id) +{ + $db = new DbOperations(); + $db->select("time_records"); + $db->where("user_id", Comparison::EQUAL, $user_id); + $db->where("record_id", Comparison::EQUAL, $record_id); + $result = $db->execute(); + + if (count($result) <= 0) { + return null; + } + $result = $result[0]; + + // Is still running? + if ($result["end_time"] == null) { + $result["duration"] = calcDuration($result["start_time"]); + } + + return $result; +} + +function getProjects($user_id) +{ + $db = new DbOperations(); + $db->select("projects"); + $db->where("user_id", Comparison::EQUAL, $user_id); + $results = $db->execute(); + + foreach ($results as $key => $project) { + $meta = getProjectRecordDerivedData($user_id, $project["project_id"]); + + foreach ($meta as $metaKey => $value) { + $results[$key][$metaKey] = $value; + } + } + + return $results; +} + +function getProjectRecordDerivedData($user_id, $project_id) +{ + $durationAttribute = "SUM(duration) AS total_duration"; + $recordCountAttribute = "COUNT(*) AS record_count"; + + $db = new DbOperations(); + $db->select("time_records", ["*", $durationAttribute, $recordCountAttribute]); + $db->where("user_id", Comparison::EQUAL, $user_id); + $db->where("project_id", Comparison::EQUAL, $project_id); + $results = $db->execute(); + + if (count($results) <= 0) { + return ["duration" => 0, "record_count" => 0]; + } else { + return [ + "duration" => (int)$results[0]["total_duration"], + "record_count" => (int)$results[0]["record_count"] + ]; + } +} + +function getProjectRecord($user_id, $project_id, $finished = null) +{ + $db = new DbOperations(); + $db->select("time_records"); + $db->where("user_id", Comparison::EQUAL, $user_id); + $db->where("project_id", Comparison::EQUAL, $project_id); + + if ($finished != null) { + $comp = Comparison::UNEQUAL; + if ($finished == false) { + $comp = Comparison::EQUAL; + } + + $db->where("end_time", $comp, null); + } + + $db->orderBy("start_time", Order::DESC); + $result = $db->execute(); + + if (count($result) <= 0) { + return null; + } + $result = $result[0]; + + // Is still running? + if ($result["end_time"] == null) { + $result["duration"] = calcDuration($result["start_time"]); + } + + return $result; +} + +function updateEndRecord($user_id, $params) +{ + $record_id = $params->get("record_id"); + + // Get start instance to calculate duration + $start_time = getTimeRecord($user_id, $record_id)[0]["start_time"]; + + $data = [ + "end_time" => $params->get("end_time"), + "duration" => calcDuration($start_time, $params->get("end_time")) + ]; + + $db = new DbOperations(); + $db->update("time_records", $data); + $db->where("user_id", Comparison::EQUAL, $user_id); + $db->where("record_id", Comparison::EQUAL, $record_id); + $db->execute(); +} + +function updateTimeRecord($user_id, $params) +{ + $data = []; + + + $anythingUpdated = false; + if ($params->exists(["start_time"])) { + $data["start_time"] = $params->get("start_time"); + $anythingUpdated = true; + } + if ($params->exists(["end_time"])) { + $data["end_time"] = $params->get("end_time"); + $anythingUpdated = true; + } + if ($params->exists(["duration"])) { + $data["duration"] = $params->get("duration"); + $anythingUpdated = true; + } + if ($params->exists(["project_id"])) { + $data["project_id"] = $params->get("project_id"); + $anythingUpdated = true; + } + if ($params->exists(["start_device_id"])) { + $data["start_device_id"] = $params->get("start_device_id"); + $anythingUpdated = true; + } + + if ($anythingUpdated == false) { + return; + } + + $db = new DbOperations(); + $db->update("time_records", $data); + $db->where("user_id", Comparison::EQUAL, $user_id); + $db->where("record_id", Comparison::EQUAL, $params->get("record_id")); + $db->execute(); +} + +function isProjectValid($project_id, $user_id) +{ + $db = new DbOperations(); + $db->select("projects"); + $db->where("project_id", Comparison::EQUAL, $project_id); + $db->where("user_id", Comparison::EQUAL, $user_id); + + return count($db->execute()) == 1; +} + +function isDeviceValid($start_device_id, $user_id) +{ + $db = new DbOperations(); + $db->select("devices"); + $db->where("start_device_id", Comparison::EQUAL, $start_device_id); + $db->where("user_id", Comparison::EQUAL, $user_id); + + return count($db->execute()) == 1; +} + +function calcDuration($start_time, $end_time = "NOW") +{ + return (int)((new DateTime($start_time))->diff(new DateTime($end_time))->format("%s")); +} diff --git a/juggl-server/services/paramCleaner.inc.php b/juggl-server/api/services/paramCleaner.inc.php similarity index 96% rename from juggl-server/services/paramCleaner.inc.php rename to juggl-server/api/services/paramCleaner.inc.php index d9ba0bc..79db884 100644 --- a/juggl-server/services/paramCleaner.inc.php +++ b/juggl-server/api/services/paramCleaner.inc.php @@ -1,50 +1,50 @@ -sourceParams = $params; - $this->selectedParams = $params; - $this->errorCount = 0; - $this->errorMessage = ""; - } - - function select (string $prop = "") { - if ($prop == "") { - $this->selectedParams = $this->sourceParams; - } else { - $this->selectedParams = $this->selectedParams[$prop]; - } - } - - function get (string $prop) { - if(isset($this->selectedParams[$prop])) { - return $this->selectedParams[$prop]; - } else { - $this->errorCount += 1; - $this->errorMessage .= "Property \"{$prop}\" missing. "; - return null; - } - } - - function exists (array $props) { - foreach ($props as $prop) { - if(isset($this->selectedParams[$prop]) == false) { - return false; - } - } - return true; - } - - function hasErrorOccurred () { - return $this->errorCount > 0; - } - - function getErrorMessage () { - return $this->errorMessage; - } - - function resetErrors () { - $this->errorMessage = ""; - $this->errorCount = 0; - } -} +sourceParams = $params; + $this->selectedParams = $params; + $this->errorCount = 0; + $this->errorMessage = ""; + } + + function select (string $prop = "") { + if ($prop == "") { + $this->selectedParams = $this->sourceParams; + } else { + $this->selectedParams = $this->selectedParams[$prop]; + } + } + + function get (string $prop) { + if(isset($this->selectedParams[$prop])) { + return $this->selectedParams[$prop]; + } else { + $this->errorCount += 1; + $this->errorMessage .= "Property \"{$prop}\" missing. "; + return null; + } + } + + function exists (array $props) { + foreach ($props as $prop) { + if(isset($this->selectedParams[$prop]) == false) { + return false; + } + } + return true; + } + + function hasErrorOccurred () { + return $this->errorCount > 0; + } + + function getErrorMessage () { + return $this->errorMessage; + } + + function resetErrors () { + $this->errorMessage = ""; + $this->errorCount = 0; + } +} ?> \ No newline at end of file diff --git a/juggl-server/services/requestTypes.inc.php b/juggl-server/api/services/requestTypes.inc.php similarity index 95% rename from juggl-server/services/requestTypes.inc.php rename to juggl-server/api/services/requestTypes.inc.php index 97607e6..786562f 100644 --- a/juggl-server/services/requestTypes.inc.php +++ b/juggl-server/api/services/requestTypes.inc.php @@ -1,19 +1,19 @@ - \ No newline at end of file diff --git a/juggl-server/services/responses.inc.php b/juggl-server/api/services/responses.inc.php similarity index 95% rename from juggl-server/services/responses.inc.php rename to juggl-server/api/services/responses.inc.php index 51c0cb7..a9c6962 100644 --- a/juggl-server/services/responses.inc.php +++ b/juggl-server/api/services/responses.inc.php @@ -1,25 +1,25 @@ -getJson()); -} - -function respondHtml(string $html) { - print($html); -} - -function respondStatus(int $statusCode, string $message = "") { - http_response_code($statusCode); - die($message); -} - -function redirectBack() { - header("Location: {$_SERVER['HTTP_REFERER']}"); -} - -function redirectTo($url) { - header("Location: {$url}"); -} +getJson()); +} + +function respondHtml(string $html) { + print($html); +} + +function respondStatus(int $statusCode, string $message = "") { + http_response_code($statusCode); + die($message); +} + +function redirectBack() { + header("Location: {$_SERVER['HTTP_REFERER']}"); +} + +function redirectTo($url) { + header("Location: {$url}"); +} ?> \ No newline at end of file diff --git a/juggl-server/startRecord.php b/juggl-server/api/startRecord.php similarity index 96% rename from juggl-server/startRecord.php rename to juggl-server/api/startRecord.php index 26d7598..fb982a5 100644 --- a/juggl-server/startRecord.php +++ b/juggl-server/api/startRecord.php @@ -1,50 +1,49 @@ -get("user_id"); - $params->select("request"); - - if ($params->exists(["start_time"]) == false) { - respondStatus(400, "Missing parameter"); - } - - $project_id = $params->get("project_id"); - if (isProjectValid($project_id, $user_id) == false) { - $project_id = null; - } - - $device_id = $params->get("start_device_id"); - if (isDeviceValid($device_id, $user_id) == false) { - $device_id = null; - } - - // Does a running record for that project already exist? - if (getProjectRecord($user_id, $project_id, false) != null) { - respondStatus(409, "Project record already started"); - } - - addStartRecord($user_id, $params, $project_id, $device_id); - $record = getProjectRecord($user_id, $project_id, false); - - $json = new JsonBuilder(); - $json->addRecords([$record]); - - respondJson($json); - } -} - -$branch = new StartRecordBranch(); -$branch->execute(); +get("user_id"); + + if ($params->exists(["start_time"]) == false) { + respondStatus(400, "Missing parameter"); + } + + $project_id = $params->get("project_id"); + if (isProjectValid($project_id, $user_id) == false) { + $project_id = null; + } + + $device_id = $params->get("start_device_id"); + if (isDeviceValid($device_id, $user_id) == false) { + $device_id = null; + } + + // Does a running record for that project already exist? + if (getProjectRecord($user_id, $project_id, false) != null) { + respondStatus(409, "Project record already started"); + } + + addStartRecord($user_id, $params, $project_id, $device_id); + $record = getProjectRecord($user_id, $project_id, false); + + $json = new JsonBuilder(); + $json->addRecords([$record]); + + respondJson($json); + } +} + +$branch = new StartRecordBranch(); +$branch->execute(); diff --git a/juggl-server/assets/logo.ico b/juggl-server/assets/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..a1e78939a3261e60ab479078da194fe07aed7c9d GIT binary patch literal 105109 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 literal 0 HcmV?d00001 diff --git a/juggl-server/assets/logo_title.png b/juggl-server/assets/logo_title.png new file mode 100644 index 0000000000000000000000000000000000000000..eb257ee54f9eaac4867cbcec3d340d52fff05dcc GIT binary patch literal 68938 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 { + line-height: 4rem; + vertical-align: baseline; + display: inline-block; +} + +textarea:focus, +input:focus, +button:focus { + outline: none; +} + +input, +button { + border-radius: 2px; + border: solid red 1px; + background-color: #0000; + color: #BBB; + padding: 5px; + font-size: 12pt; +} + +button:hover { + color: white; + background-color: #F007; +} + +button:active { + transform: translate(0px, 2px); +} + +table { + border-collapse: collapse; + width: 100%; +} + +thead * { + color: #AAA; + text-align: left; +} \ No newline at end of file diff --git a/juggl-server/index.html b/juggl-server/index.html new file mode 100644 index 0000000..65f0549 --- /dev/null +++ b/juggl-server/index.html @@ -0,0 +1,61 @@ + + + + + Juggl + + + + + + + + + + + + + + + +
+
    +
  • + +
  • +
  • + + + +
  • +
+
+
+ + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/juggl-server/js/api.js b/juggl-server/js/api.js new file mode 100644 index 0000000..b8a96e1 --- /dev/null +++ b/juggl-server/js/api.js @@ -0,0 +1,32 @@ +API_URL = "/api"; + +const api = { + getProjects() { + return request("/getProjects.php") + .then((r) => { + return r.json(); + }) + .then((j) => { + return j.projects; + }) + .catch((e) => { + console.log(e); + return []; + }); + }, +}; + +function request(path, json = {}, options = {}) { + json.api_key = getApiKey(); + json.user_id = getUserId(); + + options.method = "POST"; + options.body = JSON.stringify(json); + options.headers = { + "Content-Type": "application/json", + }; + + var url = API_URL + path; + + return fetch(url, options); +} diff --git a/juggl-server/js/auth.js b/juggl-server/js/auth.js new file mode 100644 index 0000000..c466336 --- /dev/null +++ b/juggl-server/js/auth.js @@ -0,0 +1,58 @@ +function logIn(apiKey, userId) { + localStorage.apiKey = apiKey; + localStorage.userId = userId; + setState(States.IDLE); +} + +function logOut() { + delete localStorage.apiKey; + delete localStorage.userId; + setState(States.PUBLIC); +} + +function isLoggedIn() { + return !!localStorage.apiKey && !!localStorage.userId; +} + +function getApiKey() { + return localStorage.apiKey; +} + +function getUserId() { + return localStorage.userId; +} + +function handleAuthBtn() { + if (isLoggedIn()) { + logOut(); + } else { + var apiKey = u("#api-key").first().value; + if (apiKey === undefined || apiKey === "") { + return; + } + + var userId = u("#user-id").first().value; + if (userId === undefined || userId === "") { + return; + } + + logIn(apiKey, userId); + onLogIn(); + } + u("#api-key").first().value = ""; + updateAuthBtnText(); +} + +function updateAuthBtnText() { + var btn = u("#auth-btn"); + + if (isLoggedIn()) { + btn.text("Log Out"); + } else { + btn.text("Log In"); + } +} + +function onLogIn() { + loadProjectList(); +} diff --git a/juggl-server/js/helper.js b/juggl-server/js/helper.js new file mode 100644 index 0000000..3e0c6d9 --- /dev/null +++ b/juggl-server/js/helper.js @@ -0,0 +1,59 @@ +const TABLE_ROW = "tr"; +const TABLE_DATA = "td"; + +function loadProjectList() { + api.getProjects().then((projects) => { + var table = u(u("#project-list").first()); + Object.values(projects).forEach((project) => { + var row = createNode(TABLE_ROW); + var data = undefined; + + data = createNode(TABLE_DATA); + append(row, data); + u(data).text(project["name"]); + + data = createNode(TABLE_DATA); + append(row, data); + u(data).text(new Date(project["start_date"]).toDateString()); + + var duration = parseFloat(project["duration"]) / 60 / 60; + var unit = "hours"; + data = createNode(TABLE_DATA); + append(row, data); + u(data).text(duration + " " + unit); + + data = createNode(TABLE_DATA); + append(row, data); + u(data).text(project["record_count"]); + + data = createNode(TABLE_DATA); + append(row, data); + u(data).text( + duration / parseInt(project["record_count"]) + " " + unit + "/record" + ); + + row = u(row); + row.data("project-id", project["project_id"]); + row.on("click", projectClicked); + + table.append(row); + }); + }); +} + +// Created new DOM object +// element: Type of DOM object (div, p, ...) +function createNode(element) { + return document.createElement(element); +} + +// Appends child to parent +// parent: DOM to append child to +// el: DOM child to append to parent +function append(parent, el) { + return parent.appendChild(el); +} + +function projectClicked(event) { + console.log(event); +} diff --git a/juggl-server/js/umbrella.min.js b/juggl-server/js/umbrella.min.js new file mode 100644 index 0000000..0f270b2 --- /dev/null +++ b/juggl-server/js/umbrella.min.js @@ -0,0 +1,3 @@ +/* Umbrella JS 3.2.2 umbrellajs.com */ + +var u=function(t,e){return this instanceof u?t instanceof u?t:("string"==typeof t&&(t=this.select(t,e)),t&&t.nodeName&&(t=[t]),void(this.nodes=this.slice(t))):new u(t,e)};u.prototype={get length(){return this.nodes.length}},u.prototype.nodes=[],u.prototype.addClass=function(){return this.eacharg(arguments,function(t,e){t.classList.add(e)})},u.prototype.adjacent=function(i,t,n){return"number"==typeof t&&(t=0===t?[]:new Array(t).join().split(",").map(Number.call,Number)),this.each(function(r,o){var e=document.createDocumentFragment();u(t||{}).map(function(t,e){var n="function"==typeof i?i.call(this,t,e,r,o):i;return"string"==typeof n?this.generate(n):u(n)}).each(function(t){this.isInPage(t)?e.appendChild(u(t).clone().first()):e.appendChild(t)}),n.call(this,r,e)})},u.prototype.after=function(t,e){return this.adjacent(t,e,function(t,e){t.parentNode.insertBefore(e,t.nextSibling)})},u.prototype.append=function(t,e){return this.adjacent(t,e,function(t,e){t.appendChild(e)})},u.prototype.args=function(t,e,n){return"function"==typeof t&&(t=t(e,n)),"string"!=typeof t&&(t=this.slice(t).map(this.str(e,n))),t.toString().split(/[\s,]+/).filter(function(t){return t.length})},u.prototype.array=function(o){o=o;var i=this;return this.nodes.reduce(function(t,e,n){var r;return o?((r=o.call(i,e,n))||(r=!1),"string"==typeof r&&(r=u(r)),r instanceof u&&(r=r.nodes)):r=e.innerHTML,t.concat(!1!==r?r:[])},[])},u.prototype.attr=function(t,e,r){return r=r?"data-":"",this.pairs(t,e,function(t,e){return t.getAttribute(r+e)},function(t,e,n){t.setAttribute(r+e,n)})},u.prototype.before=function(t,e){return this.adjacent(t,e,function(t,e){t.parentNode.insertBefore(e,t)})},u.prototype.children=function(t){return this.map(function(t){return this.slice(t.children)}).filter(t)},u.prototype.clone=function(){return this.map(function(t,e){var n=t.cloneNode(!0),r=this.getAll(n);return this.getAll(t).each(function(t,e){for(var n in this.mirror)this.mirror[n]&&this.mirror[n](t,r.nodes[e])}),n})},u.prototype.getAll=function(t){return u([t].concat(u("*",t).nodes))},u.prototype.mirror={},u.prototype.mirror.events=function(t,e){if(t._e)for(var n in t._e)t._e[n].forEach(function(t){u(e).on(n,t.callback)})},u.prototype.mirror.select=function(t,e){u(t).is("select")&&(e.value=t.value)},u.prototype.mirror.textarea=function(t,e){u(t).is("textarea")&&(e.value=t.value)},u.prototype.closest=function(e){return this.map(function(t){do{if(u(t).is(e))return t}while((t=t.parentNode)&&t!==document)})},u.prototype.data=function(t,e){return this.attr(t,e,!0)},u.prototype.each=function(t){return this.nodes.forEach(t.bind(this)),this},u.prototype.eacharg=function(n,r){return this.each(function(e,t){this.args(n,e,t).forEach(function(t){r.call(this,e,t)},this)})},u.prototype.empty=function(){return this.each(function(t){for(;t.firstChild;)t.removeChild(t.firstChild)})},u.prototype.filter=function(e){var t=function(t){return t.matches=t.matches||t.msMatchesSelector||t.webkitMatchesSelector,t.matches(e||"*")};return"function"==typeof e&&(t=e),e instanceof u&&(t=function(t){return-1!==e.nodes.indexOf(t)}),u(this.nodes.filter(t))},u.prototype.find=function(e){return this.map(function(t){return u(e||"*",t)})},u.prototype.first=function(){return this.nodes[0]||!1},u.prototype.generate=function(t){return/^\s* ]/.test(t)?u(document.createElement("table")).html(t).children().children().nodes:/^\s* ]/.test(t)?u(document.createElement("table")).html(t).children().children().children().nodes:/^\s*