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 0000000..a1e7893 Binary files /dev/null and b/juggl-server/assets/logo.ico differ diff --git a/juggl-server/assets/logo_title.png b/juggl-server/assets/logo_title.png new file mode 100644 index 0000000..eb257ee Binary files /dev/null and b/juggl-server/assets/logo_title.png differ diff --git a/juggl-server/css/style.css b/juggl-server/css/style.css new file mode 100644 index 0000000..e0317bc --- /dev/null +++ b/juggl-server/css/style.css @@ -0,0 +1,91 @@ +body { + margin: 0; + background-color: #222; +} + +* { + color: white; + font-family: 'Karla', sans-serif; + transition-duration: 0.1s; +} + +.hidden { + display: none; +} + +header { + width: 100%; + height: 4rem; + position: fixed; + top: 0px; + background-color: #222; + /* background: rgb(10, 133, 168); + background: linear-gradient(137deg, rgba(10, 133, 168, 1) 0%, rgba(136, 0, 0, 1) 100%); */ + /* background: rgb(10, 133, 168); + background: linear-gradient(137deg, rgba(10, 133, 168, 1) 0%, rgba(255, 255, 255, 1) 48%, rgba(136, 0, 0, 1) 100%); */ + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 0px 0px 2rem 1rem #000D; +} + +main { + max-width: 800px; + padding: 0px 20px; + margin: auto; + margin-top: 6rem; + background-color: #0000; +} + +#header-container { + max-width: 1000px; + padding: 0px 20px; + margin: auto; + list-style: none; +} + +#header-container img { + margin-top: 0.5rem; + height: 3rem; + width: auto; +} + +#header-container>* { + 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*