commit 428048955817d3c91883b462609f1f74efda6391 Author: Timothy J. Warren Date: Wed May 7 14:05:13 2014 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..7954bcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/* +coverage/* \ No newline at end of file diff --git a/Sleepy/Core/Input.php b/Sleepy/Core/Input.php new file mode 100755 index 0000000..bc07c3b --- /dev/null +++ b/Sleepy/Core/Input.php @@ -0,0 +1,391 @@ +verb = \strtolower($_SERVER['REQUEST_METHOD']); + + // Parse put and delete requests into input variables + if (isset($this->{$this->verb})) + { + $raw = \file_get_contents('php://input'); + \parse_str($raw, $this->{$this->verb}); + } + + // Set mapping for superglobals, since + // variable variables don't seem to work + // with superglobals :/ + $this->var_map = [ + 'get' => $_GET, + 'post' => $_POST, + 'server' => $_SERVER, + 'env' => $_ENV, + 'cookie' => $_COOKIE + ]; + + // Parse request headers from $_SERVER + foreach($_SERVER as $key => $val) + { + if (strpos($key, 'HTTP_') === 0) + { + $new_key = strtolower(str_replace('HTTP_', '', $key)); + $this->request_headers[$new_key] = $val; + } + } + } + + /** + * Wrapper method for input arrays + * + * Actually works as get,post,put,delete,cookie,server,and env functions + * all wrapped up in one - because boilerplate is bad! + * + * @param string $name - name of input array + * @param array $args - function arguments + * @return mixed + * @throws DomainException + */ + public function __call($name, $args=[]) + { + // Predefine arguments for input array getters + if ( ! isset($args[0])) $args[0] = NULL; + if ( ! isset($args[1])) $args[1] = FILTER_SANITIZE_STRING; + $index = $args[0]; + $filter = $args[1]; + + if (isset($this->var_map[$name])) + { + // Get a superglobal ($_VAR) value + return $this->get_superglobal_var($name, $index, $filter); + } + else if(isset($this->$name)) + { + // Get a input variable not in a superglobal (eg. PUT/DELETE) + return $this->get_request_var($name, $index, $filter); + } + + // What kind of request are you trying to make?! + throw new \DomainException('Invalid input array.'); + } + + /** + * Return header(s) sent from the request + * + * @param string $index + * @return mixed + */ + public function header($index = NULL) + { + if ($index !== NULL) + { + $index = (str_replace([' ', '-'], '_', $index)); + + if (isset($this->request_headers[$index])) + { + return $this->request_headers[$index]; + } + + return NULL; + } + + return $this->request_headers; + } + + /** + * Return parsed header(s) sent from the request + * + * @param string $index + * @return mixed + */ + public function header_array($index = NULL) + { + if (empty($this->parsed_headers)) + { + $this->parsed_headers = $this->parse_headers(); + } + + if ($index !== NULL) + { + $index = (str_replace([' ', '-'], '_', $index)); + + if (isset($this->parsed_headers[$index])) + { + return $this->parsed_headers[$index]; + } + + return NULL; + } + + return $this->parsed_headers; + } + + /** + * Convert headers to a parsed array of values + * + * @return array + */ + protected function parse_headers() + { + foreach($this->request_headers as $header => $value) + { + $has_semi = strpos($value, ';') !== FALSE; + $has_comma = strpos($value, ',') !== FALSE; + $has_eq = strpos($value, '=') !== FALSE; + + // Parse the user agent separately + if ($header === 'user_agent') + { + $this->parsed_headers[$header] = $this->parse_user_agent($value); + continue; + } + // Parse accept-type headers separately as well + else if (strpos($header, 'accept') === 0) + { + $this->parsed_headers[$header] = $this->parse_accept_header($value); + } + // If the header has a comma, and not a semicolon, split on the comma + else if ( ! $has_semi && $has_comma) + { + $this->parsed_headers[$header] = explode(",", $value); + continue; + } + // Parse cookies and other headers with values like query strings + else if ($has_eq && ! $has_semi) + { + parse_str($value, $this->parsed_headers[$header]); + continue; + } + // For headers with commas and semicolons, break first on commas, + // then on semicolons + else if ($has_semi && $has_comma) + { + $values = explode(",", $value); + foreach($values as &$v) + { + if (strpos($v, ";") !== FALSE) + { + $v = explode(";", $v); + } + } + + $this->parsed_headers[$header] = $values; + } + // Anything else, just leave it as a string + else + { + $this->parsed_headers[$header] = $value; + continue; + } + } + + return $this->parsed_headers; + } + + /** + * Parse a browser useragent into a set of useful key-value pairs + * + * @param string $ua_string + * @return array + */ + protected function parse_user_agent($ua_string) + { + $user_agent = []; + + $slash_matches = []; + $slash_pattern = "`[A-Z]+/[0-9]+((\.[0-9]+)+)?`i"; + + $paren_matches = []; + $paren_pattern = "`\(.*?\)`i"; + + // Get all the foo/12.3 paterns from the user agent string + preg_match_all($slash_pattern, $ua_string, $slash_matches); + foreach($slash_matches[0] as $arr) + { + list($key, $version) = explode("/", $arr); + $user_agent['versions'][$key] = $version; + } + + // Get all the info from parenthasized items + preg_match_all($paren_pattern, $ua_string, $paren_matches); + foreach($paren_matches[0] as $arr) + { + $arr = str_replace(['(',')'], '', $arr); + if (strpos($arr, ';') !== FALSE) + { + $user_agent['os'] = explode('; ', $arr); + } + else + { + $user_agent['misc'] = $arr; + } + } + + return $user_agent; + } + + /** + * Parse an accept-type header into an ordered list + * of values + * + * @param string $value + * @return array + */ + protected function parse_accept_header($value) + { + $output = []; + $index = 0; + + // Split into segments of different values + $groups = explode(',', $value); + + foreach($groups as $group) + { + $pair = explode(';q=', $group); + + if (count($pair) === 2) + { + list($val, $q) = $pair; + $output[$q] = $val; + } + else + { + $index++; + $output[$index] = current($pair); + } + } + + ksort($output, SORT_NATURAL); + + return $output; + } + + /** + * Get input var(s) from non-defined superglobal + * + * @param string $type - input array + * @param string $index - variable in the input array + * @param int $filter - PHP filter_var flag + * @return mixed + */ + protected function get_request_var($type, $index=NULL, $filter=FILTER_SANITIZE_STRING) + { + // If index is null, return the whole array + if ($index === NULL) + { + return ($filter !== NULL) ? \filter_var_array($this->$type, $filter) : $this->$type; + } + + // Prevent errors for non-existant variables + if ( ! isset($this->$type[$index])) + { + return NULL; + } + + return ($filter !== NULL) ? \filter_var($this->$type[$index], $filter) : $this->$type[$index]; + } + + /** + * Get index from superglobal + * + * @param string $type - superglobal + * @param string $index - variable in the superglobal + * @param int $filter - PHP filter_var flag + * @return mixed + */ + protected function get_superglobal_var($type, $index=NULL, $filter=FILTER_SANITIZE_STRING) + { + $var =& $this->var_map[$type]; + + // Return the whole array if the index is null + if ($index === NULL) + { + return ($filter !== NULL) ? \filter_var_array($var, $filter) : $var; + } + + // Prevent errors for non-existant variables + if ( ! isset($var[$index])) + { + return NULL; + } + + return ($filter !== NULL) ? \filter_var($var[$index], $filter) : $var[$index]; + } + +} +// End of core/Input.php \ No newline at end of file diff --git a/Sleepy/Core/Output.php b/Sleepy/Core/Output.php new file mode 100755 index 0000000..d5aee68 --- /dev/null +++ b/Sleepy/Core/Output.php @@ -0,0 +1,181 @@ +input = $input; + } + + /** + * Output the data to the client + */ + public function __destruct() + { + // Output the headers + $this->_output_headers(); + + // Echo the response + echo $this->type_wrapper; + } + + /** + * Add a header to be output + * + * @param mixed $header + * @return void + */ + public function set_header($header) + { + if (is_array($header)) + { + array_merge($this->headers, $header); + } + else + { + $this->headers[] = $header; + } + } + + /** + * Set the data to be output to the endpoint + * + * @param string $type - The datatype to send + * @param mixed $data + * @return void + */ + public function set_data($type = 'json', $data) + { + // Get the appropriate output format for the client + // And set the data + $this->get_accepted_type($type, $data); + } + + // -------------------------------------------------------------------------- + // ! Private helper methods + // -------------------------------------------------------------------------- + + /** + * Get the type more accepted for output + * + * @param mixed $types + * @param mixed $data + * @return void + */ + protected function get_accepted_type($types, $data) + { + $types = (array) $types; + $types = array_map('strtoupper', $types); + + $headers = $this->input->header_array(); + $accept = array_flip($headers['accept']); + + $type_map = []; + $accepted = []; + $classes = []; + + foreach($types as $t) + { + $type_class = "Sleepy\\Type\\{$t}"; + $classes[$type_class] = new $type_class($data); + $mime = $classes[$type_class]->get_mime(); + + $type_map[$mime] = $type_class; + } + + foreach($accept as $type => $q) + { + if (array_key_exists($type, $type_map)) + { + $accepted[$q] = $type; + } + } + + krsort($accepted); + + $class = $type_map[current($accepted)]; + $this->type_wrapper = $classes[$class]; + + // Make sure to set the content-type header + if (empty($this->headers)) + { + $mime = $this->type_wrapper->get_mime(); + $this->set_header("Content-type: {$mime}"); + } + } + + /** + * Set the applicable response headers + * + * @return void + */ + protected function _output_headers() + { + foreach($this->headers as $name => $val) + { + if (is_numeric($name)) + { + $output_header = $val; + } + else + { + $output_header = implode(": ", [$name, $val]); + } + + @header($output_header); + } + } +} +// End of core/Output.php \ No newline at end of file diff --git a/Sleepy/Core/Router.php b/Sleepy/Core/Router.php new file mode 100755 index 0000000..6bdae5b --- /dev/null +++ b/Sleepy/Core/Router.php @@ -0,0 +1,26 @@ +mime)) + { + throw new NotImplementedException("Output types must have a mime type defined."); + } + + if ( ! is_null($data)) + { + $this->data = $data; + } + } + + /** + * Returns the mime type needed + * for the current type + * + * @return string + */ + public function get_mime() + { + return $this->mime; + } + + /** + * Output the data as a string + * + * @return string + */ + public function __toString() + { + return $this->serialize($this->data); + } + +} +// End of core/aType.php \ No newline at end of file diff --git a/Sleepy/Core/iType.php b/Sleepy/Core/iType.php new file mode 100755 index 0000000..6cc8024 --- /dev/null +++ b/Sleepy/Core/iType.php @@ -0,0 +1,37 @@ +data = $data; + } + else + { + $data = $this->data; + } + + if (is_string($data)) return $data; + + // Lets use JSON as an output format if the value isn't scalar + return '
' . json_encode($data, JSON_PRETTY_PRINT) . '
'; + } + + /** + * Convert the encoded data to a native format + * + * @param string $data_string + * @return object + */ + public function unserialize($data_string) + { + return NULL; + } + +} + +// -------------------------------------------------------------------------- +// ! Helper function +// -------------------------------------------------------------------------- + +/** + * Function to simplify type instantiation + * + * @param mixed $data + * @return JSON + */ +function HTML($data = NULL) +{ + return new JSON($data); +} + +// End of types/JSON.php \ No newline at end of file diff --git a/Sleepy/Type/JSON.php b/Sleepy/Type/JSON.php new file mode 100755 index 0000000..b3ed0f1 --- /dev/null +++ b/Sleepy/Type/JSON.php @@ -0,0 +1,77 @@ +data = $data; + } + else + { + $data = $this->data; + } + + return json_encode($data, JSON_PRETTY_PRINT); + } + + /** + * Convert the encoded data to a native format + * + * @param string $data_string + * @return object + */ + public function unserialize($data_string) + { + return json_decode($data_string); + } + +} + +// -------------------------------------------------------------------------- +// ! Helper function +// -------------------------------------------------------------------------- + +/** + * Function to simplify type instantiation + * + * @param mixed $data + * @return JSON + */ +function JSON($data = NULL) +{ + return new JSON($data); +} + +// End of types/JSON.php \ No newline at end of file diff --git a/Sleepy/Type/YAML.php b/Sleepy/Type/YAML.php new file mode 100755 index 0000000..2107267 --- /dev/null +++ b/Sleepy/Type/YAML.php @@ -0,0 +1,64 @@ +data = $data; + } + else + { + $data = $this->data; + } + + // Yaml class doesn't support objects, cast them to arrays + $data = (array) $data; + + return YML::dump($data); + } + + /** + * Convert the encoded data to a native format + * + * @param string $data_string + * @return object + */ + public function unserialize($data_string) + { + return YML::parse($data_string, FALSE, TRUE); + } +} +// End of types/YAML.php \ No newline at end of file diff --git a/application/config/general.php b/application/config/general.php new file mode 100755 index 0000000..0d283d7 --- /dev/null +++ b/application/config/general.php @@ -0,0 +1,28 @@ + 'json', + + // -------------------------------------------------------------------------- + // The default format of data recieved. Can be form data or one of the + // types defined in either the application/types or Sleepy/types folders + // -------------------------------------------------------------------------- + 'default_input_format' => 'json', + +]; + +// End of config/general.php \ No newline at end of file diff --git a/application/config/routes.php b/application/config/routes.php new file mode 100755 index 0000000..e5baebb --- /dev/null +++ b/application/config/routes.php @@ -0,0 +1,23 @@ + 'index', + + +]; + +// End of config/routes.php \ No newline at end of file diff --git a/application/types/index.html b/application/types/index.html new file mode 100755 index 0000000..e69de29 diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..9f0b916 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "authors": [{ + "name": "Timothy J. Warren", + "email": "tim@timshomepage.net", + "homepage": "https://timshomepage.net", + "role": "Developer" + }], + "require-dev": { + "phpunit/phpunit":"3.7.*" + }, + "require": { + "php": ">=5.4.0" + } +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100755 index 0000000..ab3114f --- /dev/null +++ b/index.php @@ -0,0 +1,55 @@ +browser_name_regex = utf8_encode($browser->browser_name_regex); +$o->set_data(['json','html'], [ + '$_SERVER' => $i->server(), + '$_GET' => $i->get(), + '$_POST' => $i->post(), + '$_ENV' => $i->env(), + '$_COOKIE' => $i->cookie(), + 'browser' => $browser, + 'raw headers' => $i->header(), + 'parsed headers' => $i->header_array() +]); + +// End of index.php \ No newline at end of file diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml new file mode 100755 index 0000000..157c498 --- /dev/null +++ b/phpdoc.dist.xml @@ -0,0 +1,22 @@ + + + Sleepy + + docs + + + docs + + +