From: Kevin Day Date: Sat, 17 Dec 2016 20:24:08 +0000 (-0600) Subject: Update: initial import, including incomplete work X-Git-Url: https://git.kevux.org/?a=commitdiff_plain;h=c97071d0c8408b784e2ddc40f2da5f0f8c441c77;p=koopa Update: initial import, including incomplete work I wanted to complete all of the common/base work first, but I ended up getting side-tracked. With the holidays, I realize that I would not finish this in time and I needed to at the very least ensure all of my hard work is not lost. Much of the base work is complete except for the following: - HTML and HTML tag related processing. - Form processing. I have not decided how I want to design forms and I expect this to be the weakest part of my code design. How this will end up being developed will directly affect how the HTML processing code is built. --- diff --git a/README.md b/README.md index 7423663..e5ab236 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ # koopa Open-Source (GPL-3) PHP CMS-like project/library focused on database first originally based on drupal. + +Instead of building a CMS around PHP, this project aims to build a library around database and then extend it into PHP (and possibly more languages). + +Goals of this project: +1) Provide a professional grade open-source project that encourages deviating from core. +2) Build from the database up instead of PHP down. +3) Follow all standards claimed to follow and explicitly communicate all deviations or violations of followed standards. +4) Design in such a way to encourage hacking and personalizing. +5) Provide a consistent coding style and standard. +6) Provide a project not for beginners but instead for intermediate, advanced, and professional level users. +7) Favor read operations at the expense of write operations (an optimization for websites). diff --git a/common/base/classes/base_access.php b/common/base/classes/base_access.php new file mode 100644 index 0000000..ee7bfea --- /dev/null +++ b/common/base/classes/base_access.php @@ -0,0 +1,238 @@ +public = TRUE; + $this->system = FALSE; + $this->user = FALSE; + $this->requester = FALSE; + $this->drafter = FALSE; + $this->editer = FALSE; + $this->reviewer = FALSE; + $this->publisher = FALSE; + $this->manager = FALSE; + $this->administer = FALSE; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->public); + unset($this->system); + unset($this->user); + unset($this->requester); + unset($this->drafter); + unset($this->editer); + unset($this->reviewer); + unset($this->publisher); + unset($this->manager); + unset($this->administer); + } + + /** + * Assign a role. + * + * When role is set to NONE, and value is TRUE, then all roles are set to FALSE. + * When role is set to NONE and value is FALSE, nothing happens. + * + * @param int $role + * The role id to assign. + * @param bool $value + * Set the role value to TRUE/FALSE. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_role($role, $value) { + if (!is_int($role) || !is_bool($value)) { + return c_base_return_error::s_false(); + } + + if ($role === self::NONE) { + if ($value) { + $this->public = FALSE; + $this->system = FALSE; + $this->user = FALSE; + $this->requester = FALSE; + $this->drafter = FALSE; + $this->editer = FALSE; + $this->reviewer = FALSE; + $this->publisher = FALSE; + $this->manager = FALSE; + $this->administer = FALSE; + } + } + elseif ($role === self::PUBLIC) { + $this->public = $value; + } + elseif ($role === self::SYSTEM) { + $this->system = $value; + } + elseif ($role === self::USER) { + $this->user = $value; + } + elseif ($role === self::REQUESTER) { + $this->requester = $value; + } + elseif ($role === self::DRAFTER) { + $this->drafter = $value; + } + elseif ($role === self::EDITER) { + $this->editer = $value; + } + elseif ($role === self::REVIEWER) { + $this->reviewer = $value; + } + elseif ($role === self::PUBLISHER) { + $this->publisher = $value; + } + elseif ($role === self::MANAGER) { + $this->manager = $value; + } + elseif ($role === self::ADMINISTER) { + $this->administer = $value; + } + else { + return new c_base_return_false(); + } + + return new c_base_return_true(); + } + + /** + * Get the current status of the specified role. + * + * When role is set to NONE, TRUE is returned when all values are set to FALSE. + * When role is set to NONE, FALSE is returned when any values are set to TRUE. + * + * @param int $role + * The role id to get the value of. + * + * @return c_base_return_status + * TRUE on enabled, FALSE on disabled. + * FALSE with error bit set is returned on error. + */ + public function get_role($role) { + if (!is_int($role)) { + return c_base_return_error::s_false(); + } + + if ($role === self::NONE) { + if (!($this->public || $this->system || $this->user || $this->requester || $this->drafter || $this->editer || $this->reviewer || $this->publisher || $this->manager || $this->administer)) { + return new c_base_return_true(); + } + } + elseif ($role === self::PUBLIC) { + if ($this->public) { + return new c_base_return_true(); + } + } + elseif ($role === self::SYSTEM) { + if ($this->system) { + return new c_base_return_true(); + } + } + elseif ($role === self::USER) { + if ($this->user) { + return new c_base_return_true(); + } + } + elseif ($role === self::REQUESTER) { + if ($this->requester) { + return new c_base_return_true(); + } + } + elseif ($role === self::DRAFTER) { + if ($this->drafter) { + return new c_base_return_true(); + } + } + elseif ($role === self::EDITER) { + if ($this->editer) { + return new c_base_return_true(); + } + } + elseif ($role === self::REVIEWER) { + if ($this->reviewer) { + return new c_base_return_true(); + } + } + elseif ($role === self::PUBLISHER) { + if ($this->publisher) { + return new c_base_return_true(); + } + } + elseif ($role === self::MANAGER) { + if ($this->manager) { + return new c_base_return_true(); + } + } + elseif ($role === self::ADMINISTER) { + if ($this->administer) { + return new c_base_return_true(); + } + } + + return new c_base_return_false(); + } +} diff --git a/common/base/classes/base_ascii.php b/common/base/classes/base_ascii.php new file mode 100644 index 0000000..e926520 --- /dev/null +++ b/common/base/classes/base_ascii.php @@ -0,0 +1,139 @@ + self::ISO_8859_16) { + return new c_base_return_false(); + } + + return new c_base_return_true(); + } + + /** + * Convert the given code to a string. + * + * @param int $charset + * The integer to convert. + * + * @return c_base_return_string|c_base_return_status + * The string is returned on success. + * FALSE with error bit set is returned on error. + */ + public static function s_to_string($charset) { + if (!is_int($charset)) { + return c_base_return_error::s_false(); + } + + switch ($charset) { + case self::UTF_8: + return c_base_return_string::s_new('UTF-8'); + case self::ASCII: + return c_base_return_string::s_new('ASCII'); + case self::UTF_16: + return c_base_return_string::s_new('UTF-16'); + case self::UTF_32: + return c_base_return_string::s_new('UTF-32'); + case self::ISO_8859_1: + return c_base_return_string::s_new('ISO-8859-1'); + case self::ISO_8859_2: + return c_base_return_string::s_new('ISO-8859-2'); + case self::ISO_8859_3: + return c_base_return_string::s_new('ISO-8859-3'); + case self::ISO_8859_4: + return c_base_return_string::s_new('ISO-8859-4'); + case self::ISO_8859_5: + return c_base_return_string::s_new('ISO-8859-5'); + case self::ISO_8859_6: + return c_base_return_string::s_new('ISO-8859-6'); + case self::ISO_8859_7: + return c_base_return_string::s_new('ISO-8859-7'); + case self::ISO_8859_8: + return c_base_return_string::s_new('ISO-8859-8'); + case self::ISO_8859_9: + return c_base_return_string::s_new('ISO-8859-9'); + case self::ISO_8859_10: + return c_base_return_string::s_new('ISO-8859-10'); + case self::ISO_8859_11: + return c_base_return_string::s_new('ISO-8859-11'); + case self::ISO_8859_12: + return c_base_return_string::s_new('ISO-8859-12'); + case self::ISO_8859_13: + return c_base_return_string::s_new('ISO-8859-13'); + case self::ISO_8859_14: + return c_base_return_string::s_new('ISO-8859-14'); + case self::ISO_8859_15: + return c_base_return_string::s_new('ISO-8859-15'); + case self::ISO_8859_16: + return c_base_return_string::s_new('ISO-8859-16'); + } + + return c_base_return_error::s_false(); + } +} diff --git a/common/base/classes/base_cookie.php b/common/base/classes/base_cookie.php new file mode 100644 index 0000000..8cd55b7 --- /dev/null +++ b/common/base/classes/base_cookie.php @@ -0,0 +1,846 @@ +name = NULL; + $this->secure = TRUE; + $this->max_age = NULL; + $this->expires = NULL; + $this->path = self::DEFAULT_PATH; + $this->domain = NULL; + $this->http_only = FALSE; + $this->first_only = TRUE; + $this->value = array(); + + $this->p_set_lifetime_default(); + + parent::__construct(); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->name); + unset($this->secure); + unset($this->max_age); + unset($this->expires); + unset($this->path); + unset($this->domain); + unset($this->http_only); + unset($this->first_only); + unset($this->value); + + parent::__destruct(); + } + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, array()); + } + + /** + * Assigns the cookie name. + * + * @param string $name + * The cookie name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_name($name) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + if (mb_strlen($name) == 0 || preg_match('/^(\w|-)+$/iu', $name) != 1) { + return c_base_return_error::s_false(); + } + + $this->name = preg_replace('/(^\s+)|(\s+$)/us', '', rawurlencode($name)); + return new c_base_return_true(); + } + + /** + * Returns the stored cookie name. + * + * @return c_base_return_string + * The cookie name string or NULL if undefined. + */ + public function get_name() { + return c_base_return_string::s_new($this->name); + } + + /** + * Assigns the cookie secure flag. + * + * This tells the browser that the cookie should only be used in SSL/HTTPS communications. + * This is best left enabled for security reasons. + * + * @param bool $secure + * The security flag for the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_secure($secure) { + if (!is_bool($secure)) { + return c_base_return_error::s_false(); + } + + $this->secure = $secure; + return new c_base_return_true(); + } + + /** + * Returns the stored cookie secure flag. + * + * @return c_base_return_bool + * The cookie secure flag setting. + */ + public function get_secure() { + // this flag should never be undefined, if it is NULL, then force the default. + if (is_null($this->secure)) { + $this->secure = TRUE; + } + + return c_base_return_bool::s_new($this->secure); + } + + /** + * Assigns the expires timestamp of the cookie. + * + * This tells the browser until what date the cookie will be valid for. + * This cannot be higher than php's 'session.cookie_lifetime' value. + * If not specified, then the default is to use PHP's 'session.cookie_lifetime' based on when this class was created. + * + * A value of 0 in both max age and expires will designate a session cookie. + * + * @param int|null $expires + * A unix timestamp representing the date in which this cookie expires. + * A value of NULL disabled the usage of expires. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: self::set_max_age() + */ + public function set_expires($expires) { + if (!is_null($expires) && (!is_int($expires) || $this->expires < 0)) { + if (is_string($max_age) && is_numeric($expires)) { + $expires = (int) $expires; + + if ($expires < 0) { + return c_base_return_error::s_false(); + } + } + else { + return c_base_return_error::s_false(); + } + } + + $this->expires = $expires; + return new c_base_return_true(); + } + + /** + * Returns the stored cookie expires timestamp. + * + * @return c_base_return_int + * The expiration unix timestamp for the cookie. + * + * @see: self::get_max_age() + */ + public function get_expires() { + if (is_null($this->expires) && is_null($this->max_age)) { + $this->p_set_lifetime_default(); + } + + return c_base_return_int::s_new($this->expires); + } + + /** + * Assigns the max age of the cookie. + * + * This tells the browser how long the cookie will be valid for. + * This cannot be higher than php's 'session.cookie_lifetime' value. + * If not specified, then the default is to use PHP's 'session.cookie_lifetime' based on when this class was created. + * + * If max age is specified, but expires is not, then expires will be auto-calculated and provided for compatibility with old clients. + * + * A value of 0 in both max age and expires will designate a session cookie. + * + * @param int|null $max_age + * Number of seconds (max-age cookie flag). + * A value of NULL disabled the usage of max age. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: self::set_expires() + */ + public function set_max_age($max_age) { + if (!is_null($max_age) && (!is_int($max_age) || $this->max_age < 0)) { + if (is_string($max_age) && is_numeric($max_age)) { + $max_age = (int) $max_age; + + if ($max_age < 0) { + return c_base_return_error::s_false(); + } + } + else { + return c_base_return_error::s_false(); + } + } + + $this->max_age = $max_age; + + return new c_base_return_true(); + } + + /** + * Returns the stored cookie max age value. + * + * @return c_base_return_int + * The expiration unix timestamp for the cookie. + * FALSE (without error bit) is returned if there is no value as per $timestamp parameter. + * + * @see: self::get_expires() + */ + public function get_max_age() { + if (is_null($this->expires) && is_null($this->max_age)) { + $this->p_set_lifetime_default(); + } + + return c_base_return_int::s_new($this->max_age); + } + + /** + * Assigns the cookie path. + * + * @param string $path + * The path for the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_path($path) { + if (!is_string($path) || empty($path)) { + return c_base_return_error::s_false(); + } + + // sanitize the path string, only allowing the path portion of the url. + $parsed = parse_url($path, PHP_URL_PATH); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + $this->path = preg_replace('/(^\s+)|(\s+$)/us', '', $parsed); + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Returns the stored cookie path. + * + * @return c_base_return_string + * The cookie path. + */ + public function get_path() { + // this flag should never be undefined, if it is NULL, then force the default. + if (is_null($this->path)) { + $this->path = self::DEFAULT_PATH; + } + + return c_base_return_string::s_new($this->path); + } + + /** + * Assigns the cookie domain. + * + * @param string $domain + * The domain for the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_domain($domain) { + if (!is_string($domain) || empty($domain)) { + return c_base_return_error::s_false(); + } + + // sanitize the domain string, only allowing the host portion of the url. + $parsed = parse_url('stub://' . $domain, PHP_URL_HOST); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + $this->domain = preg_replace('/(^\s+)|(\s+$)/us', '', $parsed); + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Returns the stored cookie domain. + * + * @return c_base_return_string + * The cookie domain or null if undefined. + */ + public function get_domain() { + return c_base_return_string::s_new($this->domain); + } + + /** + * Assigns the cookie http only flag. + * + * Set this to TRUE to only allow http protocol to utilize this cookie. + * According to the PHP documentation, this prohibits javascript from accessing the cookie. + * + * @param bool $http_only + * The http-only status for the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_http_only($http_only) { + if (!is_bool($http_only)) { + return c_base_return_error::s_false(); + } + + $this->http_only = $http_only; + return new c_base_return_true(); + } + + /** + * Returns the stored cookie http only flag. + * + * @return c_base_return_bool + * The cookie http only flag. + */ + public function get_http_only() { + // this flag should never be undefined, if it is NULL, then force the default. + if (is_null($this->http_only)) { + $this->http_only = FALSE; + } + + return c_base_return_bool::s_new($this->http_only); + } + + /** + * Assigns the cookie http firsty-party flag. + * + * Set this to TRUE to only allow the cookie to be used as first party only. + * According to the PHP documentation, this tells browsers to never allow this to be used as a thirdy-party cookie. + * + * @param bool $first_only + * The first-only status for the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_first_only($first_only) { + if (!is_bool($first_only)) { + return c_base_return_error::s_false(); + } + + $this->first_only = $first_only; + return new c_base_return_true(); + } + + /** + * Returns the stored cookie http only flag. + * + * @return c_base_return_bool + * The cookie http only flag. + */ + public function get_first_only() { + // this flag should never be undefined, if it is NULL, then force the default. + if (is_null($this->http_only)) { + $this->http_only = FALSE; + } + + return c_base_return_bool::s_new($this->http_only); + } + + /** + * Assign the value. + * + * Cookies values associated with this class are only stored as an array. + * Be sure to wrap your values in an array. + * The array key 'checksum' will be created if one does not already exist when building the cookie. + * + * This overrides the parent class function of set_value(). + * Expect return values to be of the form c_base_return_*. + * + * @param array $value + * Any value so long as it is an array. + * NULL is not allowed. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_array($value)) { + return c_base_return_error::s_false(); + } + + $this->value = $value; + return new c_base_return_true(); + } + + /** + * Return the value. + * + * This overrides the parent class function of set_value(). + * Expect return values to be of the form c_base_return_*. + * + * @return c_base_return_array $value + * The value array stored within this class. + * NULL may be returned if there is no defined valid array. + */ + public function get_value() { + if (!is_null($this->value) && !is_array($this->value)) { + $this->value = array(); + } + + return c_base_return_array::s_new($this->value); + } + + /** + * Return the value of the expected type. + * + * @return array $value + * The value array stored within this class. + */ + public function get_value_exact() { + // the value should not be changed into c_base_return_array because 'exact' should consistently mean 'exact'. + return parent::get_value_exact(); + } + + /** + * Return the value at a specific index in the array. + * + * This overrides the parent class function. + * Expect return values to be of the form c_base_return_*. + * + * @param string|null $key + * A specific array key to load. + * @param string|null $type + * (optional) Perform sanity checking of return value based on a known type. + * + * If not provided, then a 'raw' type of c_base_return_value is returned. + * - This may contain things like a normal PHP TRUE or FALSE. + * + * Supported known types: + * 'bool': a boolean value, returns c_base_return_value. + * 'int': an integer value, returns c_base_return_int. + * 'float': a float value, returns c_base_return_float. + * 'string': a string value, return c_base_return_string. + * 'array': an array value, return c_base_return_array. + * 'object': an object value, return c_base_return_object. + * 'resource': a generic resource value, return c_base_return_resource. + * 'stream': a stream resource value, return c_base_return_stream. + * 'socket': a socket resource value, return c_base_return_socket. + * 'null': a NULL value, return c_base_return_value. + * + * @return c_base_return_status|c_base_return_value + * A c_base_return_array is returned when no $key is defined. + * A c_base_return_value, or more specific, is returned if $type is provided for known types. + * FALSE with error bit set is returned on error. + */ + public function get_value_at($key, $type = NULL) { + if (!is_string($key) || empty($key)) { + return c_base_return_error::s_false(); + } + + // if type is supplied, it must be string. + if (!is_null($type) && (!is_string($type) || empty($type))) { + return c_base_return_error::s_false(); + } + + if (!is_array($this->value)) { + $this->value = array(); + } + + if (!array_key_exists($key, $this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($type)) { + if ($type == 'bool') { + if (is_bool($this->value[$key])) { + return c_base_return_bool::s_new($this->value[$key]); + } + } + elseif ($type == 'int') { + if (is_int($this->value[$key])) { + return c_base_return_int::s_new($this->value[$key]); + } + } + elseif ($type == 'float') { + if (is_float($this->value[$key])) { + return c_base_return_float::s_new($this->value[$key]); + } + } + elseif ($type == 'string') { + if (is_string($this->value[$key])) { + return c_base_return_string::s_new($this->value[$key]); + } + } + elseif ($type == 'array') { + if (is_array($this->value[$key])) { + return c_base_return_array::s_new($this->value[$key]); + } + } + elseif ($type == 'object') { + if (is_object($this->value[$key])) { + return c_base_return_object::s_new($this->value[$key]); + } + } + elseif ($type == 'resource') { + if (is_resource($this->value[$key])) { + return c_base_return_resource::s_new($this->value[$key]); + } + } + elseif ($type == 'stream') { + if (is_stream($this->value[$key])) { + return c_base_return_stream::s_new($this->value[$key]); + } + } + elseif ($type == 'socket') { + if (is_socket($this->value[$key])) { + return c_base_return_socket::s_new($this->value[$key]); + } + } + elseif ($type == 'null') { + if (is_null($this->value[$key])) { + return c_base_return_value::s_new($this->value[$key]); + } + } + + return c_base_return_error::s_false(); + } + + return c_base_return_value::s_new($this->value[$key]); + } + + /** + * Save the cookie to the HTTP headers for sending to the client. + * + * This function sends an HTTP header and therefore should only be used when ready to send headers. + * + * Both name and value are required to be set before calling this function. + * + * The functions setcookie() and setrawcookie() do not provide advanced customization. + * Instead of using those functions, use header() to directly generate the cookie. + * + * @param bool $checksum + * When set to TRUE, the array will be converted to a json string and have a checksum created for it. + * This checksum value will then be placed inside the array and a final json string will be submitted. + * + * Warning: any top-level key in the array with the name of 'checksum' will be lost when using this. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: self::validate() + * @see: setcookie() + * @see: setrawcookie() + * @see: header() + */ + public function do_push($checksum = TRUE) { + if (is_null($this->name) || is_null($this->value)) { + c_base_return_error::s_false(); + } + + if ($checksum) { + unset($this->value['checksum']); + $this->value['checksum'] = $this->p_build_checksum(); + + if (is_null($this->value['checksum'])) { + unset($this->value['checksum']); + return c_base_return_error::s_false(); + } + } + + // @todo: consider adding support for assigning the json depth setting. + $json = json_encode($this->value); + if ($json === FALSE) { + unset($json); + return c_base_return_error::s_false(); + } + + $value = rawurlencode(preg_replace('/(^\s+)|(\s+$)/us', '', $json)); + unset($json); + + //$result = setrawcookie($this->name, $value, $this->max_age, $this->path, $this->domain, $this->secure, $this->http_only); + $cookie = 'Set-Cookie: ' . rawurlencode($this->name) . '=' . $value . ';'; + + if (!is_null($this->domain)) { + $cookie .= ' domain=' . $this->domain . ';'; + } + + if (!is_null($this->path)) { + $cookie .= ' path=' . $this->path . ';'; + } + + if (!is_null($this->max_age)) { + $cookie .= ' max-age=' . $this->max_age . ';'; + + // provide an expires for compatibility purposes if one is not specified. + if (is_null($this->expires)) { + $cookie .= ' expires=' . gmdate('D, d-M-Y H:i:s T', strtotime('+' . $this->max_age . ' seconds')) . ';'; + } + } + + if (!is_null($this->expires)) { + $cookie .= ' expires=' . gmdate('D, d-M-Y H:i:s T', $this->expires) . ';'; + } + + if ($this->secure) { + $cookie .= ' secure;'; + } + + if ($this->http_only) { + $cookie .= ' httponly;'; + } + + if ($this->first_only) { + $cookie .= ' first-party;'; + } + + header($cookie, FALSE); + + unset($cookie); + unset($value); + + return new c_base_return_true(); + } + +/** + * Deletes the cookie by setting both the expires and max-age to -1. + * + * This does not need to be called when updating the cookie. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: self::push() + */ + public function delete() { + $original_max_age = $this->max_age; + $original_expires = $this->expires; + + $this->max_age = -1; + $this->expires = -1; + + $result = $this->push(FALSE); + + $this->max_age = $original_max_age; + $this->expires = $original_expires; + + unset($original_max_age); + unset($original_expires); + + return $result; + } + + /** + * Retrieve the cookie from the HTTP headers sent by the client. + * + * This class object will be populated with the cookies settings. + * The cookie value will be cleared if the cookie exists. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function do_pull() { + if (!isset($_COOKIE) || !array_key_exists($this->name, $_COOKIE)) { + // This is not an error, but there is no cookie to pull. + // simply return false without the error flag set. + return new c_base_return_false(); + } + + $json = rawurldecode($_COOKIE[$this->name]); + $value = json_decode($json, TRUE); + unset($json); + + if ($value === FALSE) { + return c_base_return_error::s_false(); + } + + $this->value = $value; + unset($value); + + return new c_base_return_true(); + } + + /** + * Assigns a default value for the expiration based on php's session.cookie_lifetime. + */ + private function p_set_lifetime_default() { + $lifetime = ini_get('session.cookie_lifetime'); + if ($lifetime <= 0) { + $lifetime = self::DEFAULT_LIFETIME; + } + + $this->max_age = $lifetime; + unset($lifetime); + } + + /** + * Validate a checksum key. + * + * This is only meaningful when called after self::do_pull() is used. + * + * If a checksum key exists, will validate that the contents of the value are consistent with the checksum. + * This is useful to protect data from alterations, be it defect or accident. + * This does not protect against malicious activities because the malicious user could simply regenerate the checksum after their changes. + * + * @return c_base_return_status + * TRUE when the checksum validates, FALSE when the checksum fails or there is no checksum. + * On error FALSE is returned with the error bit set. + */ + public function validate() { + if (!is_array($this->value)) { + return c_base_return_error::s_false(); + } + + if (!array_key_exists('checksum', $this->value)) { + return new c_base_return_false(); + } + + $checksum = $this->p_build_checksum(); + if ($this->value['checksum'] == $checksum) { + unset($checksum); + return new c_base_return_true(); + } + unset($checksum); + + return new c_base_return_false(); + } + + /** + * Builds a checksum of the value array. + * + * This does not assign the checksum to the array. + * The checksum is only assigned by the do_push() or do_pull() functions. + * + * If the values are changed after this call, then this checksum will be invalid. + * + * @see: self::do_pull() + * @see: self::do_push() + */ + public function build_checksum() { + $checksum = $this->p_build_checksum(); + if (is_string($checksum)) { + return c_base_return_string::s_new($checksum); + } + unset($checksum); + + return c_base_return_error::s_false(); + } + + /** + * Generates a checksum of the value array. + * + * Any existing checksum key is preserved. + * + * @return string + * A generated checksum. + * + * @see: hash() + */ + private function p_build_checksum() { + if (!is_array($this->value)) { + $this->value = array(); + } + + $has_checksum = array_key_exists('checksum', $this->value); + $checksum = NULL; + if ($has_checksum) { + $checksum = $this->value['checksum']; + unset($this->value['checksum']); + } + + $json = json_encode($this->value); + if ($json === FALSE) { + if ($has_checksum) { + $this->value['checksum'] = $checksum; + } + + unset($has_checksum); + unset($checksum); + unset($json); + return NULL; + } + + $generated = hash(c_base_cookie::CHECKSUM_ALGORITHM, $json); + if ($has_checksum) { + $this->value['checksum'] = $checksum; + } + + unset($has_checksum); + unset($checksum); + unset($json); + + return $generated; + } +} diff --git a/common/base/classes/base_database.php b/common/base/classes/base_database.php new file mode 100644 index 0000000..19360c0 --- /dev/null +++ b/common/base/classes/base_database.php @@ -0,0 +1,2711 @@ +host = NULL; + $this->host_addr = NULL; + $this->port = NULL; + $this->dbname = NULL; + $this->user = NULL; + $this->password = NULL; + $this->connect_timeout = NULL; + $this->options = NULL; + $this->ssl_mode = NULL; + $this->service = NULL; + + $this->error = NULL; + + parent->__construct(); + } + + /** + * Class destructor. + */ + public function __destruct() { + $this->clear(); + + unset($this->host); + unset($this->host_addr); + unset($this->port); + unset($this->dbname); + unset($this->user); + unset($this->password); + unset($this->connect_timeout); + unset($this->options); + unset($this->ssl_mode); + unset($this->service); + + unset($this->error); + + parent->__destruct(); + } + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign host information. + * + * @param string $host + * Host information string, such as hostname or ip address. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_host($host) { + if (!is_string($host)) { + return c_base_return_error::s_false(); + } + + $this->host = $host; + return new c_base_return_true(); + } + + /** + * Returns the host information. + * + * @return c_base_return_string + * The host information string. + */ + public function get_host() { + if (!is_string($this->host)) { + $this->host = ''; + } + + return c_base_return_string::s_new($this->host); + } + + /** + * Assign host address information. + * + * @param string $host_addr + * Host address information string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_host_addr($host_addr) { + if (!is_string($host_addr)) { + return c_base_return_error::s_false(); + } + + $this->host_addr = $host_addr; + return new c_base_return_true(); + } + + /** + * Returns the host address information. + * + * @return c_base_return_string + * The host address information string. + */ + public function get_host_addr() { + if (!is_string($this->host_addr)) { + $this->host_addr = ''; + } + + return c_base_return_string::s_new($this->host_addr); + } + + /** + * Assign port number. + * + * @param int $port + * Port number. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_port($port) { + if (!is_int($port)) { + if (is_string($port) && is_numeric($port)) { + $port = (int) $port; + if ($port < 0) { + return c_base_return_error::s_false(); + } + } + else { + return c_base_return_error::s_false(); + } + } + + $this->port = $port; + return new c_base_return_true(); + } + + /** + * Returns the port number. + * + * @return c_base_return_int + * The port number. + */ + public function get_port() { + if (!is_int($this->port)) { + $this->port = 0; + } + + return c_base_return_int::s_new($this->port); + } + + /** + * Assign database name. + * + * @param string $dbname + * The database name string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_dbname($dbname) { + if (!is_string($dbname)) { + return c_base_return_error::s_false(); + } + + $this->dbname = $dbname; + return new c_base_return_true(); + } + + /** + * Returns the database name. + * + * @return c_base_return_string + * The database name string. + */ + public function get_dbname() { + if (!is_string($this->dbname)) { + $this->dbname = ''; + } + + return c_base_return_string::s_new($this->dbname); + } + + /** + * Assign user name. + * + * @param string $user + * The user name string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_user($user) { + if (!is_string($user)) { + return c_base_return_error::s_false(); + } + + $this->user = $user; + return new c_base_return_true(); + } + + /** + * Returns the user name. + * + * @return c_base_return_string + * The user name string. + */ + public function get_user() { + if (!is_string($this->user)) { + $this->user = ''; + } + + return c_base_return_string::s_new($this->user); + } + + /** + * Assign password. + * + * @param string|null $password + * The password string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_password($password) { + if (!is_null($password) && !is_string($password)) { + return c_base_return_error::s_false(); + } + + $this->password = $password; + return new c_base_return_true(); + } + + /** + * Returns the password. + * + * @return c_base_return_string|c_base_return_false + * The password string. + * FALSE is returned if there is no assigned password. + */ + public function get_password() { + if (is_null($this->password)) { + return new c_base_return_false(); + } + + if (!is_string($this->password)) { + $this->password = ''; + } + + return c_base_return_string::s_new($this->password); + } + + /** + * Assign connect timeout. + * + * @param int $connect_timeout + * Connect timeout number. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_connect_timeout($connect_timeout) { + if (!is_int($connect_timeout)) { + if (is_string($connect_timeout) && is_numeric($connect_timeout)) { + $connect_timeout = (int) $connect_timeout; + if ($connect_timeout < 0) { + return c_base_return_error::s_false(); + } + } + else { + return c_base_return_error::s_false(); + } + } + + $this->connect_timeout = $connect_timeout; + return new c_base_return_true(); + } + + /** + * Returns the connect timeout. + * + * @return c_base_return_int + * The connect timeout number. + */ + public function get_connect_timeout() { + if (!is_int($this->connect_timeout)) { + $this->connect_timeout = 0; + } + + return c_base_return_int::s_new($this->connect_timeout); + } + + /** + * Assign options information. + * + * @param string $options + * Options information string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_options($options) { + if (!is_string($options)) { + return c_base_return_error::s_false(); + } + + $this->options = $options; + return new c_base_return_true(); + } + + /** + * Returns the options information. + * + * @return c_base_return_string + * The options information string. + */ + public function get_options() { + if (!is_string($this->options)) { + $this->options = ''; + } + + return c_base_return_string::s_new($this->options); + } + + /** + * Assign ssl mode information. + * + * @param string $ssl_mode + * SSL Mode information string. + * One of the following: + * - 'disable' + * - 'allow' + * - 'prefer' + * - 'require' + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_ssl_mode($ssl_mode) { + if (!is_string($ssl_mode)) { + return c_base_return_error::s_false(); + } + + $this->ssl_mode = $ssl_mode; + return new c_base_return_true(); + } + + /** + * Returns the ssl mode information. + * + * @return c_base_return_string + * The ssl mode information string. + */ + public function get_ssl_mode() { + if (!is_string($this->ssl_mode)) { + $this->ssl_mode = ''; + } + + return c_base_return_string::s_new($this->ssl_mode); + } + + /** + * Assign service information. + * + * @param string $service + * Service information string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_service($service) { + if (!is_string($service)) { + return c_base_return_error::s_false(); + } + + $this->service = $service; + return new c_base_return_true(); + } + + /** + * Returns the service information. + * + * @return c_base_return_string + * The service information string. + */ + public function get_service() { + if (!is_string($this->service)) { + $this->service = ''; + } + + return c_base_return_string::s_new($this->service); + } + + /** + * Builds the connection string based on the current settings. + * + * The built string is stored inside of this objects 'value' parameter. + */ + public function build() { + $this->value = ''; + + if (!empty($this->host)) { + $this->value .= ' host=' . $this->p_escape_string($this->host); + } + + if (!empty($this->host_addr)) { + $this->value .= ' host_addr=' . $this->p_escape_string($this->host_addr); + } + + if (!empty($this->port)) { + $this->value .= ' port=' . $this->p_escape_string($this->port); + } + + if (!empty($this->dbname)) { + $this->value .= ' dbname=' . $this->p_escape_string($this->dbname); + } + + if (!empty($this->user)) { + $this->value .= ' user=' . $this->p_escape_string($this->user); + } + + if (!empty($this->password)) { + $this->value .= ' password=' . $this->p_escape_string($this->password); + } + + if (!empty($this->connect_timeout)) { + $this->value .= ' connect_timeout=' . $this->p_escape_string($this->connect_timeout); + } + + if (!empty($this->options)) { + $this->value .= ' options=' . $this->p_escape_string($this->options); + } + + if (!empty($this->ssl_mode)) { + $this->value .= ' sslmode=' . $this->p_escape_string($this->ssl_mode); + } + + if (!empty($this->service)) { + $this->value .= ' service=' . $this->p_escape_string($this->service); + } + } + + /** + * Clears the connection string. + * + * This should be cleared after use because the string generally contains a password. + * Uses an unproven technique in an attempt to 'delete' the string from memory and then unallocating the resource. + * + * This does not perform the garbage collection, but it is suggested that the caller consider calling gc_collect_cycles(). + * + * @see: gc_collect_cycles() + */ + public function clear() { + $this->value = str_repeat(' ', self::DATA_CLEAR_TEXT_LENGTH); + unset($this->value); + } + + /** + * Escape the postgresql connection string. + * + * According to the documentation, both ' and \ must be escaped with a \. + * + * @param string string + * The string to escape. + * + * @return string + * The escaped string. + */ + private function p_escape_string($string) { + $escaped = str_replace('\\', '\\\\', $string); + $escaped = str_replace('\'', '\\\'', $escaped); + + return $escaped; + } +} + +/** + * A generic class for managing database connections. + * + * errata: + * There are a number of cases where PHP's postgresql documentation is unclear on what 'failure' is. + * In certain cases, it mentions failure as an error. + * In other cases, it just says failure. + * If failure happens as a part of normal, expected behavior, then it should not be construed as an error because failure is expected behavior. + * If failure happens due to something unexpected or invalid, then it should be construed as an error. + * This is a context issue and I may be overthinking it. + * I will need to come back later and review my return results. + * For now, I am assuming failure means error as that seems like the most obvious interpretation. + * + * @require class c_base_return + * @require class c_base_session + */ +class c_base_database { + private $session; + private $persistent; + private $database; + private $connection_string; + private $asynchronous; + + private $connected; + + /** + * Class constructor. + */ + public function __construct() { + $this->session = NULL; + $this->persistent = NULL; + $this->database = NULL; + $this->connection_string = NULL; + $this->asynchronous = NULL; + + $this->connected = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + if (is_resource($this->database)) { + $this->do_disconnect(); + } + + if (is_object($this->connection_string) && $this->connection_string instanceof c_base_connection_string) { + $this->connection_string->clear(); + } + + unset($this->session); + unset($this->persistent); + unset($this->database); + unset($this->connection_string); + + unset($this->connected); + } + + /** + * Assign a session to the database. + * + * @param c_base_session $session + * An already processed and configured session object. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_session($session) { + if (!is_object($session) || !($session instanceof c_base_session)) { + return c_base_return_error::s_false(); + } + + $this->session = $session; + return new c_base_return_true(); + } + + /** + * Returns the session information. + * + * @return c_base_session_return + */ + public function get_session() { + if (!is_object($session) || !($session instanceof c_base_session)) { + $this->session = new c_base_session(); + } + + return c_base_session_return::s_value_exact($this->session); + } + + /** + * Assign a connection string to the database. + * + * @param c_base_connection_string $connection_string + * An already processed and configured connection string object. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_connection_string($connection_string) { + if (!is_object($connection_string) || !($connection_string instanceof c_base_connection_string)) { + return c_base_return_error::s_false(); + } + + $this->connection_string = $connection_string; + return new c_base_return_true(); + } + + /** + * Returns the connection string. + * + * @return c_base_connection_string_return + */ + public function get_connection_string() { + if (!is_object($connection_string) || !($connection_string instanceof c_base_connection_string)) { + $this->connection_string = new c_base_connection_string(); + } + + return $this->connection_string; + } + + /** + * Enable or Disable a persistent database connection. + * + * When using persistent connections, make sure that there is at least 2*max_connections of shared_buffers. + * Where max_connections is the number of connections you intend to allow for all systems and services to access the database. + * + * PHP's php.ini option 'pgsql.max_persistent' should be used to control the number of persistent connection limits (with -1 being infinite). + * + * If a persistent connection is created, then it cannot be closed with the normal close function. + * However, the close() function still cleans up some resources related to persistent connection and should still be executed. + * + * Avoid using persistent connection for cases where there is a table lock or transaction in use. + * + * @param bool $persistent + * TRUE to enable a persistent connection, FALSE otherwise. + * + * @param c_base_return_status + * TRUE on success, FALSE otherwise + */ + public function set_persistent($persistent) { + if (!is_bool($persistent)) { + return c_base_return_error::s_false(); + } + + $this->persistent = $persistent; + + return new c_base_return_true(); + } + + /** + * Return the persistent connection status. + * + * @return c_base_return_status + * TRUE on enabled, FALSE on disabled. + */ + public function get_persistent() { + if (!is_bool($this->persistent)) { + $this->persistent = FALSE; + } + + if ($this->persistent) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Enable or Disable an asynchronous database connection. + * + * @param bool $asynchronous + * TRUE to enable a asynchronous connection, FALSE otherwise. + * + * @param c_base_return_status + * TRUE on success, FALSE otherwise + */ + public function set_asynchronous($asynchronous) { + if (!is_bool($asynchronous)) { + return c_base_return_error::s_false(); + } + + $this->asynchronous = $asynchronous; + } + + /** + * Return the asynchronous connection status. + * + * @return c_base_return_status + * TRUE on enabled, FALSE on disabled. + */ + public function get_asynchronous() { + if (!is_bool($this->asynchronous)) { + $this->asynchronous = FALSE; + } + + if ($this->asynchronous) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Open the connection to the database. + * + * This will create a new connection if persistent connection is not enabled. + * + * @param bool $force + * (optional) When TRUE, passes PGSQL_CONNECT_FORCE_NEW to force a new connection to be made. + * + * @param c_base_return_status + * TRUE on success, FALSE otherwise + * c_base_return_true is returned if the database is connected. + * c_base_return_false is returned if the database is disconnected. + * The error flag is set if there is a problem. + * If the database is already connected when this is called, c_base_return_true is returned with the error flag set. + * + * @see: pg_connect() + */ + public function do_connect($force = FALSE) { + if (!is_bool($force)) { + return c_base_return_error::s_false(); + } + + if (is_null($this->connection_string)) { + return c_base_return_error::s_false(); + } + + if (is_resource($this->database)) { + return c_base_return_error::s_true(); + } + + $type = 0; + if ($force) { + $type += PGSQL_CONNECT_FORCE_NEW; + } + + if ($this->asynchronous) { + $type += PGSQL_CONNECT_ASYNC; + } + + // make sure the connection string is built before using. + $this->connection_string->build(); + + // Both pg_connect() and pg_pconnect() throw errors and the functions do not support try {} .. catch { statements. + // the only way to prevent unwanted error reporting (allowing the caller to the reporting) is to use @. + // The @ is considered bad practice, but there is no alternative in this case. + if ($this->persistent) { + $database = @pg_pconnect($this->connection_string->get_value_exact(), $type); + } + else { + $database = @pg_connect($this->connection_string->get_value_exact(), $type); + } + + unset($type); + if ($database === FALSE) { + unset($database); + return c_base_return_error::s_false(); + } + + $this->database = $database; + unset($database); + + // set the default encoding to unicode. + pg_set_client_encoding($this->database, 'UTF8'); + + $this->connected = TRUE; + return new c_base_return_true(); + } + + /** + * Close the connection to the database. + * + * The PHP documentation states the following: + * If there is open large object resource on the connection, do not close the connection before closing all large object resources. + * + * @param c_base_return_status + * TRUE on success, FALSE otherwise + * + * @see: pg_close() + */ + public function do_disconnect() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (pg_close($this->database)) { + $this->connected = FALSE; + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } + + /** + * Flush outbound query data. + * + * @return c_base_return_status + * TRUE on success, FALSE on partial flush. + * FALSE with error flag set on error. + * + * @see: pg_flush() + */ + public function do_flush() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $result = pg_flush($this->database); + if ($result === TRUE) { + return new c_base_return_true(); + } + + if ($result === FALSE) { + return c_base_return_error::s_false(); + } + + return new c_base_return_false(); + } + + /** + * Returns whether or not a connection has been established to the database. + * + * @return c_base_return_status + * TRUE on connected, FALSE otherwise. + */ + public function is_connected() { + if ($this->connected === TRUE && pg_connection_status($this->database) === PGSQL_CONNECTION_OK) { + return c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Returns the connection busy status. + * + * This is used for asynchronous connections. + * + * @return c_base_return_status + * TRUE on busy, FALSE when not busy. + * Error flag is set on error. + * + * @see: pg_connection_busy() + */ + public function is_busy() { + if (!$this->asynchronous) { + return c_base_return_error::s_false(); + } + + if (pg_connection_busy($this->database)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Returns the parameter status. + * + * @param string $name + * Name of the parameter to get the status of. + * + * @return c_base_return_status|c_base_return_string + * String containing the status or FALSE on failure. + * + * @see: pg_parameter_status() + */ + public function get_parameter_status($name) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + $result = pg_parameter_status($this->database, $name); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Returns the connection busy status. + * + * This is used for asynchronous connections. + * + * @return c_base_return_int + * The integer is returned on success or failure. + * The failure flag will be set accordingly. + * + * @see: pg_connect_poll() + */ + public function do_poll() { + if (!$this->asynchronous) { + return c_base_return_error::value(0, 'c_base_return_int'); + } + + return c_base_return_int::s_value_exact(pg_connect_poll($this->database)); + } + + /** + * Resets the connection (perform a reconnect). + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: pg_connection_reset() + */ + public function do_reset() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (pg_connection_reset($this->database)) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } + + /** + * Returns the connection status. + * + * @return c_base_return_int + * PGSQL_CONNECTION_OK or PGSQL_CONNECTION_BAD. + * + * @see: pg_connection_status() + */ + public function get_status() { + return c_base_return_int::s_new(pg_connection_status($this->database)); + } + + /** + * Ping the connection and attempt to reconnect if broken. + * + * This calls pg_status() and then pg_connection_reset() instead of pg_ping() so that a more granular return result can be provided. + * + * @return c_base_return_status + * TRUE if ping was successful, FALSE if ping failed, but was successfully restarted. + * FALSE with error flag set is returned on error or if ping failed and restart failed. + * + * @see: pg_ping() + * @see: pg_status() + * @see: pg_connection_reset() + */ + public function do_ping() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (pg_status($this->database) === PGSQL_CONNECTION_OK) { + return new c_base_return_true(); + } + + if (pg_connection_reset($this->database) === TRUE) { + return new c_base_return_false(); + } + + return c_base_return_error::s_false(); + } + + /** + * Escape an SQL literal (an SQL string). + * + * PostgreSQL requires the database connection to be used (or provides its own) when escaping something. + * Use this function so that the associated connection gets used instead of some unknown connection. + * + * @param string $literal + * The string to escape. + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_escape_literal() + */ + public function escape_literal($literal) { + if (!is_string($literal)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_value_exact(pg_escape_literal($this->database, $literal)); + } + + /** + * Escape an SQL bytea, a blob like-type specific to PostgreSQL. + * + * PostgreSQL requires the database connection to be used (or provides its own) when escaping something. + * Use this function so that the associated connection gets used instead of some unknown connection. + * + * @param string $bytea + * The string to escape. + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_escape_bytea() + */ + public function escape_bytea($bytea) { + if (!is_string($bytea)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_value_exact(pg_escape_bytea($this->database, $bytea)); + } + + /** + * Escape an SQL identifier (such as a column or table name). + * + * PostgreSQL requires the database connection to be used (or provides its own) when escaping something. + * Use this function so that the associated connection gets used instead of some unknown connection. + * + * @param string $identifier + * The string to escape. + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_escape_identifier() + */ + public function escape_identifier($identifier) { + if (!is_string($identifier)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_value_exact(pg_escape_identifier($this->database, $identifier)); + } + + /** + * Unescape an SQL bytea, a blob like-type specific to PostgreSQL. + * + * PostgreSQL requires the database connection to be used (or provides its own) when escaping something. + * Use this function so that the associated connection gets used instead of some unknown connection. + * + * @param string $bytea + * The string to unescape. + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_unescape_bytea() + */ + public function unescape_bytea($bytea) { + if (!is_string($bytea)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_value_exact(pg_unescape_bytea($this->database, $bytea)); + } + + /** + * Assigns the client encoding. + * + * @param string $encoding + * The client encoding to assign + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_set_client_encoding() + */ + public function set_client_encoding($encoding) { + if (!is_string($encoding)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + // this function has a strange return status. + // 0 is returned instead of TRUE on success, -1 is returned instead of FALSE on error. + if (pg_set_client_encoding($this->database, $encoding) === 0) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } + + /** + * Returns the client encoding. + * + * @return c_base_return_string|c_base_return_status + * The string to be returned. + * FALSE is returned on error + * + * @see: pg_client_encoding() + */ + public function get_client_encoding() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $encoding = pg_client_encoding($this->database); + if ($encoding === FALSE) { + unset($encoding); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_value_exact($encoding); + } + + /** + * Flushes input on the connection. + * + * Do not confuse this with the flush command. + * The flush command flushsehs the query output. + * This flushes input. + * + * This is likely useful for asynchronous connections. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: pg_consume_input() + */ + public function consume_input() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (pg_consume_input($this->database)) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } + + /** + * Converts an associated array into a suitable SQL statement. + * + * @param string $table + * The name of the table. + * @param string $array + * The associative array to convert. + * @param int $options + * (optional) Additional options, such as: + * - PGSQL_CONV_IGNORE_DEFAULT + * - PGSQL_CONV_FORCE_NULL + * - PGSQL_CONV_IGNORE_NOT_NULL + * + * @return c_base_return_array|c_base_return_status + * The converted array or FALSE on error. + * + * @see: pg_convert() + */ + public function do_convert($table, $array, $options = 0) { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_array($array)) { + return c_base_return_error::s_false(); + } + + if (!is_int($options)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $converted = pg_connect_status($this->database, $table, $array, $options); + if ($converted === FALSE) { + unset($converted); + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_value_exact($converted); + } + + /** + * Execute a given SQL query statement and wait for results. + * + * When set to asynchronous, this effectively calls pg_send_execute(). + * + * @param string $name + * The unique name of a query prepared by self::prepare(). + * @param array $parameters + * (optional) Parameters to be substituted. + * These are all converted to strings. + * + * @return c_base_return_query|c_base_return_status + * Query resource is returned on success, FALSE otherwise. + * + * @see: self::query() + * @see: self::prepare() + * @see: pg_execute() + * @see: pg_send_execute() + * @see: pg_query() + * @see: pg_query_params() + * @see: pg_query_send() + * @see: pg_query_send_params() + * @see: pg_prepare() + * @see: pg_send_prepare() + */ + public function do_execute($name, $parameters = array()) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + if (!is_array($parameters)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if ($this->asynchronous) { + $result = pg_send_execute($this->database, $name, $parameters); + } + else { + $result = pg_execute($this->database, $name, $parameters); + } + + if (is_resource($result)) { + return c_base_database_result::s_new($result); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Process a single SQL query statement. + * + * When set to asynchronous, this effectively calls pg_send_query() or pg_query_send_params(). + * + * @param string $query + * The query statement to execute. + * @param array $parameters + * (optional) Parameters to be substituted. + * These are all converted to strings. + * + * Any bytea field must not be used as a parameter. + * Instead, use pg_escape_bytea() or a large object function. + * + * @return c_base_database_result|c_base_return_status + * Query resource is returned on success, FALSE otherwise. + * + * @see: self::execute() + * @see: self::prepare() + * @see: pg_execute() + * @see: pg_send_execute() + * @see: pg_query() + * @see: pg_query_params() + * @see: pg_query_send() + * @see: pg_query_send_params() + * @see: pg_prepare() + * @see: pg_send_prepare() + */ + public function do_query($query, $parameters = array()) { + if (!is_string($query) || empty($query)) { + return c_base_return_error::s_false(); + } + + if (!is_array($parameters)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if ($this->asynchronous) { + if (empty($parameters)) { + $result = pg_send_query($this->database, $query); + } + else { + $result = pg_send_query_params($this->database, $query, $parameters); + } + } + else { + if (empty($parameters)) { + $result = pg_query($this->database, $query); + } + else { + $result = pg_query_params($this->database, $query, $parameters); + } + } + + if (is_resource($result)) { + return c_base_database_result::s_new($result); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Prepares an SQL statement for multiple uses. + * + * When set to asynchronous, this effectively calls pg_send_prepare(). + * + * @param string $name + * A unique name for the prepared statement so that it can be executed via self::execute(). + * @param string $query + * The query statement to execute. + * + * @return c_base_database_result|c_base_return_status + * Query resource is returned on success, FALSE otherwise. + * + * @see: self::execute() + * @see: self::query() + * @see: pg_execute() + * @see: pg_send_execute() + * @see: pg_prepare() + * @see: pg_send_prepare() + */ + public function do_prepare($name) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + if (!is_string($query) || empty($query)) { + return c_base_return_error::s_false(); + } + + if (!is_array($parameters)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if ($this->asynchronous) { + $result = pg_send_prepare($this->database, $name, $query); + } + else { + $result = pg_prepare($this->database, $name, $query); + } + + if (is_resource($result)) { + return c_base_database_result::s_new($result); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Get the result of an asynchronous query. + * + * This is only useful when a query is asynchronous. + * + * @return c_base_database_result|c_base_return_status + * A database result is returned on success. + * FALSE is returned on failure. + * When asynchronous is not enabled, FALSE is returned without an error flag set. + */ + public function get_result() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (!$this->asynchronous) { + return new c_base_return_false(); + } + + $result = pg_get_result($this->database); + if (is_resource($result)) { + return c_base_database_result::s_new($result); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Cancel an asynchronous query. + * + * This is only useful when a query is asynchronous. + * + * It is unspecific as to whether this cancels the last query sent. + * This appears to be the case, so it is difficulty to cancel multiple queries or even know which query is or is not still running (or waiting). + * It is further difficult to cancel all queries as there is no way to know if FALSe means there was an error or if there are no longer any queries to cancel. + * With any luck, I will come across documentation that clarifies this behavior and discover functions to better handle this. + * + * The documentation does suggest that it prints out messages such as: + * "First call to pg_get_result(): Resource id #3 + * Resource id #3 has 3 records" + * But this is rather useless as it is not a return value to process. + * Furthermore, it clobbers output, which may be a problem. + * + * Due to the immaturity or limitations of functions like this, it is recommended that asynchronous calls not be used for anything that needs to be reliably controlled. + * + * One possible approach is to implement ones own caching mechanisms. + * Implement an array "stack" that stores all queries planned to be executed. + * Then use pg_connection_busy() to check if the execution is available. + * And pop an item when not busy and send the query. + * This design, however, means that only a single a synchronous query operation may be performed at any given time. + * + * @return c_base_return_status + * TRUE is returned on success. + * FALSE is returned on failure. + * When asynchronous is not enabled, FALSE is returned without an error flag set. + * + * @see: pg_cancel_query() + */ + public function do_cancel() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (!$this->asynchronous) { + return new c_base_return_false(); + } + + if (pg_cancel_query($this->database)) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } + + /** + * Perform an SQL INSERT query. + * + * The asynchronous behavior is inconsistent with other functions. + * Instead of having an insert_send() function, it uses an option called PGSQL_DML_ASYNC. + * But that then triggers the call to pg_convert() so suddenly a new option PGSQL_DML_NO_CONV must be used to ensure this does not unintentionally happen. + * + * This is better documentation from PHP's pg_last_oid() on insert: + * To get the value of a SERIAL field in an inserted row, it is necessary to use the PostgreSQL CURRVAL function, naming the sequence whose last value is required. + * If the name of the sequence is unknown, the pg_get_serial_sequence PostgreSQL 8.0 function is necessary. + * PostgreSQL 8.1 has a function LASTVAL that returns the value of the most recently used sequence in the session. + * This avoids the need for naming the sequence, table or column altogether. + * + * @param string $table + * The name of the table to insert into. + * @param array $values + * An associative array of values to insert. + * @param null|int $options + * (optional) If not NULL, pg_convert() is called against $values with these options passed to it. + * The exception to this is when any of the following are passed: + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * + * All options are: + * - PGSQL_CONV_OPTS + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * - PGSQL_DML_EXEC + * - PGSQL_DML_ASYNC (auto-added or auto-removed when asynchronous is enabled or disabled) + * - PGSQL_DML_STRING + * + * @return c_base_return_status|c_base_return_string + * TRUE on success, FALSE on failure. + * If PGSQL_DML_STRING is set, a string is returned on success. + * + * Its unclear as to what the returned string is, but it can be assumed to be a value, such as an serialized number that was incremented by this operation. + * + * @see: pg_insert() + * @see: pg_convert() + * @see: pg_last_oid() + */ + public function do_insert($table, $values, $options = NULL) { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_array($values)) { + return c_base_return_error::s_false(); + } + + if (!is_null($values) || !is_int($options)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $this->p_handle_asynchronous_options_parameter($options); + + if (is_null($options)) { + $result = pg_insert($this->database, $table, $values); + } + else { + $result = pg_insert($this->database, $table, $values, $options); + } + + if (!is_null($options) && $options & PGSQL_DML_STRING) { + if (is_string($result)) { + return c_base_return_string::s_new($result); + } + } + elseif ($result === TRUE) { + unset($result); + return new c_base_return_true(); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Perform an SQL UPDATE. + * + * The asynchronous behavior is inconsistent with other functions. + * Instead of having an update_send() function, it uses an option called PGSQL_DML_ASYNC. + * But that then triggers the call to pg_convert() so suddenly a new option PGSQL_DML_NO_CONV must be used to ensure this does not unintentionally happen. + * + * @param string $table + * The name of the table to insert into. + * @param array $values + * An associative array of values to insert. + * @param array $conditions + * An associative array of conditions that each row must meet to be updated. + * @param null|int $options + * (optional) If not NULL, pg_convert() is called against $values with these options passed to it. + * The exception to this is when any of the following are passed: + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * + * All options are: + * - PGSQL_CONV_FORCE_NULL + * - PGSQL_CONV_OPTS + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * - PGSQL_DML_EXEC + * - PGSQL_DML_ASYNC (auto-added or auto-removed when asynchronous is enabled or disabled) + * - PGSQL_DML_STRING + * + * @return c_base_return_status|c_base_return_string + * TRUE on success, FALSE on failure. + * If PGSQL_DML_STRING is set, a string is returned on success. + * + * Its unclear as to what the returned string is, but it can be assumed to be a value, such as an serialized number that was incremented by this operation. + * + * @see: pg_update() + * @see: pg_convert() + */ + function do_update($table, $values, $conditions, $options = NULL) { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_array($values)) { + return c_base_return_error::s_false(); + } + + if (!is_array($conditions)) { + return c_base_return_error::s_false(); + } + + if (!is_null($values) || !is_int($options)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $this->p_handle_asynchronous_options_parameter($options); + + if (is_null($options)) { + $result = pg_update($this->database, $table, $values, $conditions); + } + else { + $result = pg_update($this->database, $table, $values, $conditions, $options); + } + + if (!is_null($options) && $options & PGSQL_DML_STRING) { + if (is_string($result)) { + return c_base_return_string::s_new($result); + } + } + elseif ($result === TRUE) { + unset($result); + return new c_base_return_true(); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Perform an SQL SELECT. + * + * The asynchronous behavior is inconsistent with other functions. + * Instead of having a select_send() function, it uses an option called PGSQL_DML_ASYNC. + * But that then triggers the call to pg_convert() so suddenly a new option PGSQL_DML_NO_CONV must be used to ensure this does not unintentionally happen. + * + * @param string $table + * The name of the table to insert into. + * @param array $conditions + * An associative array of conditions that each row must meet to be updated. + * @param null|int $options + * (optional) If not NULL, pg_convert() is called against $values with these options passed to it. + * The exception to this is when any of the following are passed: + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * + * All options are: + * - PGSQL_CONV_FORCE_NULL + * - PGSQL_CONV_OPTS + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * - PGSQL_DML_EXEC + * - PGSQL_DML_ASYNC (auto-added or auto-removed when asynchronous is enabled or disabled) + * - PGSQL_DML_STRING + * + * @return c_base_return_status|c_base_return_string|c_base_return_array + * TRUE or an array on success, FALSE on failure. + * If PGSQL_DML_STRING is set, a string is returned on success. + * + * Its unclear as to what the returned string is, but it can be assumed to be a value, such as an serialized number that was incremented by this operation. + * + * The PHP documentation states that the return value is TRUE on success but it also states that it returns an array containing all selected records on success. + * This is confusing and a return value of an array makes the most sense. + * + * @see: pg_select() + * @see: http://us.php.net/manual/en/function.pg-select.php + */ + function do_select($table, $conditions, $options = NULL) { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_array($values)) { + return c_base_return_error::s_false(); + } + + if (!is_array($conditions)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $this->p_handle_asynchronous_options_parameter($options); + + if (is_null($options)) { + $result = pg_select($this->database, $table, $conditions); + } + else { + $result = pg_select($this->database, $table, $conditions, $options); + } + + if (!is_null($options) && $options & PGSQL_DML_STRING) { + if (is_string($result)) { + return c_base_return_string::s_new($result); + } + } + elseif (is_array($result)) { + return c_base_return_array::s_new($result); + } + elseif ($result === TRUE) { + unset($result); + return new c_base_return_true(); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Perform an SQL DELETE. + * + * The asynchronous behavior is inconsistent with other functions. + * Instead of having a delete_send() function, it uses an option called PGSQL_DML_ASYNC. + * But that then triggers the call to pg_convert() so suddenly a new option PGSQL_DML_NO_CONV must be used to ensure this does not unintentionally happen. + * + * @param string $table + * The name of the table to insert into. + * @param array $conditions + * An associative array of conditions that each row must meet to be updated. + * @param null|int $options + * (optional) If not NULL, pg_convert() is called against $values with these options passed to it. + * The exception to this is when any of the following are passed: + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * + * All options are: + * - PGSQL_CONV_FORCE_NULL + * - PGSQL_CONV_OPTS + * - PGSQL_DML_NO_CONV + * - PGSQL_DML_ESCAPE + * - PGSQL_DML_EXEC + * - PGSQL_DML_ASYNC (auto-added or auto-removed when asynchronous is enabled or disabled) + * - PGSQL_DML_STRING + * + * @return c_base_return_status|c_base_return_string|c_base_return_array + * TRUE on success, FALSE on failure. + * If PGSQL_DML_STRING is set, a string is returned on success. + * + * Its unclear as to what the returned string is, but it can be assumed to be a value, such as an serialized number that was incremented by this operation. + * + * @see: pg_delete() + */ + function do_delete() { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_array($values)) { + return c_base_return_error::s_false(); + } + + if (!is_array($conditions)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $this->p_handle_asynchronous_options_parameter($options); + + if (is_null($options)) { + $result = pg_select($this->database, $table, $conditions); + } + else { + $result = pg_select($this->database, $table, $conditions, $options); + } + + if (!is_null($options) && $options & PGSQL_DML_STRING) { + if (is_string($result)) { + return c_base_return_string::s_new($result); + } + } + elseif ($result === TRUE) { + unset($result); + return new c_base_return_true(); + } + unset($result); + + return c_base_return_error::s_false(); + } + + /** + * Request for additional information provided by a table. + * + * This may includes things like comments and similar database-style documentation. + * + * @param string $table + * The name of the table. + * @param bool $extended + * TRUE for extended additional information, FALSE for normal additional information. + * + * @return c_base_return_status|c_base_return_array + * An array containing the additional information or FALSE on error. + * + * @see: pg_meta_data() + */ + public function get_meta_data($table, $extended = FALSE) { + if (!is_string($table) || empty($table)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($extended)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $result = pg_meta_data($this->database, $table, $extended); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($result); + } + + /** + * Sets the verbosity of reported errors. + * + * @param int $verbosity + * One of the following: + * - PGSQL_ERRORS_TERSE + * - PGSQL_ERRORS_DEFAULT + * - PGSQL_ERRORS_VERBOSE + * + * @return c_base_return_status|c_base_return_int + * The previous verbosity level on success, FALSE otherwise. + * + * @see: pg_set_error_verbosity() + */ + public function set_error_verbosity($verbosity) { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + if (!is_int($verbosity)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new(pg_set_error_verbosity($this->database, $verbosity)); + } + + /** + * Returns the last error message string. + * + * @return c_base_return_status|c_base_return_string + * Message string on success, FALSE otherwise. + * + * @see: pg_last_error() + */ + public function get_last_error() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $result = pg_last_error($this->database); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Returns the last notice message string. + * + * @return c_base_return_status|c_base_return_string + * Message string on success, FALSE otherwise. + * + * @see: pg_last_notice() + */ + public function get_last_notice() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $result = pg_last_notice($this->database); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Returns the state of a transaction in the database. + * + * @return c_base_return_status|c_base_return_int + * Transaction state success, FALSE otherwise. + * The returned transaction state integer is expected to be one of the following: + * - PGSQL_TRANSACTION_IDLE: currently idle. + * - PGSQL_TRANSACTION_ACTIVE: command is currently executing. + * - PGSQL_TRANSACTION_INTRANS: idle, in a valid transaction block. + * - PGSQL_TRANSACTION_INERROR: idle, in a failed transaction block. + * - PGSQL_TRANSACTION_UNKNOWN: invalid connection. + * - PGSQL_TRANSACTION_ACTIVE: query sent to server, but not yet completed. + * + * @see: pg_transaction_status() + */ + public function get_transaction_status() { + if (!is_resource($this->database)) { + return c_base_return_error::s_false(); + } + + $result = pg_transaction_status($this->database); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Make sure the options parameter has PGSQL_DML_ASYNC set appropriately. + * + * @param int|null $options + * The options number to add or remove PGSQL_DML_ASYNC based on asynchronous status. + */ + private function p_handle_asynchronous_options_parameter(&$options) { + if ($this->asynchronous) { + if (is_null($options)) { + $options = PGSQL_DML_ASYNC | PGSQL_DML_NO_CONV; + } + else { + if ($options & PGSQL_DML_ASYNC == 0) { + $options += PGSQL_DML_ASYNC; + } + } + } + else { + if (!is_null($options)) { + if ($options & PGSQL_DML_ASYNC > 0) { + $options -= PGSQL_DML_ASYNC; + } + } + } + } +} + +/** + * A generic class for managing database query results. + * + * It is important to note that the fetch_*() functions may return NULL as the database result. + * This NULL is not stored as NULL, but is instead stored as c_base_return_value with the value set to NULL. + * Therefore, one should use caution when using a c_base_return_value::get_value_exact() call as NULL values will not be returned. + * It is recommended that only the c_base_return_value::get_value() call be used when accessing the result value. + */ +class c_base_database_result extends c_base_return_resource { + + /** + * Class destructor. + */ + public function __destruct() { + if (is_resource($this->value)) { + pg_free_result($this->value); + } + + parent::__destruct(); + } + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + + /** + * Fetch all columns in result set. + * + * This is not fetching all columns in the result set as in all column names. + * Instead it is fetch all values from every row that belongs to a single column. + * + * This would be more aptly named something like fetch_all_column_rows($column). + * But even that is not the best of names. + * + * To determine a columns number by name, use pg_field_num(). + * + * @param int $column + * The column number to fetch. + * + * @return c_base_return_status|c_base_return_value + * The value on success, FALSE otherwise. + * + * @see: self::field_number() + * @see: pg_fetch_all_columns() + * @see: pg_field_num() + */ + public function fetch_all_columns($column) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + } + + /** + * Fetch all rows from result set as an array. + * + * @return c_base_return_status|c_base_return_array + * The value on success, FALSE otherwise. + * + * @see: pg_fetch_all() + */ + public function fetch_all() { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + $result = pg_fetch_all($this->value); + if ($result === FALSE) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($result); + } + + /** + * Fetch array from result set. + * + * @param null|int $row + * (optional) The number of the row to fetch. + * If NULL, the next row is fetched. + * @param int $type + * (optional) Specify the type of array returned: + * - PGSQL_ASSOC = return an associative array (just like when calling pg_fetch_assoc()). + * - PGSQL_NUM = return an array with numerical indeces. + * - PGSQL_BOTH = return with both associated indeces and numeric indeces. + * + * @return c_base_return_status|c_base_return_array + * The value on success, FALSE otherwise. + * + * @see: pg_fetch_array() + */ + public function fetch_array($row = NULL, $type = PGSQL_ASSOC) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row) && (!is_int($row) || $row < 0)) { + return c_base_return_error::s_false(); + } + + $result = pg_fetch_array($this->value, $row, $column); + if ($result === FALSE) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($result); + } + + /** + * Fetch object from result set. + * + * @param null|int $row + * (optional) The number of the row to fetch. + * If NULL, the next row is fetched. + * @param null|string $class + * When not NULL, then is a string represnting the name of a type of class. + * The return object will be returned as this type of object. + * @param null|array $parameters + * When $class is not NULL and this is not NULL, then this contains parameters to pass to the object of type $class during initialization. + * + * @return c_base_return_status|c_base_return_object + * The value on success, FALSE otherwise. + * Even if a custom class is specified, a valid object is always returned as c_base_return_object. + * This is done for simplicity purposes. + * For types that inherit c_base_return_object, it should be simple to cast this return result to that child class. + * + * @see: pg_fetch_object() + */ + public function fetch_object($row = NULL, $class = NULL, $parameters = NULL) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row) && (!is_int($row) || $row < 0)) { + return c_base_return_error::s_false(); + } + + if (!is_null($class) && !is_string($class)) { + return c_base_return_error::s_false(); + } + + if (!is_null($parameters) && !is_array($parameters)) { + return c_base_return_error::s_false(); + } + + if (is_string($class) && !empty($class)) { + $result = pg_fetch_object($this->value, $row, $class, $parameters); + } + else { + $result = pg_fetch_object($this->value, $row); + } + + if ($result === FALSE) { + return c_base_return_error::s_false(); + } + + return c_base_return_object::s_new($result); + } + + /** + * Fetch a result from result set. + * + * @param null|int $row + * The number of the row to fetch. + * If NULL, the next row is fetched. + * @param string|int $column + * A string representing the column name or an integer representing the column number. + * + * @return c_base_return_status|c_base_return_value + * The value on success, FALSE otherwise. + * + * @see: pg_fetch_result() + */ + public function fetch_result($row, $column) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row) && !is_int($row)) { + return c_base_return_error::s_false(); + } + + if (!is_int($row) && !is_string($row) || is_int($row) && $row < 0 || is_string($row) && empty($row)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row)) { + $result = pg_fetch_result($this->value, $row, $column); + } + else { + $result = pg_fetch_result($this->value, $column); + } + + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_value::s_new($result); + } + + /** + * Fetch row from result set. + * + * @param null|int $row + * (optional) The number of the row to fetch. + * If NULL, the next row is fetched. + * + * @return c_base_return_status|c_base_return_value + * The value on success, FALSE otherwise. + * + * @see: pg_fetch_row() + */ + public function fetch_row($row = NULL) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row) && (!is_int($row) || $row < 0)) { + return c_base_return_error::s_false(); + } + + $result = pg_fetch_row($this->value, $row); + if ($result === FALSE) { + return c_base_return_error::s_false(); + } + + return c_base_return_value::s_new($result); + } + + /** + * Returns an individual field code of an error port. + * + * @param null|int $code + * When not NULL this is a field code, such as: + * - PGSQL_DIAG_SEVERITY + * - PGSQL_DIAG_SQLSTATE + * - PGSQL_DIAG_MESSAGE_PRIMARY + * - PGSQL_DIAG_MESSAGE_DETAIL + * - PGSQL_DIAG_MESSAGE_HINT + * - PGSQL_DIAG_STATEMENT_POSITION + * - PGSQL_DIAG_INTERNAL_POSITION + * - PGSQL_DIAG_INTERNAL_QUERY + * - PGSQL_DIAG_CONTEXT + * - PGSQL_DIAG_SOURCE_FILE + * - PGSQL_DIAG_SOURCE_LINE + * - PGSQL_DIAG_SOURCE_FUNCTION + * + * @return c_base_return_status|c_base_return_string + * String is returned if found, a NULL is returned if not found, and FALSE is returned on error (with error flag set). + * + * @see: pg_result_error() + * @see: pg_result_error_field() + */ + public function error($code = NULL) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($code) && !is_int($code)) { + return c_base_return_error::s_false(); + } + + if (is_null($code)) { + $result = pg_result_error($this->value); + } + else { + $result = pg_result_error_field($this->value, $code); + } + + if (is_null($result) || is_string($result) && mb_strlen($result) == 0) { + unset($result); + return new c_base_return_null(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Returns the number of rows affected by the SQL statement associated with this result set. + * + * @return c_base_return_int|c_base_return_status + * The number of items (be it instances, records, or rows) that are affected by any INSERT, UPDATE, DELETE, or SELECT queries. + * FALSE with error bit set is returned on error. + * + * @see: pg_affected_rows() + */ + public function affected_rows() { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new(pg_affected_rows($this->value)); + } + + /** + * Get the number of rows in the result set. + * + * @return c_base_return_status|c_base_return_int + * The number of rows or FALSE on failure. + * + * @see: pg_num_rows() + */ + public function number_of_rows() { + if (!is_resource($this->value)) { + return new c_base_return_false(); + } + + $result = pg_num_rows($this->value); + if ($result < 0) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Get the number of columns in the result set. + * + * @return c_base_return_status|c_base_return_int + * The number of rows or FALSE on failure. + * + * @see: pg_num_fields() + */ + public function number_of_columns() { + if (!is_resource($this->value)) { + return new c_base_return_false(); + } + + $result = pg_num_fields($this->value); + if ($result < 0) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Returns the name of a column at the given location in the result set. + * + * @param int $number + * The column number of the field to get the name of. + * + * @return c_base_return_status|c_base_return_string + * The name of the field or FALSE on error. + * + * @see: pg_field_name() + */ + public function field_name($number) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_int($number) || $number < 0) { + return c_base_return_error::s_false(); + } + + $result = pg_field_name($this->value, $number); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Returns the number of a column with the given name in the result set. + * + * @param string $name + * The column name of the field to get the number of. + * + * @return c_base_return_status|c_base_return_int + * The number of the field or FALSE on error. + * + * @see: pg_field_num() + */ + public function field_number($name) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_string($name) || mb_strlen($name) == 0) { + return c_base_return_error::s_false(); + } + + // this returnes -1 on error and >= 0 on success, so translate the codes appropriately. + $result = pg_field_number($this->value, $name); + if ($result < 0) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Returns the 'printed length' of a field in the result set. + * + * This is the number of characters used to represent a result value. + * + * @param int|null $row + * The row number to fetch. + * @param string|int $name_or_number + * The column name or column number of the field to get the printed length of. + * When passed as an integer, this is the column number. + * When passed as a string, this is a column name. + * Therefore a value of '0' would be a column named 0 and a value of 0 would be column number 0. + * + * @return c_base_return_status|c_base_return_int + * The printed length of the 'field' or FALSE on error. + * + * @see: self::field_bytes() + * @see: pg_field_prtlen() + */ + public function field_length($row, $name_or_number) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_null($row) && (!is_int($row) || $row < 0)) { + return c_base_return_error::s_false(); + } + + if (!is_int($name_or_number) && !(is_string($name_or_number) && mb_strlen($name) > 0)) { + return c_base_return_error::s_false(); + } + + if (is_null($row)) { + $result = pg_field_prtlen($this->value, $name_or_number); + } + else { + $result = pg_field_prtlen($this->value, $row, $name_or_number); + } + + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Return the size of the column. + * + * @param int $column + * The column number to return the size of. + * + * @return c_base_return_status|c_base_return_int + * The size of the column or FALSE on error. + * The returned size may be -1, in which case means the size is variable. + * + * @see: self::field_length() + * @see: pg_field_size() + */ + public function field_bytes($column) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_int($column) || $column < 0) { + return c_base_return_error::s_false(); + } + + $result = pg_size($this->value, $column); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Return the name or oid of the tables field. + * + * The word usage here is confusing. + * Documentation states that this function 'returns the name or oid of the "Tables Field"'. + * Really, what this appears to do is return name or oid of the "Columns Table". + * + * @param int $column + * The column number to return the table name for. + * @param bool $oid + * (optional) When TRUE, the oid is retuned instead of the table name. + * + * @return c_base_return_status|c_base_return_int|c_base_return_string + * The name of the table that the given column belongs to or FALSE on error. + * If oid is set to TRUE, then the oid. + * + * @see: pg_field_table() + * @see: http://www.postgresql.org/docs/current/static/datatype-oid.html + */ + public function field_table($column, $oid = FALSE) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_int($column) || $column < 0) { + return c_base_return_error::s_false(); + } + + if (!is_bool($oid)) { + return c_base_return_error::s_false(); + } + + $result = pg_field_table($this->value, $column, $oid); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + if ($oid) { + return c_base_return_int::s_new($result); + } + + return c_base_return_string::s_new($result); + } + + /** + * Return the oid of the column. + * + * @param int $column + * The column number to return the oid of. + * + * @return c_base_return_status|c_base_return_int + * The oid of the requested column to or FALSE on error. + * + * @see: pg_field_type_oid() + * @see: http://www.postgresql.org/docs/current/static/datatype-oid.html + */ + public function field_type_oid($column) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_int($column) || $column < 0) { + return c_base_return_error::s_false(); + } + + $result = pg_field_type_oid($this->value, $column); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($result); + } + + /** + * Returns the last row's oid. + * + * This is used to retrieve an OID assigned to an inserted row. + * Oid is now optional, so try using pg_result_status() instead. + * + * @return c_base_return_status|c_base_return_string + * A string containing the oid assigned to the most recently inserted row on success, FALSE otherwise. + * + * @see: pg_last_oid() + * @see: pg_result_status() + * @see: http://www.postgresql.org/docs/current/static/datatype-oid.html + */ + public function last_oid() { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + $result = pg_last_oid($this->database); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Return the storage type of the column. + * + * @param int $column + * The column number to return the oid of. + * + * @return c_base_return_status|c_base_return_int + * The oid of the requested column to or FALSE on error. + * + * @see: pg_field_type() + */ + public function field_type($column) { + if (!is_resource($this->value)) { + return c_base_return_error::s_false(); + } + + if (!is_int($column) || $column < 0) { + return c_base_return_error::s_false(); + } + + $result = pg_field_type($this->value, $column); + if ($result === FALSE) { + unset($result); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($result); + } + + /** + * Free's memory allocated to the class. + * + * @return c_base_return_status + * TRUE is returned on success, FALSE is returned if nothing to free. + * FALSE with the error flag set is returned on error. + * + * @see: pg_free_result() + */ + public function free_result() { + if (!is_resource($this->value)) { + return new c_base_return_false(); + } + + if (pg_free_result($this->value)) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } +} + +/** + * A generic class for building a query array. + * + * A query array is essentially an array whose keys define how the query is to be generated. + * Future work is planned, but this is essentially being provided a stub until then. + */ +class c_base_database_query extends c_base_return_array { + const OPERATION_NONE = 0; + const OPERATION_SELECT = 1; + + const ALIAS_PREFIX_TABLE = 't_'; + const ALIAS_PREFIX_COLUMN = 'c_'; + + /** + * Class constructor. + */ + public function __construct() { + $this->p_initialize(); + + parent::__construct(); + } + + /** + * Class destructor. + */ + public function __destruct() { + parent::__destruct(); + } + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, array()); + } + + /** + * Import and process an exported array. + * + * This string will be validated and processed before being saved. + * Invalid data will be lost. + * + * @param string $import + * A json encoded array reflecting the contents of this object. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function import($import) { + if (!is_string($import) || empty($import)) { + return c_base_return_error::s_false(); + } + + $decoded = json_decode($import, TRUE); + if (!is_array($decoded) || empty($decoded)) { + return c_base_return_error::s_false(); + } + + $this->p_initialize(); + + $this->value = $decoded; + + return new c_base_return_true(); + } + + /** + * Export the values of this object into a json string. + * + * @return c_base_return_status|c_base_return_string + * FALSE is returned when there is no content to export. + * Otherwise, a json encoded string is returned. + */ + public function export() { + if ($this->count_table == 0 || empty($this->value)) { + return new c_base_return_false(); + } + + // only the value array needs to be exported. + // everything else has to be re-created on import for security reasons. + $encoded = json_encode($this->value); + if (!is_string($encoded)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($encoded); + } + + /** + * Perform initialization of all variables in this class. + */ + private function p_initialize() { + unset($this->value); + $this->value = array(); + } +} diff --git a/common/base/classes/base_debug.php b/common/base/classes/base_debug.php new file mode 100644 index 0000000..2815dc7 --- /dev/null +++ b/common/base/classes/base_debug.php @@ -0,0 +1,240 @@ +time_start = NULL; + $this->time_stop = NULL; + + $this->memory_usage_start = NULL; + $this->memory_usage_stop = NULL; + $this->memory_usage_peak = NULL; + $this->memory_allocated_start = NULL; + $this->memory_allocated_stop = NULL; + $this->memory_allocated_peak = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->time_start); + unset($this->time_stop); + + unset($this->memory_usage_start); + unset($this->memory_usage_stop); + unset($this->memory_usage_peak); + unset($this->memory_allocated_start); + unset($this->memory_allocated_stop); + unset($this->memory_allocated_peak); + } + + /** + * Turn on/off debugging for every instance of this class. + * + * @param bool $debug + * Set to TRUE to enable debugging, FALSE to disable. + * + * @param c_base_return_status + * TRUE is returned on success, FALSE otherwise. + */ + public static function s_set_debugging($debug) { + if (!is_bool($debug)) { + return c_base_return_error::s_false(); + } + + self::$ps_debugging = $debug; + + return new c_base_return_true(); + } + + /** + * Get debugging enabled/disabled status. + * + * @return c_base_return_status + * TRUE when debugging is enabled, FALSE otherwise. + */ + public static function s_get_debugging() { + if (self::$ps_debugging) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Begin recording the time and memory consumption. + */ + public function do_start_consumption_recording() { + if (!self::$ps_debugging) { + return; + } + + $this->time_start = microtime(TRUE); + $this->memory_usage_start = memory_get_usage(); + $this->memory_allocated_start = memory_get_usage(TRUE); + } + + /** + * Recording begin time and memory consumption. + */ + public function do_stop_consumption_recording() { + if (!self::$ps_debugging) { + return; + } + + // don't do anything if the start consumption has yet to be called. + if (is_null($this->time_start)) { + return; + } + + $this->time_stop = microtime(TRUE); + $this->memory_usage_stop = memory_get_usage(); + $this->memory_allocated_stop = memory_get_usage(TRUE); + + $this->memory_usage_peak = memory_get_peak_usage(); + $this->memory_allocated_peak = memory_get_peak_usage(TRUE); + } + + /** + * Record end time and memory consumption. + */ + public function do_reset_consumption_recording() { + if (!self::$ps_debugging) { + return; + } + + $this->time_start = NULL; + $this->time_stop = NULL; + + $this->memory_usage_start = NULL; + $this->memory_usage_stop = NULL; + $this->memory_usage_peak = NULL; + + $this->memory_allocated_start = NULL; + $this->memory_allocated_stop = NULL; + $this->memory_allocated_peak = NULL; + } + + /** + * Get the amount of time consumed between the requested start/stop commands. + * + * @param bool $milliseconds + * Return time in milliseconds when TRUE, otherwise return time in microseconds. + * + * @return c_return_status|c_return_int + * An integer is returned representing the time difference or FALSE with error bit set is returned on error. + * If debugging is disabled, then no error bit will be set on FALSE. + * + * @see: do_start_consumption_recording() + * @see: do_stop_consumption_recording() + * @see: do_reset_consumption_recording() + */ + public function get_consumption_time($milliseconds = TRUE) { + if (!self::$ps_debugging) { + return new c_base_return_false(); + } + + if (!is_bool($milliseconds)) { + return c_base_return_error::s_false(); + } + + if (is_null($this->time_start) || is_null($this->time_stop)) { + return c_base_return_error::s_false(); + } + + if ($milliseconds) { + return c_base_return_int::s_new(($this->time_stop - $this->time_start) * 1000); + } + + return c_base_return_int::s_new($this->time_stop - $this->time_start); + } + + /** + * Get the amount of memory used between the requested start/stop commands. + * + * @param int $option + * When set to 1: return peak usage. + * When set to 2: return stop usage. + * When set to 3: return the difference between the start and stop usage (may be negative). + * @param bool $megabytes + * Return the value in megabytes when TRUE, otherwise return the value in bytes. + * + * @return c_return_status|c_return_int + * An integer is returned representing the time difference or FALSE with error bit set is returned on error. + * If debugging is disabled, then no error bit will be set on FALSE. + * + * @see: do_start_consumption_recording() + * @see: do_stop_consumption_recording() + * @see: do_reset_consumption_recording() + */ + public function get_consumption_memory_usage($option = 1, $megabytes = TRUE) { + if (!self::$ps_debugging) { + return new c_base_return_false(); + } + + if (!is_int($option) || $option < 1 || $option > 3 || !is_bool($megabytes)) { + return c_base_return_error::s_false(); + } + + if ($option == 1) { + if (is_null($this->memory_usage_peak)) { + return c_base_return_error::s_false(); + } + + if ($megabytes) { + return c_base_return_int::s_new($this->memory_usage_peak / 1024 / 1024); + } + + return c_base_return_int::s_new($this->memory_usage_peak); + } + elseif ($option == 2) { + if (is_null($this->time_stop)) { + return c_base_return_error::s_false(); + } + + if ($megabytes) { + return c_base_return_int::s_new($this->memory_usage_stop / 1024 / 1024); + } + + return c_base_return_int::s_new($this->memory_usage_stop); + } + else { + if (is_null($this->time_start) || is_null($this->time_stop)) { + return c_base_return_error::s_false(); + } + + if ($megabytes) { + return c_base_return_int::s_new(($this->memory_usage_stop - $this->memory_usage_start) / 1024 / 1024); + } + + return c_base_return_int::s_new($this->memory_usage_stop - $this->memory_usage_start); + } + } +} diff --git a/common/base/classes/base_email.php b/common/base/classes/base_email.php new file mode 100644 index 0000000..7cd98a6 --- /dev/null +++ b/common/base/classes/base_email.php @@ -0,0 +1,608 @@ +, [name_machine]@[name_address]]; + * + * Comments can get out of hand but is still technical part of the standard, so the following must also be supported: + * [[name_group]: ([comment])["[name_human]"]([comment]) <([comment])[name_machine]([comment])@([comment])[name_address]([comment])>, ([comment])[name_machine]([comment])@([comment])[name_address]([comment])]; + * + * This is incredibly prone to abuse, mis-use, and exploitation. + * To prevent abuse, comments will be stripped out of the e-mail. + * + * @param string $email + * The email to validate and decode. + * @param null|bool $custom_processing + * @fixme: on second thought, it might be cleaner to implement these "custom_processing" cases in separate functions. + * (optional) If NULL, then support entire email possibilities. + * If FALSE, then only process a single (ungrouped) email with entire remaining possibilities. + * IF TRUE, then support only the machine name portions of a single e-mail (the "id_left @ id_right" part of what is called "message id"). + * + * @return array + * A decoded e-mail split into its different parts inside an array. + * An array key called 'invalid' exists to designate that the uri is invalid. + * + * @see: base_rfc_string::pr_rfc_string_prepare() + * @see: https://tools.ietf.org/html/rfc5322#section-3.4 + * @see: https://tools.ietf.org/html/rfc6854 + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.1 + */ + private static function p_parse_email_full($email) { + $result = array( + 'emails' => array(), + 'invalid' => FALSE, + ); + + $processed = array(); + $group = NULL; + $group_id = NULL; + $process_machine_part = FALSE; + $current = 0; + $current_string = NULL; + $current_chunk_start = 0; + $delimited = FALSE; + $name_human = NULL; + $name_machine = NULL; + $name_group = NULL; + + $email_text = $this->pr_rfc_string_prepare($email); + if ($email_text['invalid']) { + unset($email_text); + $result['invalid'] = TRUE; + return $result; + } + + $stop = count($email_text['codepoints']) + 1; + + $is_quoted = FALSE; + $is_named_human = FALSE; + $is_named_machine = FALSE; + $has_found_comment = FALSE; + for (; $current < $stop; $current++) { + if ($email_text['codepoints'][$current] == c_base_ascii::LESS_THAN) { + if (!$is_quoted && !$is_named_human && !is_null($current_string)) { + $name_human = $current_string; + $is_named_human = TRUE; + } + + $current_string = NULL; + $has_found_comment = FALSE; + + $parsed = p_parse_email_machine($email_text['codepoints'], $email_text['characters'], $current, $stop, c_base_ascii::GREATER_THAN); + $current = $parse_result['current']; + + // if the proper stop point was reached, then the '<' is a valid opening. + if ($parsed['stopped_at'] && !$parsed['invalid'] && !empty($parsed['name_machine'])) { + $name_machine = $parsed['name_machine'] . '@' . $parsed['name_address']; + $result['emails'][$name_machine] = array( + 'name_human' => $name_human, + 'name_machine' => $name_machine, + 'name_address' => $parsed['name_address'], + ); + + $current_chunk_start = $current + 1; + $is_named_machine = TRUE; + + unset($parsed); + continue; + } + + $result['invalid'] = TRUE; + unset($parsed); + break; + } + elseif ($email_text['codepoints'][$current] == c_base_ascii::QUOTE_DOUBLE) { + if ($is_quoted || $is_named_human || $is_named_machine) { + // cannot have more than one quoted or unquoted string and human names cannot follow machine names. + $result['invalid'] = TRUE; + break; + } + + $current_string = NULL; + $has_found_comment = FALSE; + + $is_quoted = TRUE; + $closing_quote = FALSE; + for (; $current < $stop; $current++) { + + if ($email_text['codepoints'][$current] == c_base_ascii::SLASH_BACKWARD) { + if ($current + 1 >= $stop) { + $result['invalid'] = TRUE; + break; + } + + // only a double quote may be delimited, otherwise the backwards slash is invalid. + if ($email_text['codepoints'][$current + 1] == c_base_ascii::QUOTE_DOUBLE) { + $current++; + } + else { + $result['invalid'] = TRUE; + break; + } + } + elseif ($email_text['codepoints'][$current] == c_base_ascii::QUOTE_DOUBLE) { + $closing_quote = TRUE; + break; + } + elseif ($this->pr_rfc_char_is_crlf($email_text['codepoints'][$current])) { + // not allowed inside a quoted string. + $result['invalid'] = TRUE; + break; + } + + $name_human .= $email[$current]; + } + + if (!$closing_quote) { + $result['invalid'] = TRUE; + unset($closing_quote); + break; + } + unset($closing_quote); + + $current_chunk_start = $current + 1; + $is_named_human = TRUE; + + continue; + } + elseif ($this->pr_rfc_char_is_fws($email_text['codepoints'][$current])) { + // though unusual, starting with whitespace appears to be technically allowed unless I am misunderstanding or overlooking something. + if (!$is_named_human && !$is_named_machine && is_null($current_string)) { + continue; + } + } + elseif ($email_text['codepoints'][$current] == c_base_ascii::PARENTHESIS_OPEN) { + if ($has_found_comment) { + // there may be only one comment between non-comments. + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + // start with a comment and comments may have comments within themselves. + // though bizarre, this appears to be technically allowed unless I am misunderstanding or overlooking something. + // this includes delimiters. + $parsed = $this->pr_rfc_string_is_comment($email_text['codepoints'], $email_text['characters'], $current, $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + $has_found_comment = TRUE; + $current_chunk_start = $current + 1; + + unset($parsed); + continue; + } + elseif ($email_text['codepoints'][$current] == c_base_ascii::COLON) { + if ($is_named_human || $is_named_machine) { + // A colon may not be specified following human or machine names. + $result['invalid'] = TRUE; + break; + } + + if (is_null($current_string)) { + // A colon without any preceding valid text is considered an invalid group (groups must have group names). + $result['invalid'] = TRUE; + break; + } + + // @todo: implement this via another loop or function due to the complexity. + // do not nullify $current_string because it is needed to define/provide the name of the group. + // @todo: if the colon is supplied, but double quotes were used, then does this mean it is a double quoted group name? (in which case the test against $is_named_human is invalid.) + } + elseif ($email_text['codepoints'][$current] == c_base_ascii::AT) { + if ($is_named_human || $is_named_machine) { + // when a human name is supplied, then the e-mail address must be inside of '<' and '>'. + // multiple machine names may not be specified. + $result['invalid'] = TRUE; + break; + } + + if ($current == $current_chunk_start) { + // if the machine name starts with an '@', then it is invalid. + $result['invalid'] = TRUE; + break; + } + + $current_string = NULL; + $has_found_comment = FALSE; + + // the machine name is processed using the current chunk start instead of the current value because the '@' is not the first character in the machine name. + $parsed = p_parse_email_machine($email_text['codepoints'], $email_text['characters'], $current_chunk_start, $stop); + $current = $parse_result['current']; + + // if the proper stop point was reached, then a valid machine name was found. + if ($parsed['stopped_at'] && !$parsed['invalid'] && !empty($parsed['name_machine'])) { + $name_machine = $parsed['name_machine'] . '@' . $parsed['name_address']; + $result['emails'][$name_machine] = array( + 'name_human' => $name_human, + 'name_machine' => $name_machine, + 'name_address' => $parsed['name_address'], + ); + + $current_chunk_start = $current + 1; + + unset($parsed); + continue; + } + + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + $current_string .= $email_text['characters'][$current]; + } + unset($name_group); + unset($name_machine); + unset($name_human); + unset($delimited); + unset($is_quoted); + unset($is_named_human); + unset($is_named_machine); + unset($has_found_comment); + unset($process_machine_part); + unset($current); + unset($current_string); + unset($current_chunk_start); + unset($group_id); + unset($group); + unset($processed); + unset($stop); + unset($email_text); + + return $result; + } + + private static function p_parse_email_single($email, $start = NULL, $stop = NULL) { + // @todo: implement this. + } + + /** + * Process only the machine part of a single e-mail address. + * + * This parses an e-mail string of the form: [name_machine]@[name_address]. + * + * The standard allows for (comment)[name_machine](comment)@(comment)[name_address](comment). + * - These comments will be ignored and stripped out. + * + * This does not validate [name_address] to be a correct host name or ip address. + * @todo: consider doing this filter_var() and options like: FILTER_VALIDATE_IP (I must review whether or not FILTER_VALIDATE_IP is standards compliant). + * + * The arguments $start, $stop, and $end_at_brace allow for the other more complex email parsers to call this function when necessary. + * + * @param array $email_codes + * An array containing codepoint values for an e-mail address string. + * @param array $email_characters + * An array containing characters for the e-mail address string. + * @param null|int $start + * (optional) Specify a starting point in which to begin processing. + * @param null|int $stop + * (optional) Specify a stopping point in which to end processing. + * + * @param bool|int $stop_at + * (optional) When FALSE, this function returns only when the end of string or an invalid character is found (including brace). + * Invalid characters result in an error. + * When not FALSE, this is an integer representing a single character to halt processing on. + * + * @return array + * An array containing the processed pieces. + * The array key 'invalid' is set TRUE on error. + * The array key 'current' is set to location in the string of where processing stopped. + * - For example, if execution was stopped on an unexpected brace, the location would be the position of that specific brace. + */ + private static function p_parse_email_machine($email_codes, $email_characters, $start = NULL, $stop = NULL, $stop_at = FALSE) { + $result = array( + 'name_machine' => '', + 'name_address' => '', + 'invalid' => FALSE, + 'current' => 0, + 'stopped_at' => FALSE, + ); + + if (!is_null($start)) { + $result['current'] = $start; + } + + if (is_null($stop)) { + $stop = count($email_codes) + 1; + } + + // first check for and ignore comments at the start. + for (; $result['current'] < $stop; $result['current']++) { + $code = $email_codes[$result['current']]; + + if ($code === $stop_at) { + // stopping at this point means that the address is invalid. + $result['invalid'] = TRUE; + $result['stopped_at'] = TRUE; + break; + } + + if ($email[$current] == '(') { + $parsed = $this->pr_rfc_string_is_comment($email_codes, $email_characters, $result['current'], $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + unset($parsed); + + // there may be multiple comments, so do not break at this point. + } + elseif (!$this->pr_rfc_char_is_fws($code)) { + // the first non-comment, non-fws char should be the start of the [machine_name]. + break; + } + } + unset($code); + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + return $result; + } + + // process [machine_name]. + $started = FALSE; + $stopped = FALSE; + $comments = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $email_codes[$result['current']]; + + if ($code === $stop_at) { + // stopping at this point means that the address is invalid. + $result['invalid'] = TRUE; + $result['stopped_at'] = TRUE; + break; + } + + if ($code == c_base_ascii::PARENTHESIS_OPEN) { + if ($started) { + $started = FALSE; + $stopped = TRUE; + } + + $comments = TRUE; + + // comments may follow a machine name. + $parsed = $this->pr_rfc_string_is_comment($email_codes, $email_characters, $result['current'], $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + unset($parsed); + + continue; + } + elseif ($this->pr_rfc_char_is_fws($code)) { + if (!$started || $stopped) { + // ignore leading/trailing whitespace. + continue; + } + + // whitespace at this point should mean a stop point has been reached. + $started = FALSE; + $stopped = TRUE; + $comments = FALSE; + } + elseif ($code != c_base_ascii::PERIOD && !$this->pr_rfc_char_is_atext($code)) { + if ($code != c_base_ascii::AT) { + $result['invalid'] = TRUE; + break; + } + + $result['current']++; + break; + } + elseif ($stopped) { + // comments or whitespace may not appear between text. + $result['invalid'] = TRUE; + break; + } + elseif (!$started) { + $comments = FALSE; + $started = TRUE; + } + + $result['name_machine'] .= $email_characters[$result['current']]; + } + unset($code); + unset($started); + unset($stopped); + unset($comments); + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + return $result; + } + + // comments may appear before the [name_address], skip past them. + for (; $result['current'] < $stop; $result['current']++) { + $code = self::pr_char_to_code($email[$result['current']]); + + if ($code === $stop_at) { + // stopping at this point means that the address is invalid. + $result['invalid'] = TRUE; + $result['stopped_at'] = TRUE; + break; + } + + if ($code == c_base_ascii::PARENTHESIS_OPEN) { + $parsed = $this->pr_rfc_string_is_comment($email_codes, $email_characters, $result['current'], $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + unset($parsed); + + // there may be multiple comments, so do not break at this point. + } + elseif (!$this->pr_rfc_char_is_fws($code)) { + // the first non-comment, non-fws char should be the start of the [ip_address]. + break; + } + } + unset($code); + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + return $result; + } + + // process [name_address]. + if ($email_codes[$result['current']] == c_base_ascii::BRACKET_OPEN) { + // process as a literal domain. + for (; $result['current'] < $stop; $result['current']++) { + $code = $email_codes[$result['current']]; + + if ($code === $stop_at) { + // stopping at this point means that the address is invalid. + $result['invalid'] = TRUE; + $result['stopped_at'] = TRUE; + break; + } + + if (!$this->pr_rfc_char_is_dtext($code)) { + if ($code != c_base_ascii::BRACKET_CLOSE) { + $result['invalid'] = TRUE; + unset($stop); + return $result; + } + + $result['current']++; + break; + } + + $result['name_address'] .= $email_characters[$result['name_address']]; + } + unset($code); + } + else { + // process as a non-literal domain. + for (; $result['current'] < $stop; $result['current']++) { + $code = $email_codes[$result['current']]; + + if ($code === $stop_at) { + // stopping at this point, there is no way to know if it is valid or not. + // @todo: this would be a good place to perform the name_address spot check for valid ip or host name. + $result['stopped_at'] = TRUE; + break; + } + + if ($code != c_base_ascii::PERIOD && !$this->pr_rfc_char_is_atext($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['name_address'] .= $email_characters[$result['name_address']]; + } + unset($code); + } + + // @todo: the spot to check if name_address is a valid ip or host name. + + if ($result['invalid']) { + return $result; + } + + // comments may appear after the [name_address], skip past them. + for (; $result['current'] < $stop; $result['current']++) { + $code = $email_codes[$result['current']]; + + if ($code === $stop_at) { + // this is a valid name, comments are irrelevant. + // that said, the comment was not completed, so it may be invalid. + $result['stopped_at'] = TRUE; + break; + } + + if ($code == c_base_ascii::PARENTHESIS_OPEN) { + $parsed = $this->pr_rfc_string_is_comment($email_codes, $email_characters, $result['current'], $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + unset($parsed); + + // there may be multiple comments, so do not break at this point. + } + elseif (!$this->pr_rfc_char_is_fws($code)) { + // the first non-comment, non-fws char should be the start of the [ip_address]. + break; + } + } + unset($code); + + if ($current < $stop) { + // There should be nothing at the end of a valid string. + // If there is, then this is an invalid e-mail. + // If stop_at is not FALSE, then stopping before the final stop point is not automatically a problem. + if ($stop_at !== FALSE) { + $result['invalid'] = TRUE; + } + } + + return $result; + } +} diff --git a/common/base/classes/base_error.php b/common/base/classes/base_error.php new file mode 100644 index 0000000..829e500 --- /dev/null +++ b/common/base/classes/base_error.php @@ -0,0 +1,475 @@ +name = NULL; + $this->message = NULL; + $this->details = NULL; + $this->severity = NULL; + $this->limit = NULL; + $this->backtrace = NULL; + $this->code = NULL; + $this->reported = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->name); + unset($this->message); + unset($this->details); + unset($this->severity); + unset($this->limit); + unset($this->backtrace); + unset($this->code); + unset($this->reported); + } + + /** + * Write an error log message and returns the resulting new error object. + * + * This will silently ignore invalid arguments, with the exception of the reporting controls. + * For reporting information, use self::get_reporting(). + * + * @todo: this is incomplete. + * + * @param string|null $name + * (optional) The name of something associated with a problem. + * This is often a variable name or a function name. + * @param string|null $message + * (optional) A message string describing the problem, in some manner. + * @param array|null $details + * (optional) An array containing further, detailed, information. + * @param int|null $code + * (optional) An integer identifying the message in some manner. + * @param int|null $severity + * (optional) A number representing the severity level. + * The c_base_error constants, such as EMERGENCY or ERROR should be use here. + * This defaults to: self::ERROR. + * @param int|bool|null $limit + * (optional) A number representing the backtrace limit. + * If set to FALSE, then no backtrace is generated. + * @param bool $report + * (optional) If TRUE, then report the error using the appropriate methods. + * @fixme: it would probably be best to make this an object that knows how to report so that the object can do what it needs to do. + * + * @return c_base_error + * Always returns a newly created c_base_error object. + * No error status is ever returned. + * + * @see: self::get_reporting() + */ + public static function s_log($name = NULL, $message = NULL, $details = NULL, $code = NULL, $severity = NULL, $limit = NULL, $report = TRUE) { + $class = __CLASS__; + $entry = new $class(); + unset($class); + + if (is_string($name)) { + $entry->set_name($name); + } + elseif (is_null($this->name)) { + $entry->set_name(''); + } + + if (is_string($message)) { + $entry->set_message($message); + } + elseif (is_null($this->message)) { + $entry->set_message(''); + } + + if (is_array($details)) { + $entry->set_details($details); + } + elseif (is_null($this->details)) { + $entry->set_details(array()); + } + + if (is_int($code)) { + $entry->set_code($code); + } + elseif (is_null($this->message)) { + $entry->set_code(0); + } + + if (is_int($severity) && $severity >= self::EMERGENCY && $severity < self::UNKNOWN) { + $entry->set_severity($severity); + } + elseif (is_null($this->message)) { + $entry->set_severity(self::ERROR); + } + + if ($limit === FALSE || (is_int($limit) && $limit >= 0)) { + $entry->set_limit($limit); + } + elseif (is_null($this->limit)) { + $entry->set_limit(self::DEFAULT_BACKTRACE_LIMIT); + } + + // @todo: call self::p_backtrace() accordingly. + + if (is_bool($report) && $report) { + // @todo: use the report object to report the problem. + // @fixme: this is setup as a bool, but I know I need to create and use a report object (which has yet to be created). + $this->reported = NULL; + } + else { + $this->reported = NULL; + } + + return $entry; + } + + /** + * Assign an error name string. + * + * @param string $name + * An error name string + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_name($name) { + if (!is_string($name)) { + return FALSE; + } + + $this->name = $name; + } + + /** + * Returns the assigned name. + * + * @return string|null + * A name to associate with the error, such as a variable or function name. + * NULL is returned if not defined. + */ + public function get_name() { + return $this->name; + } + + /** + * Assign an error message string. + * + * @param string $message + * An error message string + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_message($message) { + if (!is_string($message)) { + return FALSE; + } + + $this->message = $message; + } + + /** + * Returns the assigned message. + * + * @return string|null + * An error message string or NULL if not defined. + */ + public function get_message() { + return $this->message; + } + + /** + * Assigns the details array. + * + * @param array $details + * An array of details. + * + * @param bool + * TRUE on success, FALSE otherwise. + */ + public function set_details($details) { + if (!is_array($details)) { + return FALSE; + } + + $this->details = $details; + return TRUE; + } + + /** + * Returns the details array. + * + * The details array is defined by the caller and may have any structure, so long as it is an array. + * + * @return array|null + * An array of additional details or NULL if not defined. + */ + public function get_details() { + return $this->details; + } + + /** + * Assigns a severity level. + * + * @param int $severity + * A severity integer, representing the severity level. + * Such as self::ERROR. + */ + public function set_severity($severity) { + if (!is_int($severity) || $severity < 0 || $severity > 7) { + return FALSE; + } + + $this->severity = $severity; + return TRUE; + } + + /** + * Returns the currently assigned severity level. + * + * @return int + * The currently assigned severity level. + * This defaults to self::ERROR when undefined. + */ + public function get_severity() { + if (is_null($this->severity)) { + $this->severity = self::ERROR; + } + + return $this->severity; + } + + /** + * Assigns a limit for use with debug_backtrace(). + * + * @param int|bool $limit + * An integer representing the number of backtraces. + * A value of 0 means no limit (use with care, can result in performance/resource/timeout problems). + * A value of FALSE means disable backtrace for errors. + * + * @see: c_base_error::set_backtrace() + */ + public function set_limit($limit) { + if ($limit !== FALSE || !is_int($limit) || $limit < 0) { + return FALSE; + } + + $this->limit = $limit; + return TRUE; + } + + /** + * Returns the currently assigned limit. + * + * @return int|bool + * The currently assigned limit. + * This defaults to self::DEFAULT_BACKTRACE_LIMIT. + * + * @see: c_base_error::set_backtrace() + */ + public function get_limit() { + if ($limit !== FALSE || !is_int($limit) || $limit < 0) { + $this->limit = self::DEFAULT_BACKTRACE_LIMIT; + } + + return $this->limit; + } + + /** + * Assigns a backtrace. + * + * This is auto-performed by the class. + * All settings should be assigned prior to utilizing this. + * + * @return bool + * TRUE on success, FALSE otherwise. + * + * @see: c_base_error::set_limit() + */ + public function set_backtrace() { + $this->p_backtrace(1); + + return TRUE; + } + + /** + * Returns the backtrace object. + * + * @return object|null + * A populate backtrace object or NULL if no backtrace is defined. + * + * @see: c_base_error::set_limit() + */ + public function get_backtrace() { + return $this->backtrace; + } + + /** + * Assign an error code integer. + * + * A code is used to categorize the error in some manner. + * + * @param int $code + * An error code integer + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_code($code) { + if (!is_string($code)) { + return FALSE; + } + + $this->code = $code; + } + + /** + * Returns the assigned code. + * + * A code is used to categorize the error in some manner. + * + * @return int|null + * A code to associate with the error. + * NULL is returned if not defined. + */ + public function get_code() { + return $this->code; + } + + /** + * Assigns a reported. + * + * The reported variable is auto-processed by this class. + * All this does is reset the value to NULL. + * + * @return bool + * TRUE on success, FALSE otherwise. + * + * @see: c_base_error::set_limit() + */ + public function set_reported() { + unset($this->reported); + + $this->reported = NULL; + + return TRUE; + } + + /** + * Returns the reported object. + * + * Use this to determine the results of the last report status. + * + * @return object|null + * A populate reported object or NULL if no report was performed. + * + * @see: c_base_error::set_limit() + */ + public function get_reported() { + return $this->reported; + } + + /** + * Build the debug backtrace. + * + * This will not include this function in the backtrace. + * The backtrace will be stored as an object. + * + * @param int $count + * (optional) Assign a custom count that is used to prevent unecessary function calls from being included in the backtrace. + * This is essentially to remove the functions called to generate this backtrace, which do not matter. + * Instead, only the function where the error happens should the backtrace limit count apply. + * This does nothing when the limit is set to 0 (which means unlimited). + * + * @see: debug_backtrace() + */ + private function p_backtrace($count = 0) { + if ($this->limit === FALSE) { + $this->backtrace = NULL; + return; + } + + // Make sure unnecessary backtrace logs are not part of the count. + $limit = $this->limit; + if ($this->limit > 0) { + $limit = $this->limit + 1; + + if (is_int($count) && $count > 0) { + $limit += $count; + } + } + + $this->backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit); + unset($limit); + + // Remove unecessary backtrace logs. + $count = $count + 1; + $i = 0; + for (; $i < $count; $i++) { + array_shift($this->backtrace); + } + unset($i); + } +} diff --git a/common/base/classes/base_form.php b/common/base/classes/base_form.php new file mode 100644 index 0000000..2f724be --- /dev/null +++ b/common/base/classes/base_form.php @@ -0,0 +1,1494 @@ +id_internal = NULL; + $this->id_form = NULL; + $this->type = self::TYPE_NONE; + $this->data = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->id_internal); + unset($this->id_form); + unset($this->type); + unset($this->data); + } + + /** + * Assign the unique id which is intended to uniquely identify the form. + * + * @param int $id_internal + * The internal numeric id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_id_internal($id_internal) { + if (!is_int($id_internal)) { + return c_base_return_error::s_false(); + } + + $this->id_internal = $id_internal; + return new c_base_return_true(); + } + + /** + * Get the unique id which is intended to uniquely identify the form. + * + * @return c_base_return_string|c_base_return_false + * The unique internal id assigned to this object. + * FALSE is returned if the unique numeric tag id is not set. + * FALSE with error bit set is returned on error. + */ + public function get_id_internal() { + if (!isset($this->id_internal)) { + return new c_base_return_false(); + } + + return new c_base_return_string($this->id_internal); + } + + /** + * Assign the unique id which is intended to represent the form id string. + * + * @param string $id_form + * The form id string to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_id_form($id_form) { + if (!is_string($id)) { + return c_base_return_error::s_false(); + } + + $this->id_form = $id_form; + return new c_base_return_true(); + } + + /** + * Get the unique id which is intended to represent the form id string. + * + * @return c_base_return_string|c_base_return_false + * The form id string assigned to this object. + * FALSE is returned if the unique numeric tag id is not set. + * FALSE with error bit set is returned on error. + */ + public function get_id_form() { + if (!isset($this->id_form)) { + return new c_base_return_false(); + } + + return new c_base_return_string($this->id_form); + } + + /** + * Assign the data. + * + * @param int|string|float|bool|null $data + * The data whose type is directly dependend on $this->type. + * Failure to assign the proper type will result in an error. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_data($data) { + if ($this->type === self::TYPE_NONE) { + if (!is_null($data)) { + return c_base_return_error::s_false(); + } + } + elseif ($this->type === self::TYPE_TEXT) { + if (!is_string($data)) { + return c_base_return_error::s_false(); + } + } + elseif ($this->type === self::TYPE_BOOLEAN) { + if (!is_bool($data)) { + return c_base_return_error::s_false(); + } + } + elseif ($this->type === self::TYPE_NUMBER) { + if (!is_int($data)) { + return c_base_return_error::s_false(); + } + } + elseif ($this->type === self::TYPE_FLOAT) { + if (!is_float($data)) { + return c_base_return_error::s_false(); + } + } + else { + return c_base_return_error::s_false(); + } + + $this->data = $data; + return new c_base_return_true(); + } + + /** + * Get the assigned data. + * + * @return c_base_return_status|c_base_return_int|c_base_return_float|c_base_return_string|c_base_return_bool|c_base_return_null + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function get_data() { + if ($this->type === self::TYPE_NONE) { + return new c_base_return_null(); + } + elseif ($this->type === self::TYPE_TEXT) { + return new c_base_return_string($this->data); + } + elseif ($this->type === self::TYPE_BOOLEAN) { + if ($this->data) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + elseif ($this->type === self::TYPE_NUMBER) { + return new c_base_return_int($this->data); + } + elseif ($this->type === self::TYPE_FLOAT) { + return new c_base_return_float($this->data); + } + + return c_base_return_error::s_false(); + } + + /** + * Assign the type of data to be stored in this object. + * + * Assigning this will reset/clear the data. + * + * @param int $type + */ + public function set_type($type) { + if ($type === self::TYPE_NONE) { + $this->type = $type; + $this->data = NULL; + } + elseif ($type === self::TYPE_TEXT) { + $this->type = $type; + $this->data = ''; + } + elseif ($type === self::TYPE_BOOLEAN) { + $this->type = $type; + $this->data = FALSE; + } + elseif ($type === self::TYPE_NUMBER) { + $this->type = $type; + $this->data = 0; + } + elseif ($type === self::TYPE_FLOAT) { + $this->type = $type; + $this->data = 0.0; + } + else { + return c_base_return_error::s_false(); + } + + return new c_base_return_true(); + } + + /** + * Get the assigned type. + * + * @return c_base_return_status|c_base_return_int + */ + public function get_type() { + return new c_base_return_int($this->type); + } +} + +/** + * A generic class for storing form data. + * + * Each tag is expected to have a unique id. + * The internal id is an integer, primarily intended to be used in databases for performance reasons. + * The form id is a string that can be identical to the internal id. + * The form id is intended to represent the form id or form name as used on HTML form id tags. + * + */ +class c_base_form_data extends c_base_form_data_item { + private $data = array(); + + /** + * Class constructor. + */ + public function __construct() { + super.__construct(); + + $this->data = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + super.__destruct(); + + unset($this->data); + } + +} + +// below will be moved into output/theme areas. +/** + * A generic class for form tags. + * + * The unique tag id is an integer to be used for internal purposes but may be exposed on output. + * If the id attribute is defined, then on output the id attribute is used for the HTML tag. + * + * @see: https://www.w3.org/TR/html5/forms.html#forms + */ +class c_base_form_tag { + const TAG_NONE = 0; + const TAG_INPUT = 1; + const TAG_RADIO = 2; + const TAG_SUBMIT = 3; + const TAG_TEXT_AREA = 4; + const TAG_FIELD_SET = 5; + const TAG_SELECT = 6; + const TAG_OPTION = 7; + const TAG_DATA_LIST = 8; + const TAG_KEY_GEN = 9; + const TAG_OUTPUT = 10; + + const TYPE_NONE = 0; + const TYPE_TEXT = 1; + const TYPE_BUTTON = 2; + const TYPE_CHECKBOX = 3; + const TYPE_COLOR = 4; + const TYPE_DATE = 5; + const TYPE_DATETIME = 6; + const TYPE_DATETIME_LOCAL = 7; + const TYPE_EMAIL = 8; + const TYPE_FILE = 9; + const TYPE_HIDDEN = 10; + const TYPE_IMAGE = 11; + const TYPE_MONTH = 12; + const TYPE_NUMBER = 13; + const TYPE_PASSWORD = 14; + const TYPE_RADIO = 15; + const TYPE_RANGE = 16; + const TYPE_RESET = 17; + const TYPE_SEARCH = 18; + const TYPE_SUBMIT = 19; + const TYPE_TELEPHONE = 20; + const TYPE_TEXT = 21; + const TYPE_TIME = 22; + const TYPE_URL = 23; + const TYPE_WEEK = 24; + + private $id; + private $type; + private $attributes; + private $parent; + private $children; + + /** + * Class constructor. + */ + public function __construct() { + $this->id = NULL; + $this->type = self::TAG_NONE; + $this->attributes = array(); + $this->parent = NULL; + $this->children = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->id); + unset($this->type); + unset($this->attributes); + unset($this->parent); + unset($this->children); + } + + /** + * Assign the internal unique numeric tag id. + * + * @param int $id + * The internal numeric id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_id($id) { + if (!is_int($id)) { + return c_base_return_error::s_false(); + } + + $this->id = $id; + return new c_base_return_true(); + } + + /** + * Get the unique numeric tag id assigned to this object. + * + * @return c_base_return_int|c_base_return_false + * The tag type assigned to this class. + * FALSE is returned if the unique numeric tag id is not set. + * FALSE with error bit set is returned on error. + */ + public function get_id() { + if (!isset($this->id)) { + return new c_base_return_false(); + } + + return new c_base_return_int($this->id); + } + + /** + * Assign the specified tag type. + * + * @param int $type + * The tag type to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_type($type) { + if (!is_int($type)) { + return c_base_return_error::s_false(); + } + + switch ($type) { + case self::TAG_NONE: + case self::TAG_INPUT: + case self::TAG_RADIO: + case self::TAG_SUBMIT: + case self::TAG_TEXT_AREA: + case self::TAG_FIELD_SET: + case self::TAG_SELECT: + case self::TAG_OPTION: + case self::TAG_DATA_LIST: + case self::TAG_KEY_GEN: + case self::TAG_OUTPUT: + break; + default: + return new c_base_return_false(); + } + + $this->type = $type; + return new c_base_return_true(); + } + + /** + * Get the tag type assigned to this object. + * + * @return c_base_return_int + * The tag type assigned to this class. + * FALSE with error bit set is returned on error. + */ + public function get_type() { + if (!isset($this->type)) { + $this->type = self::TAG_NONE; + } + + return new c_base_return_int($this->type); + } + + /** + * Assign the specified tag. + * + * @param int $attribute + * The attribute to assign. + * @param $value + * The value of the attribute. + * The actual value type is specific to each attribute type. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_attribute($attribute, $value) { + if (!is_int($attribute)) { + return c_base_return_error::s_false(); + } + + switch ($attribute) { + case c_base_form_attributes::ATTRIBUTE_NONE: + unset($this->attribute[$attribute]); + return new c_base_return_true(); + + case c_base_form_attributes::ATTRIBUTE_ACTION: + case c_base_form_attributes::ATTRIBUTE_DIRECTION_NAME: + case c_base_form_attributes::ATTRIBUTE_FOR: + case c_base_form_attributes::ATTRIBUTE_FORM: + case c_base_form_attributes::ATTRIBUTE_FORM_ACTION: + case c_base_form_attributes::ATTRIBUTE_FORM_TARGET: + case c_base_form_attributes::ATTRIBUTE_KEY_TYPE: + case c_base_form_attributes::ATTRIBUTE_LABEL: + case c_base_form_attributes::ATTRIBUTE_LIST: + case c_base_form_attributes::ATTRIBUTE_NAME: + case c_base_form_attributes::ATTRIBUTE_ON_ABORT: + case c_base_form_attributes::ATTRIBUTE_ON_AFTER_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_END: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_ITERATION: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_start: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_BLUR: + case c_base_form_attributes::ATTRIBUTE_ON_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_CONTEXT_MENU: + case c_base_form_attributes::ATTRIBUTE_ON_COPY: + case c_base_form_attributes::ATTRIBUTE_ON_CUT: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY_THROUGH: + case c_base_form_attributes::ATTRIBUTE_ON_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_DOUBLE_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_END: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_START: + case c_base_form_attributes::ATTRIBUTE_ON_DROP: + case c_base_form_attributes::ATTRIBUTE_ON_DURATION_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_EMPTIED: + case c_base_form_attributes::ATTRIBUTE_ON_ENDED: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_IN: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_HASH_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_INPUT: + case c_base_form_attributes::ATTRIBUTE_ON_INVALID: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_PRESS: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_UP: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_META_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD_START: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_UP: + case c_base_form_attributes::ATTRIBUTE_ON_MESSAGE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_WHEEL: + case c_base_form_attributes::ATTRIBUTE_ON_OPEN: + case c_base_form_attributes::ATTRIBUTE_ON_ONLINE: + case c_base_form_attributes::ATTRIBUTE_ON_OFFLINE: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_HIDE: + case c_base_form_attributes::ATTRIBUTE_ON_PASTE: + case c_base_form_attributes::ATTRIBUTE_ON_PAUSE: + case c_base_form_attributes::ATTRIBUTE_ON_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_PLAYING: + case c_base_form_attributes::ATTRIBUTE_ON_PROGRESS: + case c_base_form_attributes::ATTRIBUTE_ON_POP_STATE: + case c_base_form_attributes::ATTRIBUTE_ON_RESIZE: + case c_base_form_attributes::ATTRIBUTE_ON_RESET: + case c_base_form_attributes::ATTRIBUTE_ON_RATE_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_SCROLL: + case c_base_form_attributes::ATTRIBUTE_ON_SEARCH: + case c_base_form_attributes::ATTRIBUTE_ON_SELECT: + case c_base_form_attributes::ATTRIBUTE_ON_SUBMIT: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKED: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKING: + case c_base_form_attributes::ATTRIBUTE_ON_STALLED: + case c_base_form_attributes::ATTRIBUTE_ON_SUSPEND: + case c_base_form_attributes::ATTRIBUTE_ON_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_STORAGE: + case c_base_form_attributes::ATTRIBUTE_ON_TIME_UPDATE: + case c_base_form_attributes::ATTRIBUTE_ON_TRANSITION_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOGGLE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_CANCEL: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_START: + case c_base_form_attributes::ATTRIBUTE_ON_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_VOLUME_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_WAITING: + case c_base_form_attributes::ATTRIBUTE_ON_WHEEL: + case c_base_form_attributes::ATTRIBUTE_PATTERN: + case c_base_form_attributes::ATTRIBUTE_PLACE_HOLDER: + case c_base_form_attributes::ATTRIBUTE_READONLY: + case c_base_form_attributes::ATTRIBUTE_REQUIRED: + case c_base_form_attributes::ATTRIBUTE_ROWS: + case c_base_form_attributes::ATTRIBUTE_SELECTED: + case c_base_form_attributes::ATTRIBUTE_SIZE: + case c_base_form_attributes::ATTRIBUTE_SOURCE: + case c_base_form_attributes::ATTRIBUTE_STEP: + case c_base_form_attributes::ATTRIBUTE_TYPE: + case c_base_form_attributes::ATTRIBUTE_WRAP: + case c_base_form_attributes::ATTRIBUTE_VALUE: + if (!is_string($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_FORM_NO_VALIDATED: + case c_base_form_attributes::ATTRIBUTE_AUTO_COMPLETE: + case c_base_form_attributes::ATTRIBUTE_AUTO_FOCUS: + case c_base_form_attributes::ATTRIBUTE_CHALLENGE: + case c_base_form_attributes::ATTRIBUTE_CHECKED: + case c_base_form_attributes::ATTRIBUTE_DISABLED: + case c_base_form_attributes::ATTRIBUTE_MULTIPLE: + if (!is_bool($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_ACCEPT: + case c_base_form_attributes::ATTRIBUTE_FORM_ENCODE_TYPE: + if (!this->pr_validate_value_mime_type($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_COLUMNS: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM_LENGTH: + case c_base_form_attributes::ATTRIBUTE_MINIMUM: + if (!is_int($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_FORM_METHOD: + if (!this->pr_validate_value_http_method($value)) { + return c_base_return_false(); + } + break; + + default: + return new c_base_return_false(); + } + + $this->attribute[$attribute] = $value; + + return new c_base_return_true(); + } + + /** + * Get the attributes assigned to this object. + * + * @return c_base_return_array + * The attributes assigned to this class. + * FALSE with error bit set is returned on error. + */ + public function get_attributes() { + if (!isset($this->attributes) && !is_array($this->attributes)) { + $this->attributes = array(); + } + + return new c_base_return_array($this->attributes); + } + + /** + * Get the value of a single attribute assigned to this object. + * + * @param int $attribute + * The attribute to assign. + * + * @return c_base_return_int|c_base_return_string|c_base_return_bool|c_base_return_false + * The value assigned to the attribte (the data type is different per attribute). + * FALSE is returned if the element does not exist. + * FALSE with error bit set is returned on error. + */ + public function get_attribute($attribute) { + if (!is_int($attribute)) { + return c_base_return_error::s_false(); + } + + if (!isset($this->attributes) && !is_array($this->attributes)) { + $this->attributes = array(); + } + + if (array_key_exists($attribute, $this->attributes)) { + switch ($attribute) { + case c_base_form_attributes::ATTRIBUTE_NONE: + // should not be possible, so consider this an error (attributes set to NONE are actually unset from the array). + return c_base_return_error::s_false(); + + case c_base_form_attributes::ATTRIBUTE_ACTION: + case c_base_form_attributes::ATTRIBUTE_DIRECTION_NAME: + case c_base_form_attributes::ATTRIBUTE_FOR: + case c_base_form_attributes::ATTRIBUTE_FORM: + case c_base_form_attributes::ATTRIBUTE_FORM_ACTION: + case c_base_form_attributes::ATTRIBUTE_FORM_TARGET: + case c_base_form_attributes::ATTRIBUTE_KEY_TYPE: + case c_base_form_attributes::ATTRIBUTE_LABEL: + case c_base_form_attributes::ATTRIBUTE_LIST: + case c_base_form_attributes::ATTRIBUTE_NAME: + case c_base_form_attributes::ATTRIBUTE_ON_ABORT: + case c_base_form_attributes::ATTRIBUTE_ON_AFTER_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_END: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_ITERATION: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_start: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_BLUR: + case c_base_form_attributes::ATTRIBUTE_ON_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_CONTEXT_MENU: + case c_base_form_attributes::ATTRIBUTE_ON_COPY: + case c_base_form_attributes::ATTRIBUTE_ON_CUT: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY_THROUGH: + case c_base_form_attributes::ATTRIBUTE_ON_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_DOUBLE_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_END: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_START: + case c_base_form_attributes::ATTRIBUTE_ON_DROP: + case c_base_form_attributes::ATTRIBUTE_ON_DURATION_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_EMPTIED: + case c_base_form_attributes::ATTRIBUTE_ON_ENDED: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_IN: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_HASH_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_INPUT: + case c_base_form_attributes::ATTRIBUTE_ON_INVALID: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_PRESS: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_UP: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_META_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD_START: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_UP: + case c_base_form_attributes::ATTRIBUTE_ON_MESSAGE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_WHEEL: + case c_base_form_attributes::ATTRIBUTE_ON_OPEN: + case c_base_form_attributes::ATTRIBUTE_ON_ONLINE: + case c_base_form_attributes::ATTRIBUTE_ON_OFFLINE: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_HIDE: + case c_base_form_attributes::ATTRIBUTE_ON_PASTE: + case c_base_form_attributes::ATTRIBUTE_ON_PAUSE: + case c_base_form_attributes::ATTRIBUTE_ON_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_PLAYING: + case c_base_form_attributes::ATTRIBUTE_ON_PROGRESS: + case c_base_form_attributes::ATTRIBUTE_ON_POP_STATE: + case c_base_form_attributes::ATTRIBUTE_ON_RESIZE: + case c_base_form_attributes::ATTRIBUTE_ON_RESET: + case c_base_form_attributes::ATTRIBUTE_ON_RATE_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_SCROLL: + case c_base_form_attributes::ATTRIBUTE_ON_SEARCH: + case c_base_form_attributes::ATTRIBUTE_ON_SELECT: + case c_base_form_attributes::ATTRIBUTE_ON_SUBMIT: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKED: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKING: + case c_base_form_attributes::ATTRIBUTE_ON_STALLED: + case c_base_form_attributes::ATTRIBUTE_ON_SUSPEND: + case c_base_form_attributes::ATTRIBUTE_ON_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_STORAGE: + case c_base_form_attributes::ATTRIBUTE_ON_TIME_UPDATE: + case c_base_form_attributes::ATTRIBUTE_ON_TRANSITION_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOGGLE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_CANCEL: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_START: + case c_base_form_attributes::ATTRIBUTE_ON_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_VOLUME_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_WAITING: + case c_base_form_attributes::ATTRIBUTE_ON_WHEEL: + case c_base_form_attributes::ATTRIBUTE_PATTERN: + case c_base_form_attributes::ATTRIBUTE_READONLY: + case c_base_form_attributes::ATTRIBUTE_REQUIRED: + case c_base_form_attributes::ATTRIBUTE_ROWS: + case c_base_form_attributes::ATTRIBUTE_SELECTED: + case c_base_form_attributes::ATTRIBUTE_SIZE: + case c_base_form_attributes::ATTRIBUTE_SOURCE: + case c_base_form_attributes::ATTRIBUTE_STEP: + case c_base_form_attributes::ATTRIBUTE_TYPE: + case c_base_form_attributes::ATTRIBUTE_WRAP: + case c_base_form_attributes::ATTRIBUTE_PLACE_HOLDER: + case c_base_form_attributes::ATTRIBUTE_VALUE: + return c_base_return_string::s_new($value); + + case c_base_form_attributes::ATTRIBUTE_FORM_NO_VALIDATED: + case c_base_form_attributes::ATTRIBUTE_AUTO_COMPLETE: + case c_base_form_attributes::ATTRIBUTE_AUTO_FOCUS: + case c_base_form_attributes::ATTRIBUTE_CHALLENGE: + case c_base_form_attributes::ATTRIBUTE_CHECKED: + case c_base_form_attributes::ATTRIBUTE_DISABLED: + case c_base_form_attributes::ATTRIBUTE_MULTIPLE: + return c_base_return_bool::s_new($value); + + case c_base_form_attributes::ATTRIBUTE_ACCEPT: + case c_base_form_attributes::ATTRIBUTE_FORM_ENCODE_TYPE: + return c_base_return_int::s_new($value); + + case c_base_form_attributes::ATTRIBUTE_COLUMNS: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM_LENGTH: + case c_base_form_attributes::ATTRIBUTE_MINIMUM: + return c_base_return_int::s_new($value); + + case c_base_form_attributes::ATTRIBUTE_FORM_METHOD: + return c_base_return_int::s_new($value); + + default: + return new c_base_return_false(); + } + } + + $this->attribute[$attribute] = $value; + + return new c_base_return_false(); + } + + /** + * Assign the specified numeric id as the parent tag. + * + * @param int $parent_id + * The numeric id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_parent($parent_id) { + if (!is_int($parent_id)) { + return c_base_return_error::s_false(); + } + + $this->parent = $parent_id; + return new c_base_return_true(); + } + + /** + * Get the parent tag numeric id associated with this. + * + * @return c_base_return_int|c_base_return_false + * The tag type assigned to this class. + * FALSE without error bit is set when no parent is defined. + * FALSE with error bit set is returned on error. + */ + public function get_parent() { + if (!isset($this->parent)) { + return new c_base_return_false(); + } + + return new c_base_return_int($this->parent); + } + + /** + * Add a child tag. + * + * @param c_base_form_tag $child + * The numeric id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_child($child_tag) { + if (!($child_tag instanceof c_base_form_tag)) { + return c_base_return_error::s_false(); + } + + // this requires the class to have a tag id assigned. + if (!is_int($this->id)) { + return c_base_return_error::s_false(); + } + + $child_id = $child_tag->get_id(); + if (!($child_id instanceof c_base_return_int)) { + unset($child_id); + return c_base_return_error::s_false(); + } + $child_id = $child_id->get_value_exact(); + + $child_tag->set_parent($this->id); + $this->children[$child_id] = $child_tag; + unset($child_id); + + return new c_base_return_true(); + } + + /** + * Get the specified child by its unique numeric tag id. + * + * @param int $child_id + * The unique numeric tag id of the child. + * + * @return c_base_form_return_tag|c_base_return_false + * The tag type assigned to this class. + * FALSE without error bit is set when the child is not defined. + * FALSE with error bit set is returned on error. + */ + public function get_child($child_id) { + if (!is_array($this->children) || !array_key_exists($child_id, $this->children)) { + return new c_base_return_false(); + } + + if (!($this->children[$child_id] instanceof c_base_form_tag)) { + return c_base_return_error::s_false(); + } + + return new c_base_form_return_tag($this->children[$child_id]); + } + + /** + * Assign an array of child tags. + * + * @param array $children + * An array of children to assign. + * The array key must be an integer or it is ignore. + * The array value must be a c_base_form_tag instance or it is ignored. + * @param bool $append + * (optional) When TRUE, child elements are appended. + * When FALSE, any existing children are removed. + * + * @return c_base_return_int|c_base_return_false + * An integer representing the number of children successfully added. + * FALSE with error bit set is returned on error. + */ + public function set_children($children, $append = FALSE) { + if (!is_array($children)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + if (!$append) { + $this->children = array(); + } + + $added = 0; + foreach ($children as $child_id => $child_tag) { + if (!is_numeric($child_id) || !($child_tag instanceof c_base_form_tag)) { + continue; + } + + $child_tag_id = $child_tag->get_id(); + if (!($child_tag_id instanceof c_base_return_int) || $child_id != $child_tag_id->get_value_exact()) { + unset($child_tag_id); + continue; + } + unset($child_tag_id); + + $child_tag->set_parent($this->id); + $this->children[(int) $child_id] = $child_tag; + $added++; + } + unset($child_id); + unset($child_tag); + + return new c_base_return_int($added); + } + + /** + * Get all assigned child tags. + * + * @return c_base_return_array|c_base_return_false + * The array of child tags. + * FALSE with error bit set is returned on error. + */ + public function get_children() { + if (!is_array($this->children)) { + $this->children = array(); + } + + return new c_base_return_array($this->children); + } + + /** + * Protected function for mime values. + * + * @param int $value + * The value of the attribute populate from c_base_mime. + * + * @return bool + * TRUE on success. + * FALSE otherwise. + */ + protected function pr_validate_value_mime_type($value) { + if (!is_int($value)) { + return FALSE; + } + + switch ($value) { + case c_base_mime::CATEGORY_UNKNOWN: + case c_base_mime::CATEGORY_PROVIDED: + case c_base_mime::CATEGORY_STREAM: + case c_base_mime::CATEGORY_TEXT: + case c_base_mime::CATEGORY_IMAGE: + case c_base_mime::CATEGORY_AUDIO: + case c_base_mime::CATEGORY_VIDEO: + case c_base_mime::CATEGORY_DOCUMENT: + case c_base_mime::CATEGORY_CONTAINER: + case c_base_mime::CATEGORY_APPLICATION: + case c_base_mime::TYPE_UNKNOWN: + case c_base_mime::TYPE_PROVIDED: + case c_base_mime::TYPE_STREAM: + case c_base_mime::TYPE_TEXT_PLAIN: + case c_base_mime::TYPE_TEXT_HTML: + case c_base_mime::TYPE_TEXT_RSS: + case c_base_mime::TYPE_TEXT_ICAL: + case c_base_mime::TYPE_TEXT_CSV: + case c_base_mime::TYPE_TEXT_XML: + case c_base_mime::TYPE_TEXT_CSS: + case c_base_mime::TYPE_TEXT_JS: + case c_base_mime::TYPE_TEXT_JSON: + case c_base_mime::TYPE_TEXT_RICH: + case c_base_mime::TYPE_TEXT_XHTML: + case c_base_mime::TYPE_TEXT_PS: + case c_base_mime::TYPE_IMAGE_PNG: + case c_base_mime::TYPE_IMAGE_GIF: + case c_base_mime::TYPE_IMAGE_JPEG: + case c_base_mime::TYPE_IMAGE_BMP: + case c_base_mime::TYPE_IMAGE_SVG: + case c_base_mime::TYPE_IMAGE_TIFF: + case c_base_mime::TYPE_AUDIO_WAV: + case c_base_mime::TYPE_AUDIO_OGG: + case c_base_mime::TYPE_AUDIO_MP3: + case c_base_mime::TYPE_AUDIO_MP4: + case c_base_mime::TYPE_AUDIO_MIDI: + case c_base_mime::TYPE_VIDEO_MPEG: + case c_base_mime::TYPE_VIDEO_OGG: + case c_base_mime::TYPE_VIDEO_H264: + case c_base_mime::TYPE_VIDEO_QUICKTIME: + case c_base_mime::TYPE_VIDEO_DV: + case c_base_mime::TYPE_VIDEO_JPEG: + case c_base_mime::TYPE_VIDEO_WEBM: + case c_base_mime::TYPE_DOCUMENT_LIBRECHART: + case c_base_mime::TYPE_DOCUMENT_LIBREFORMULA: + case c_base_mime::TYPE_DOCUMENT_LIBREGRAPHIC: + case c_base_mime::TYPE_DOCUMENT_LIBREPRESENTATION: + case c_base_mime::TYPE_DOCUMENT_LIBRESPREADSHEET: + case c_base_mime::TYPE_DOCUMENT_LIBRETEXT: + case c_base_mime::TYPE_DOCUMENT_LIBREHTML: + case c_base_mime::TYPE_DOCUMENT_PDF: + case c_base_mime::TYPE_DOCUMENT_ABIWORD: + case c_base_mime::TYPE_DOCUMENT_MSWORD: + case c_base_mime::TYPE_DOCUMENT_MSEXCEL: + case c_base_mime::TYPE_DOCUMENT_MSPOWERPOINT: + case c_base_mime::TYPE_CONTAINER_TAR: + case c_base_mime::TYPE_CONTAINER_CPIO: + case c_base_mime::TYPE_CONTAINER_JAVA: + break; + default: + return FALSE; + } + + return TRUE; + } + + /** + * Protected function for http method values. + * + * Only GET and POST methods are supported. + * + * @param int $value + * The value of the attribute populate from http method. + * + * @return bool + * TRUE on success. + * FALSE otherwise. + */ + protected function pr_validate_value_http_method($value) { + if (!is_int($value)) { + return FALSE; + } + + switch ($value) { + case c_base_http::HTTP_METHOD_NONE: + case c_base_http::HTTP_METHOD_GET: + case c_base_http::HTTP_METHOD_POST: + break; + default: + return FALSE; + } + + return TRUE; + } +} + +/** + * A generic class for form fields. + * + * The unique tag id is an integer to be used for internal purposes but may be exposed on output. + * If the id attribute is defined, then on output the id attribute is used for the HTML tag. + * + * This uses a simple approach to store different form inputs. + * Each input has a single depth group (with NULL being the default group). + * The input fields do not relate directly to the form input. + * + * @see: https://www.w3.org/TR/html5/forms.html#forms + */ +class c_base_form { + private $id; + private $inputs; + private $attributes; + + /** + * Class constructor. + */ + public function __construct() { + $this->id = NULL; + $this->tags = array(); + $this->attributes = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->id); + unset($this->tags); + unset($this->attributes); + } + + /** + * Assign a unique numeric id to represent this form. + * + * @param int $id + * The unique form tag id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_id($id) { + if (!is_int($id)) { + return c_base_return_error::s_false(); + } + + $this->id = $id; + return new c_base_return_true(); + } + + /** + * Get the unique id assigned to this object. + * + * @return c_base_return_int|c_base_return_false + * The unique numeric id assigned to this object. + * FALSE is returned if no id is assigned. + * FALSE with error bit set is returned on error. + */ + public function get_id() { + if (!is_int($this->id)) { + return c_base_return_false(); + } + + return new c_base_return_int($this->id); + } + + /** + * Assign the specified tag. + * + * @param int $attribute + * The attribute to assign. + * @param $value + * The value of the attribute. + * The actual value type is specific to each attribute type. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_attribute($attribute, $value) { + if (!is_int($attribute)) { + return c_base_return_error::s_false(); + } + + switch ($attribute) { + case c_base_form_attributes::ATTRIBUTE_NONE: + unset($this->attribute[$attribute]); + return new c_base_return_true(); + + case c_base_form_attributes::ATTRIBUTE_ACTION: + case c_base_form_attributes::ATTRIBUTE_DIRECTION_NAME: + case c_base_form_attributes::ATTRIBUTE_FOR: + case c_base_form_attributes::ATTRIBUTE_FORM: + case c_base_form_attributes::ATTRIBUTE_FORM_ACTION: + case c_base_form_attributes::ATTRIBUTE_FORM_TARGET: + case c_base_form_attributes::ATTRIBUTE_KEY_TYPE: + case c_base_form_attributes::ATTRIBUTE_LABEL: + case c_base_form_attributes::ATTRIBUTE_LIST: + case c_base_form_attributes::ATTRIBUTE_NAME: + case c_base_form_attributes::ATTRIBUTE_ON_ABORT: + case c_base_form_attributes::ATTRIBUTE_ON_AFTER_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_END: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_ITERATION: + case c_base_form_attributes::ATTRIBUTE_ON_ANIMATION_start: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_BEFORE_PRINT: + case c_base_form_attributes::ATTRIBUTE_ON_BLUR: + case c_base_form_attributes::ATTRIBUTE_ON_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_CONTEXT_MENU: + case c_base_form_attributes::ATTRIBUTE_ON_COPY: + case c_base_form_attributes::ATTRIBUTE_ON_CUT: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_CAN_PLAY_THROUGH: + case c_base_form_attributes::ATTRIBUTE_ON_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_DOUBLE_CLICK: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_END: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_DRAG_START: + case c_base_form_attributes::ATTRIBUTE_ON_DROP: + case c_base_form_attributes::ATTRIBUTE_ON_DURATION_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_EMPTIED: + case c_base_form_attributes::ATTRIBUTE_ON_ENDED: + case c_base_form_attributes::ATTRIBUTE_ON_ERROR: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_IN: + case c_base_form_attributes::ATTRIBUTE_ON_FOCUS_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_HASH_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_INPUT: + case c_base_form_attributes::ATTRIBUTE_ON_INVALID: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_PRESS: + case c_base_form_attributes::ATTRIBUTE_ON_KEY_UP: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOADED_META_DATA: + case c_base_form_attributes::ATTRIBUTE_ON_LOAD_START: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_DOWN: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_ENTER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_LEAVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OVER: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_OUT: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_UP: + case c_base_form_attributes::ATTRIBUTE_ON_MESSAGE: + case c_base_form_attributes::ATTRIBUTE_ON_MOUSE_WHEEL: + case c_base_form_attributes::ATTRIBUTE_ON_OPEN: + case c_base_form_attributes::ATTRIBUTE_ON_ONLINE: + case c_base_form_attributes::ATTRIBUTE_ON_OFFLINE: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_PAGE_HIDE: + case c_base_form_attributes::ATTRIBUTE_ON_PASTE: + case c_base_form_attributes::ATTRIBUTE_ON_PAUSE: + case c_base_form_attributes::ATTRIBUTE_ON_PLAY: + case c_base_form_attributes::ATTRIBUTE_ON_PLAYING: + case c_base_form_attributes::ATTRIBUTE_ON_PROGRESS: + case c_base_form_attributes::ATTRIBUTE_ON_POP_STATE: + case c_base_form_attributes::ATTRIBUTE_ON_RESIZE: + case c_base_form_attributes::ATTRIBUTE_ON_RESET: + case c_base_form_attributes::ATTRIBUTE_ON_RATE_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_SCROLL: + case c_base_form_attributes::ATTRIBUTE_ON_SEARCH: + case c_base_form_attributes::ATTRIBUTE_ON_SELECT: + case c_base_form_attributes::ATTRIBUTE_ON_SUBMIT: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKED: + case c_base_form_attributes::ATTRIBUTE_ON_SEEKING: + case c_base_form_attributes::ATTRIBUTE_ON_STALLED: + case c_base_form_attributes::ATTRIBUTE_ON_SUSPEND: + case c_base_form_attributes::ATTRIBUTE_ON_SHOW: + case c_base_form_attributes::ATTRIBUTE_ON_STORAGE: + case c_base_form_attributes::ATTRIBUTE_ON_TIME_UPDATE: + case c_base_form_attributes::ATTRIBUTE_ON_TRANSITION_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOGGLE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_CANCEL: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_END: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_MOVE: + case c_base_form_attributes::ATTRIBUTE_ON_TOUCH_START: + case c_base_form_attributes::ATTRIBUTE_ON_UNLOAD: + case c_base_form_attributes::ATTRIBUTE_ON_VOLUME_CHANGE: + case c_base_form_attributes::ATTRIBUTE_ON_WAITING: + case c_base_form_attributes::ATTRIBUTE_ON_WHEEL: + case c_base_form_attributes::ATTRIBUTE_PATTERN: + case c_base_form_attributes::ATTRIBUTE_PLACE_HOLDER: + case c_base_form_attributes::ATTRIBUTE_READONLY: + case c_base_form_attributes::ATTRIBUTE_REQUIRED: + case c_base_form_attributes::ATTRIBUTE_ROWS: + case c_base_form_attributes::ATTRIBUTE_SELECTED: + case c_base_form_attributes::ATTRIBUTE_SIZE: + case c_base_form_attributes::ATTRIBUTE_SOURCE: + case c_base_form_attributes::ATTRIBUTE_STEP: + case c_base_form_attributes::ATTRIBUTE_TYPE: + case c_base_form_attributes::ATTRIBUTE_WRAP: + case c_base_form_attributes::ATTRIBUTE_VALUE: + if (!is_string($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_FORM_NO_VALIDATED: + case c_base_form_attributes::ATTRIBUTE_AUTO_COMPLETE: + case c_base_form_attributes::ATTRIBUTE_AUTO_FOCUS: + case c_base_form_attributes::ATTRIBUTE_CHALLENGE: + case c_base_form_attributes::ATTRIBUTE_CHECKED: + case c_base_form_attributes::ATTRIBUTE_DISABLED: + case c_base_form_attributes::ATTRIBUTE_MULTIPLE: + if (!is_bool($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_ACCEPT: + case c_base_form_attributes::ATTRIBUTE_FORM_ENCODE_TYPE: + if (!this->pr_validate_value_mime_type($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_COLUMNS: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM: + case c_base_form_attributes::ATTRIBUTE_MAXIMUM_LENGTH: + case c_base_form_attributes::ATTRIBUTE_MINIMUM: + if (!is_int($value)) { + return c_base_return_false(); + } + break; + + case c_base_form_attributes::ATTRIBUTE_FORM_METHOD: + if (!this->pr_validate_value_http_method($value)) { + return c_base_return_false(); + } + break; + + default: + return new c_base_return_false(); + } + + $this->attribute[$attribute] = $value; + + return new c_base_return_true(); + } + + /** + * Get the attributes assigned to this object. + * + * @return c_base_return_array + * The attributes assigned to this class. + * FALSE with error bit set is returned on error. + */ + public function get_attributes() { + if (!isset($this->attributes) && !is_array($this->attributes)) { + $this->attributes = array(); + } + + return new c_base_return_array($this->attributes); + } +} + diff --git a/common/base/classes/base_html.php b/common/base/classes/base_html.php new file mode 100644 index 0000000..a53b165 --- /dev/null +++ b/common/base/classes/base_html.php @@ -0,0 +1,459 @@ += 0) { + return new c_base_return_true(); + } + } + } + else { + if (is_int($value)) { + return new c_base_return_true(); + } + } + + return new c_base_return_false(); + } + + /** + * Validate that value is a float value. + * + * @param float $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#floating-point-numbers + */ + public static function is_float($value) { + if (is_float($value)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Validate that value is an unsigned number value. + * + * This is functionaly the same as a float or an unsigned integer. + * + * @param float $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: self::is_float() + * @see: https://www.w3.org/TR/html5/infrastructure.html#percentages-and-dimensions + */ + public static function is_dimension($value) { + if (is_int($value) || is_float($value)) { + if ($value >= 0) { + return new c_base_return_true(); + } + } + + return new c_base_return_false(); + } + + /** + * Validate that value is a list of numbers. + * + * @param array $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#lists-of-integers + */ + public static function is_number_list($value) { + if (!is_array($value)) { + return new c_base_return_false(); + } + + foreach ($value as $part) { + if (!is_int($part)) { + unset($part); + return c_base_return_false(); + } + } + unset($part); + + return new c_base_return_true(); + } + + /** + * Validate that value is a list of dimensions. + * + * @param array $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#lists-of-dimensions + */ + public static function is_dimension_list($value) { + if (!is_array($value)) { + return new c_base_return_false(); + } + + foreach ($value as $part) { + if (!is_int($part) && !is_float($part)) { + unset($part); + return c_base_return_false(); + } + } + unset($part); + + return new c_base_return_true(); + } + + /** + * Validate that value is a date or time value. + * + * A date or time is expected to be a unix timestamp, which is a valid integer. + * + * @param int $value + * The value to validate. + * @param bool $as_float + * (optional) When TRUE, allow the date to be a float. + * When FALSE, date is treated as an integer. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#dates-and-times + * @see: https://www.w3.org/TR/html5/infrastructure.html#months + * @see: https://www.w3.org/TR/html5/infrastructure.html#floating-dates-and-times + * @see: https://www.w3.org/TR/html5/infrastructure.html#vaguer-moments-in-time + */ + public static function is_date($value, $as_float = FALSE) { + if (!is_bool($as_float)) { + return c_base_return_error::s_false(); + } + + if ($as_float) { + if (is_float($value)) { + return new c_base_return_true(); + } + } + else { + if (is_int($value)) { + return new c_base_return_true(); + } + } + + return new c_base_return_false(); + } + + /** + * Validate that value is a list of dates or times. + * + * @param array $value + * The value to validate. + * @param bool $as_float + * (optional) When TRUE, allow the date to be a float. + * When FALSE, date is treated as an integer. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#dates + * @see: https://www.w3.org/TR/html5/infrastructure.html#yearless-dates + * @see: https://www.w3.org/TR/html5/infrastructure.html#times + */ + public static function is_date_list($value, $as_float = FALSE) { + if (!is_bool($as_float)) { + return c_base_return_error::s_false(); + } + + if (!is_array($value)) { + return new c_base_return_false(); + } + + if ($as_float) { + foreach ($value as $part) { + if (!is_float($part)) { + unset($part); + return c_base_return_false(); + } + } + unset($part); + } + else { + foreach ($value as $part) { + if (!is_int($part)) { + unset($part); + return c_base_return_false(); + } + } + unset($part); + } + + return new c_base_return_true(); + } + + /** + * Validate that value is a token value. + * + * This processes a single token. + * There are two types: + * 1) Space Separated + * 2) Comma Separated + * + * Perhaps I am misreading but the standard does not seem clear about other symbols. + * For now, I am just allowing them (I can come back later and fix this at any time). + * + * @param string $value + * The value to validate. + * @param bool $comma_separated + * (optional) When TRUE, token is treated as a comma separated token. + * When FALSE, token is treated as space separated token. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * @see: https://www.w3.org/TR/html5/infrastructure.html#space-separated-tokens + * @see: https://www.w3.org/TR/html5/infrastructure.html#comma-separated-tokens + */ + public static function is_token($value, $comma_separated = FALSE) { + if (!is_bool($comma_separated)) { + return c_base_return_error::s_false(); + } + + if (is_string($value)) { + if ($comma_Separated) { + if (strpos($value, ',') !== FALSE) { + return new c_base_return_true(); + } + } + else { + if (preg_match('/\s/i', $value) === 0) { + return new c_base_return_true(); + } + } + } + + return new c_base_return_false(); + } + + /** + * Validate that value is a list of syntax references. + * + * @param array $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * https://www.w3.org/TR/html5/infrastructure.html#syntax-references + */ + public static function is_syntax_reference($value) { + if (!is_string($value)) { + return new c_base_return_false(); + } + + if (preg_match('/^#(^s)+/i', $value) > 0) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Validate that value is a list of syntax references. + * + * @param array $value + * The value to validate. + * + * @return c_base_return_status + * TRUE on valid, FALSE on invalid. + * FALSE with error bit set is returned on error. + * + * https://www.w3.org/TR/html5/infrastructure.html#urls + */ + public static function is_url($value) { + if (!is_string($value)) { + return new c_base_return_false(); + } + + // @todo. + + return new c_base_return_false(); + } +} diff --git a/common/base/classes/base_http.php b/common/base/classes/base_http.php new file mode 100644 index 0000000..bcdc0ac --- /dev/null +++ b/common/base/classes/base_http.php @@ -0,0 +1,7911 @@ + 9. + const ENCODING_BZIP = 5; // Compression Options: 1 -> 9. + const ENCODING_LZO = 6; // Compression Options: LZO1_99, LZO1A_99, LZO1B_999, LZO1C_999, LZO1F_999, LZO1X_999, LZO1Y_999, LZO1Z_999, LZO2A_999 (and many more). + const ENCODING_XZ = 7; + const ENCODING_EXI = 8; + const ENCODING_IDENTITY = 9; + const ENCODING_SDCH = 10; + const ENCODING_PG = 11; + + // timestamps + const TIMESTAMP_RFC_5322 = 'D, d M Y H:i:s T'; // rfc5322 is the preferred/recommended format. + const TIMESTAMP_RFC_1123 = 'D, d M Y H:i:s O'; + const TIMESTAMP_RFC_850 = 'l, d-M-y H:i:s T'; + + // http methods + const HTTP_METHOD_NONE = 0; + const HTTP_METHOD_GET = 1; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_HEAD = 2; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_POST = 3; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_PUT = 4; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_DELETE = 5; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_TRACE = 6; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Security + const HTTP_METHOD_OPTIONS = 7; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_CONNECT = 8; + const HTTP_METHOD_PATCH = 9; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods + const HTTP_METHOD_TRACK = 10; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Security + const HTTP_METHOD_DEBUG = 11; // https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Security + + private $headers; + private $headers_sent; + private $request; + private $request_time; + private $response; + + private $content; + private $content_is_file; + private $buffer_enabled; + + private $language_class; + + /** + * Class constructor. + */ + public function __construct() { + $this->headers = NULL; + $this->headers_sent = FALSE; + $this->request = array(); + $this->request_time = NULL; + $this->response = array(); + + $this->content = NULL; + $this->content_is_file = NULL; + $this->buffer_enabled = FALSE; + + $this->language_class = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->headers); + unset($this->headers_sent); + unset($this->request); + unset($this->request_time); + unset($this->response); + + unset($this->content); + unset($this->content_is_file); + unset($this->buffer_enabled); + + unset($this->language_class); + } + + /** + * Get the HTTP request array. + * + * Load the entire HTTP request array or a specific request field. + * + * @param int|null $header_name + * (optional) The numeric id of the request or NULL to load all requests. + * + * @return c_base_return_array|c_base_return_status + * The HTTP request array or an array containing the request field information. + * FALSE with error bit set is returned on error. + */ + public function get_request($header_name = NULL) { + if (is_null($header_name)) { + return c_base_return_array::s_new($this->request); + } + + if (!is_int($header_name) || !array_key_exists($header_name, $this->request)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->request[$header_name]); + } + + /** + * Get the HTTP response array. + * + * Load the entire HTTP response array. + * + * @return c_base_return_array + * The HTTP response array. + */ + public function get_response() { + return c_base_return_array::s_new($this->response); + } + + /** + * Assign the class name as the language class string. + * + * @param string $class_name + * A string name representing an object that is a sub-class of i_base_language. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_language_class($class_name) { + if (!is_string($class_name) || !is_subclass_of('i_base_language', $class_name) ) { + return c_base_return_error::s_false(); + } + + $this->language_class = $class_name; + return new c_base_return_true(); + } + + /** + * Get the language class string currently assigned to this class. + * + * @return c_base_return_string + * The language class string. + */ + public function get_language_class() { + return c_return_status_string::s_new($this->language_class); + } + + /** + * Get the HTTP request timestamp.. + * + * @return c_base_return_float|c_base_return_status + * The HTTP request time. + * FALSE without error bit is returned when the request timestamp has not yet been loaded + */ + public function get_request_time() { + if (is_null($this->request_time)) { + return new c_base_return_false(); + } + + return c_base_return_float::s_new($this->request_time); + } + + /** + * Load, process, and interpret all of the supported http request headers. + */ + public function do_load_request() { + if (!is_array($this->headers)) { + $this->p_get_all_headers(); + } + + // force the request array to be defined. + $this->request = array(); + + $initialize_keys = array( + self::REQUEST_ACCEPT, + self::REQUEST_ACCEPT_CHARSET, + self::REQUEST_ACCEPT_ENCODING, + self::REQUEST_ACCEPT_LANGUAGE, + self::REQUEST_ACCEPT_DATETIME, + self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD, + self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS, + self::REQUEST_AUTHORIZATION, + self::REQUEST_CACHE_CONTROL, + self::REQUEST_CONNECTION, + self::REQUEST_COOKIE, + self::REQUEST_CONTENT_LENGTH, + self::REQUEST_CONTENT_TYPE, + self::REQUEST_DATE, + self::REQUEST_EXPECT, + self::REQUEST_FROM, + self::REQUEST_HOST, + self::REQUEST_IF_MATCH, + self::REQUEST_IF_MODIFIED_SINCE, + self::REQUEST_IF_NONE_MATCH, + self::REQUEST_IF_RANGE, + self::REQUEST_IF_UNMODIFIED_SINCE, + self::REQUEST_MAX_FORWARDS, + self::REQUEST_ORIGIN, + self::REQUEST_PRAGMA, + self::REQUEST_PROXY_AUTHORIZATION, + self::REQUEST_RANGE, + self::REQUEST_REFERER, + self::REQUEST_TE, + self::REQUEST_USER_AGENT, + self::REQUEST_UPGRADE, + self::REQUEST_VIA, + self::REQUEST_WARNING, + self::REQUEST_X_REQUESTED_WITH, + self::REQUEST_X_FORWARDED_FOR, + self::REQUEST_X_FORWARDED_HOST, + self::REQUEST_X_FORWARDED_PROTO, + self::REQUEST_CHECKSUM_HEADER, + self::REQUEST_CHECKSUM_HEADERS, + self::REQUEST_CHECKSUM_CONTENT, + ); + + foreach ($initialize_keys as $initialize_key) { + $this->request[$initialize_key] = array( + 'defined' => FALSE, + 'invalid' => FALSE, + 'data' => array( + ), + ); + } + unset($initialize_key); + unset($initialize_keys); + + // build an array of headers so that unknown/unsupported headers can still be processed. + $headers = array_flip(array_keys($this->headers)); + + // additional keys for specific cases. + $this->request[self::REQUEST_ACCEPT]['types'] = array(); + + + if (array_key_exists('accept', $this->headers)) { + $this->p_load_request_accept(); + unset($headers['accept']); + } + + if (array_key_exists('accept_language', $this->headers)) { + $this->p_load_request_accept_language(); + unset($headers['accept_language']); + } + + if (array_key_exists('accept_encoding', $this->headers)) { + $this->p_load_request_accept_encoding(); + unset($headers['accept_encoding']); + } + + if (array_key_exists('accept_charset', $this->headers)) { + $this->p_load_request_accept_charset(); + unset($headers['accept_charset']); + } + + if (array_key_exists('accept_datetime', $this->headers)) { + $this->p_load_request_accept_datetime(); + unset($headers['accept_datetime']); + } + + // @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + if (array_key_exists('access_control_request_method', $this->headers)) { + $this->p_load_request_rawish('access_control_request_method', $this->REQUEST_ACCESS_CONTROL_REQUEST_METHOD, 256); + unset($headers['access_control_request_method']); + } + + // @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + if (array_key_exists('access_control_request_headers', $this->headers)) { + $this->p_load_request_rawish('access_request_allow_headers', $this->REQUEST_ACCESS_CONTROL_REQUEST_HEADERS, 256); + unset($headers['access_control_request_headers']); + } + + // @see: https://tools.ietf.org/html/rfc7235#section-4.2 + if (array_key_exists('authorization', $this->headers)) { + $this->p_load_request_rawish('authorization', $this->REQUEST_AUTHORIZATION, 4096); + unset($headers['authorization']); + } + + if (array_key_exists('cache_control', $this->headers)) { + $this->p_load_request_cache_control(); + unset($headers['cache_control']); + } + + if (array_key_exists('connection', $this->headers)) { + $this->p_load_request_connection(); + unset($headers['connection']); + } + + if (array_key_exists('pragma', $this->headers)) { + $this->p_load_request_pragma(); + unset($headers['pragma']); + } + + if (is_array($_COOKIE) && !empty($_COOKIE)) { + $this->p_load_request_cookies(); + unset($headers['cookie']); + } + + if (array_key_exists('content_length', $this->headers)) { + $this->p_load_request_content_length(); + unset($headers['content_length']); + } + + if (array_key_exists('content_md5', $this->headers)) { + $this->p_load_request_content_md5(); + unset($headers['content_md5']); + } + + if (array_key_exists('content_checksum', $this->headers)) { + $this->p_load_request_content_checksum(); + unset($headers['content_checksum']); + } + + if (array_key_exists('content_type', $this->headers)) { + $this->p_load_request_content_type(); + unset($headers['content_type']); + } + + if (array_key_exists('date', $this->headers)) { + $this->p_load_request_date(); + unset($headers['date']); + } + + if (array_key_exists('expect', $this->headers)) { + $this->p_load_request_expect(); + unset($headers['expect']); + } + + if (array_key_exists('from', $this->headers)) { + $this->p_load_request_from(); + unset($headers['from']); + } + + if (array_key_exists('host', $this->headers)) { + $this->p_load_request_host(); + unset($headers['host']); + } + + if (array_key_exists('if_match', $this->headers)) { + $this->p_load_request_if_match(); + unset($headers['if_match']); + } + + if (array_key_exists('if_none_match', $this->headers)) { + $this->p_load_request_if_none_match(); + unset($headers['if_none_match']); + } + + if (array_key_exists('if_modified_since', $this->headers)) { + $this->p_load_request_if_modified_since(); + unset($headers['if_modified_since']); + } + + if (array_key_exists('if_unmodified_since', $this->headers)) { + $this->p_load_request_if_unmodified_since(); + unset($headers['if_unmodified_since']); + } + + if (array_key_exists('if_range', $this->headers)) { + $this->p_load_request_if_range(); + unset($headers['if_range']); + } + + if (array_key_exists('range', $this->headers)) { + $this->p_load_request_range(); + unset($headers['range']); + } + + if (array_key_exists('max_forwards', $this->headers)) { + $this->p_load_request_max_forwards(); + unset($headers['max_forwards']); + } + + if (array_key_exists('origin', $this->headers)) { + $this->p_load_request_origin(); + unset($headers['origin']); + } + + // @see: https://tools.ietf.org/html/rfc7235#section-4.4 + if (array_key_exists('proxy_authorization', $this->headers)) { + $this->p_load_request_rawish('proxy_authorization', $this->REQUEST_PROXY_AUTHORIZATION, 4096); + unset($headers['proxy_authorization']); + } + + if (array_key_exists('referer', $this->headers)) { + $this->p_load_request_referer(); + unset($headers['referer']); + } + + if (array_key_exists('te', $this->headers)) { + $this->p_load_request_te(); + unset($headers['te']); + } + + if (array_key_exists('user_agent', $this->headers)) { + $this->p_load_request_user_agent(); + unset($headers['user_agent']); + } + + if (array_key_exists('upgrade', $this->headers)) { + $this->p_load_request_upgrade(); + unset($headers['upgrade']); + } + + if (array_key_exists('via', $this->headers)) { + $this->p_load_request_via(); + unset($headers['via']); + } + + if (array_key_exists('warning', $this->headers)) { + $this->p_load_request_warning(); + unset($headers['warning']); + } + + if (array_key_exists('x_requested_with', $this->headers)) { + $this->p_load_request_rawish('x_requested_with', $this->REQUEST_X_REQUESTED_WITH, 64); + unset($headers['x_requested_with']); + } + + if (array_key_exists('x_forwarded_for', $this->headers)) { + $this->p_load_request_rawish('x_forwarded_for', $this->REQUEST_X_FORWARDED_for, 512); + unset($headers['x_forwarded_for']); + } + + if (array_key_exists('x_forwarded_host', $this->headers)) { + $this->p_load_request_rawish('x_forwarded_host', $this->REQUEST_X_FORWARDED_HOST, 512); + unset($headers['x_forwarded_host']); + } + + if (array_key_exists('x_forwarded_proto', $this->headers)) { + $this->p_load_request_rawish('x_forwarded_proto', $this->REQUEST_X_FORWARDED_PROTO, 64); + unset($headers['x_forwarded_proto']); + } + + if (array_key_exists('checksum_header', $this->headers)) { + $this->p_load_request_checksum_header(); + unset($headers['checksum_header']); + } + + if (array_key_exists('checksum_headers', $this->headers)) { + $this->p_load_request_checksum_headers(); + unset($headers['checksum_headers']); + } + + if (array_key_exists('checksum_content', $this->headers)) { + $this->p_load_request_checksum_content(); + unset($headers['checksum_content']); + } + + + // process all unknown headers and store them in the unknown key, with a max size of 256. + if (!empty($headers)) { + foreach ($headers as $header_name => $header_value) { + $this->p_load_request_unknown($header_name, self::REQUEST_UNKNOWN, 256); + } + unset($header_name); + unset($header_value); + } + unset($headers); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-allow-origin. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @param string $uri + * The uri to assign to the specified header. + * The wildcard character '*' is also allowed. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_allow_origin($uri) { + if (!is_string($uri)) { + return c_base_return_error::s_false(); + } + + if ($uri == c_base_ascii::ASTERISK) { + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = array('wildcard' => TRUE); + } + else { + $parsed = $this->p_parse_uri($uri); + if ($parsed['invalid']) { + unset($parsed); + return c_base_return_error::s_false(); + } + unset($parsed['invalid']); + + $parsed['wildcard'] = FALSE; + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN] = $parsed; + unset($parsed); + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-allow-credentials. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @param bool $allow_credentials + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_allow_credentials($allow_credentials) { + if (!is_bool($allow_credentials)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS] = $allow_credentials; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-expose-headers. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * @todo: should this be written in the same way as set_response_allow()? + * + * @param string $header_name + * The header name to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_expose_headers($header_name, $append = TRUE) { + if (!is_string($header_name)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($header_name); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS][$parsed] = $parsed; + } + else { + $this->response[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS] = array($parsed => $parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-max-age. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @param int|float $timestamp + * The timestamp to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_max_age($timestamp) { + if (!is_int($timestamp) && !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_ACCESS_CONTROL_MAX_AGE] = $timestamp; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-allow-methods. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @param int $method + * The code representing the method to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_allow_methods($method, $append = TRUE) { + if (!is_int($uri)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS][$method] = $method; + } + else { + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS] = array($method => $method); + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: access-control-allow-headers. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @param string $header_name + * The header name to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function set_response_access_control_allow_headers($header_name, $append = TRUE) { + if (!is_string($header_name)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($header_name); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS][$parsed] = $parsed; + } + else { + $this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS] = array($parsed => $parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: accept-patch. + * + * @param string $media_type + * The media type to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: self::pr_rfc_string_is_media_type() + * @see: https://tools.ietf.org/html/rfc5789#section-3.1 + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + public function set_response_accept_patch($media_type, $append = TRUE) { + if (!is_string($media_type)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + $text = $this->pr_rfc_string_prepare($media_type); + if ($text['invalid']) { + unset($text); + + return c_base_return_error::s_false(); + } + + $parsed = $this->pr_rfc_string_is_media_type($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + unset($parsed); + + return c_base_return_error::s_false(); + } + unset($parsed['invalid']); + unset($parsed['current']); + + if ($append) { + $this->response[self::RESPONSE_ACCEPT_PATCH][] = $parsed; + } + else { + $this->response[self::RESPONSE_ACCEPT_PATCH] = array($parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: accept_ranges. + * + * @param string $ranges + * An string representing ranges to assign to the specified header with the following structure: + * - 1*(tchar) + * + * Common ranges are: + * - bytes + * - none + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7233#section-2.3 + * @see: https://tools.ietf.org/html/rfc7233#section-3.1 + */ + public function set_response_accept_ranges($ranges) { + if (!is_string($ranges)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($ranges); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_ACCEPT_RANGES] = $parsed; + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: age. + * + * @param int $seconds + * The number of seconds to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.1 + */ + public function set_response_age($seconds) { + if (!is_int($seconds)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_AGE] = $seconds; + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: allow. + * + * When HTTP_METHOD_NONE is passed, this forces all existing values to be removed ($append will be ignored). + * + * @param int $allow + * A code representing the specific allow method. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.1 + */ + public function set_response_allow($allow, $append = TRUE) { + if (!is_int($allow)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + switch ($allow) { + case self::HTTP_METHOD_NONE: + case self::HTTP_METHOD_GET: + case self::HTTP_METHOD_HEAD: + case self::HTTP_METHOD_POST: + case self::HTTP_METHOD_PUT: + case self::HTTP_METHOD_DELETE: + case self::HTTP_METHOD_TRACE: + case self::HTTP_METHOD_OPTIONS: + case self::HTTP_METHOD_CONNECT: + case self::HTTP_METHOD_PATCH: + case self::HTTP_METHOD_TRACK: + case self::HTTP_METHOD_DEBUG: + break; + default: + return c_base_return_error::s_false(); + } + + if ($allow == self::HTTP_METHOD_NONE) { + $this->response[self::RESPONSE_ALLOW] = array($allow => $allow); + return new c_base_return_true(); + } + + if ($append) { + $this->response[self::RESPONSE_ALLOW][$allow] = $allow; + } + else { + $this->response[self::RESPONSE_ALLOW] = array($allow => $allow); + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: cache-control. + * + * According to the standard, the cache-control directive has the following structure: + * - 1*(tchar) *("=" (1*(tchar) / quoted-string)) + * + * It then later describes the use of multiple-cache control directives using commas to separate. + * This is very misleading and so the standards own definition of "cache-directive" is inconsistent with itself. + * + * Based on what I have seen in practice, the cache-control directive should instead be treated as: + * 1*(tchar) *("=" 1*(1*(tchar) / quoted-string) *(*(wsp) "," *(wsp) 1*(tchar) *("=" 1*(1*(tchar) / quoted-string)) + * + * @param int $directive_name + * May be an integer id code representing the name or a literal string name of the directive. + * @param string $directive_value + * (optional) The value of the directive, if one exists, which has the following structure: + * - 1*(1*(tchar) / quoted-string) + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.2 + * @see: https://tools.ietf.org/html/rfc7234#section-5.2.3 + */ + public function set_response_cache_control($directive_name, $directive_value = NULL, $append = TRUE) { + if (!is_int($directive_name)) { + return c_base_return_error::s_false(); + } + + if (!is_null($directive_value) && !is_string($directive_value)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + switch($directive_name) { + case self::CACHE_CONTROL_NO_CACHE: + case self::CACHE_CONTROL_NO_STORE: + case self::CACHE_CONTROL_NO_TRANSFORM: + case self::CACHE_CONTROL_MAX_AGE: + case self::CACHE_CONTROL_MAX_AGE_S: + case self::CACHE_CONTROL_MAX_STALE: + case self::CACHE_CONTROL_MIN_FRESH: + case self::CACHE_CONTROL_ONLY_IF_CACHED: + case self::CACHE_CONTROL_PUBLIC: + case self::CACHE_CONTROL_PRIVATE: + case self::CACHE_CONTROL_MUST_REVALIDATE: + case self::CACHE_CONTROL_PROXY_REVALIDATE: + break; + + default: + return c_base_return_error::s_false(); + } + + $parsed_directive_value = NULL; + if (!is_null($directive_value)) { + $text = $this->pr_rfc_string_prepare($directive_value); + if ($text['invalid']) { + unset($text); + + return c_base_return_error::s_false(); + } + + $parsed = $this->pr_rfc_string_is_token_quoted($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + unset($parsed); + + return c_base_return_error::s_false(); + } + unset($parsed); + } + + if ($append) { + $this->response[self::RESPONSE_CACHE_CONTROL][$directive_name] = $directive_value; + } + else { + $this->response[self::RESPONSE_CACHE_CONTROL] = array($directive_name => $directive_value); + } + + unset($parsed_directive_value); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: connection. + * + * @param string $header_name + * The header name to assign as a connection-specific field. + * These header fields apply only to the immediate client. + * The header name format is: + * - 1*(tchar) + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7230#section-6.1 + */ + public function set_response_connection($header_name, $append = TRUE) { + if (!is_string($header_name)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($header_name); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_CONNECTION][$parsed] = $parsed; + } + else { + $this->response[self::RESPONSE_CONNECTION] = array($parsed => $parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response content (the data/body). + * + * The term 'content' and 'body' seem to be used interchangably. + * The header fields often refer to this as 'content', therefore this will be called 'content' and neither 'body' nor 'data'. + * + * This can either be a representation of one or more files (by their filename) or a string (binary or otherwise). + * - Appending a file after a string is assigned or vice-versa will result in the previous data being deleted. + * + * @param string $content + * The content to assign. + * This could be either a string or binary a binary string. + * If $is_file is TRUE, then this is the local filename to the content. + * @param bool $append + * (optional) If TRUE, then append the content or filename. + * If FALSE, then assign the content or filename. + * @param bool $is_file + * (optional) If TRUE, then $content is treated as a file name. + * If FALSE, then is_file will be assigned to FALSE. + * If $append is TRUE, then this argument is ignored. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_content($content, $append = TRUE, $is_file = FALSE) { + if (!is_string($content)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($is_file)) { + return c_base_return_error::s_false(); + } + + if ($append) { + if ($this->content_is_file) { + if (is_array($this->content)) { + $this->content[] = $content; + } + else { + $this->content = array($content); + } + + + } + else { + if (is_array($this->content)) { + $this->content = $content; + } + else { + $this->content .= $content; + } + } + } + else { + unset($this->content); + + if ($is_file) { + $this->content_is_file = TRUE; + $this->content = array($content); + } + else { + $this->content_is_file = FALSE; + $this->content = $content; + } + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content-disposition. + * + * The standard defines this as: + * - 1*(tchar) *(";" 1*(token) "=" 1*(1*(tchar) / 1*(quoted_string))) + * + * @param ?? $disposition + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc6266#section-4 + */ + public function set_response_content_disposition($disposition) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: content-encoding. + * + * The standard is horrible at describing the differences between content-encoding and transfer-encoding. + * Here is my understanding of the RFCs: + * + * Content Encoding: + * - Defines how the content is encoding as a complete entire content structure. + * - This is directly related to the mime-type, such that if there was a tarball like: example.html.gz, then content-encoding could be set to gzip to represent the mimetype for example.html (which might be: text/html). + * + * Transfer Encoding: + * - Defines the encoding applied to the content for the purpos of transmitting. + * - This is completely unrelated to the mime-type such that the example.html.gz could still be gzipped (such as using a different compression level) but it would represent example.html.gz and not example.html.gz.gz. + * + * This becomes more convoluted because the standard for content encoding states that it does not refer to encodings "inherent" to the content type. + * - If that is the case, then the file example.html.gz would have no content-encoding because the file being transmitted is example.html.gz. + * + * The standard uses "Content-Encoding: gzip" as an example, even though gzip is used as a transfer-encoding. + * - This means that the encoding is not about the content, but instead of how the content is being transferred (which sounds an awful lot like the words "transfer" and "encoding"). + * + * That said, many sites and services out there appear to use (and document) that content-encoding is used for compressing data on transfer. + * - This directly conflicts with the standards requirements as the compression is done for the transfer. + * + * Now, with the transfer-encoding header, there is a major issue in which the standard states (under 3.3.2. Content-Length): + * - "A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header field." + * - This ruins and breaks some of the functionality and benefits (including security implications) provided by Content-Length. + * - This also contradicts its purpose of representing the message and the content. + * - Given that the content-length represents the content and not the transport of the data, requiring content-length to be removed is outright idiotic. + * + * Chunked transfer was originally designed with the idea that the size of the content is unknown. + * - In practice, many servers always send chunked data for performance reasons (such as progressive load jpeg). + * - In many of these cases where chunked is used, the content size is known. + * + * PHP (or possibly the web server) appears to almost always send chunked data. + * - This effectively forces a requirement of transfer-encoding: chunked, but never sets that and browsers still accept chunked transfer without the transfer encoding specifying it. + * - Clients still support receiving data in chunks despite a complete lack of the transfer-encoding header (by which the standard states an error must be thrown). + * + * Content-Length is used to tell clients how large the content is. + * - This has performance, integrity, and even security implications and is generally a good idea. + * + * Multiple content-encodings should also be avoided because multiple values in content-length does not relate to the multiple content-encodings. + * - The content-length is supposed to represent the content and not the transmittion of the content and not allowing a content-length to match every content-encoding is yet another contradiction. + * - Effectively, content-length, when used with multiple values is treated as a transfer-encoding length (which is the contradiction!): + * - "If a message is received that has multiple Content-Length header fields with field-values consisting of the same decimal value, or a single Content-Length header field with a field value containing a list of identical decimal values (e.g., "Content-Length: 42, 42"), indicating that duplicate Content-Length header fields have been generated or combined by an upstream message processor, then the recipient MUST either reject the message as invalid or replace the duplicated field-values with a single valid Content-Length field containing that decimal value prior to determining the message body length or forwarding the message.". + * - This leaves content-length with three possible (contradictory) interpretations: + * 1) content-length represents the original content (doing this causes invalid message-length errors on web browsers). + * 2) content-length represents the length of only the first content encoding (doing this probably causes errors on the web browsers). + * 3) content-length represents the length of the last encoding (there is no way to get or confirm the length based on the headers for each individual encoding). + * - The problem is, using #3 (which is the only one that seems to work), content-length must be interepreted as a transfer-encoding content-length (again, there is that annoying contradiction). + * + * In summary, I recommend the following: + * - Never use transfer-encoding, it contradicts itself and content-encoding. + * - Avoid multiple content-encodings. + * - The standard is poorly written and many clients do not follow the standard (probably due to its poor design). + * + * @todo: this should be an array of values in the order in which the encoding is applied. + * + * @param int $encoding + * The encoding to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: self::set_response_transfer_encoding() + * @see: self::set_response_content_length() + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.2.2 + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.1 + */ + public function set_response_content_encoding($encoding, $append = TRUE) { + if (!is_int($encoding)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + switch ($encoding) { + case self::ENCODING_CHUNKED: + case self::ENCODING_COMPRESS: + case self::ENCODING_DEFLATE: + case self::ENCODING_GZIP: + case self::ENCODING_BZIP: + case self::ENCODING_LZO: + case self::ENCODING_XZ: + case self::ENCODING_EXI: + case self::ENCODING_IDENTITY: + case self::ENCODING_SDCH: + case self::ENCODING_PG: + break; + default: + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CONTENT_ENCODING] = $encoding; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content-language. + * + * @param int|null $language + * The language code to assign to the specified header. + * If NULL, then the default language according the the given language class is used. + * If NULL and the default language is not set, then FALSE with error bit set is returned. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.3.2 + */ + public function set_response_content_language($language = NULL) { + if (!is_null($language) && !is_int($language)) { + return c_base_return_error::s_false(); + } + + if (is_null($language)) { + if (!is_object($this->language_class) || !($this->language_class instanceof i_base_language)) { + return c_base_return_error::s_false(); + } + + $default = $this->language_class->s_get_default_id(); + if ($default instanceof c_base_return_false) { + unset($default); + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CONTENT_LANGUAGE] = $default; + unset($default); + } + else { + if ($language_class->s_get_names_by_id($language) instanceof c_base_return_false) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CONTENT_LANGUAGE] = $language; + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content-length. + * + * Read the function comments for self::set_response_transfer_encoding(). + * + * @param int|null $length + * (optional) The content-length, representing the total number of octals (8-bits). + * Set to NULL for auto-calculation from already assigned data. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE without error bit set is returned when the Transfer-Encoding header field is assigned. + * FALSE with error bit set is returned on error. + * + * @see: self::set_response_content_encoding() + * @see: self::set_response_transfer_encoding() + * @see: self::p_calculate_content_length() + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.2 + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.3 + */ + public function set_response_content_length($length = NULL) { + if (!is_null($length) && !is_int($length) || $length < 0) { + return c_base_return_error::s_false(); + } + + // From the RFC: "A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header field." + if (array_key_exists(self::RESPONSE_TRANSFER_ENCODING, $this->response)) { + return new c_base_return_false(); + } + + if (is_null($length)) { + if (is_null($this->content)) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = 0; + } + else { + if ($this->content_is_file) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = 0; + + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($filename); + // @todo: provide a file not found error. + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CONTENT_LENGTH] += filesize($filename); + } + } + else { + $this->response[self::RESPONSE_CONTENT_LENGTH] = $this->p_calculate_content_length($this->content); + } + } + } + else { + $this->response[self::RESPONSE_CONTENT_LENGTH] = $length; + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content_range. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_content_range($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: content_type. + * + * @todo: implement a thorough sanity check, this currently uses a simple check. + * + * @param string $content_type + * The content type to assign to the specified header. + * @param int $charset + * (optional) The character set to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: self::set_response_encoding() + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.1.5 + */ + public function set_response_content_type($content_type, $charset = c_base_charset::UTF_8) { + if (!is_string($content_type)) { + return c_base_return_error::s_false(); + } + + if (!c_base_charset::s_is_valid($charset)) { + return c_base_return_error::s_false(); + } + + // perform a very basic syntax check. + if (strpos($content_type, ';')) { + return c_base_return_error::s_false(); + } + + $content_type_part = mb_split('/', $content_type); + + if (count($content_type_part) != 2) { + unset($content_type_part); + return c_base_return_error::s_false(); + } + unset($content_type_part); + + $this->response[self::RESPONSE_CONTENT_TYPE] = array( + 'type' => mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $content_type)), + 'charset' => $charset, + ); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: date. + * + * @param int|float|null $timestamp + * (optional) The timestamp to assign to the specified header. + * When NULL, the $request_time timestamp defined by this class is used. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + */ + public function set_response_date($timestamp = NULL) { + if (is_null($timestamp)) { + if (is_null($this->request_time)) { + $this->request_time = microtime(TRUE); + } + + $this->response[self::RESPONSE_DATE_ACTUAL] = $this->request_time; + return new c_base_return_true(); + } + + if (!is_int($timestamp) && !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_DATE] = $timestamp; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: date_actual. + * + * This is identical to the HTTP response header: date. + * The purpose of this is to allow clients to still receive the correct/actual date when HTTP servers, such as apache, overwrite or alter the HTTP date response header. + * This should therefore be used and calculated with when the date variable. + * + * @param int|float|null $timestamp + * (optional) The timestamp to assign to the specified header. + * When NULL, the $request_time timestamp defined by this class is used. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + */ + public function set_response_date_actual($timestamp = NULL) { + if (is_null($timestamp)) { + if (is_null($this->request_time)) { + $this->request_time = microtime(TRUE); + } + + $this->response[self::RESPONSE_DATE_ACTUAL] = $this->request_time; + return new c_base_return_true(); + } + + if (!is_int($timestamp) && !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_DATE_ACTUAL] = $timestamp; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: etag. + * + * The auto-generated e-tag will be a checksum. + * - The checksum will be against the assigned content and should be called before any content encoding is performed. + * + * @param string|null|bool $entity_tag + * (optional) The entity tag to assign to the specified header. + * If NULL, then the entity tag will be auto-calculated from the content using a full sha256 checksum. + * When NULL, the content must be fully defined or the checksum will be invalid. + * If FALSE, the entity tag will not be changed, only the weak setting will be changed. + * TRUE is not used and is considered invalid. + * @param bool $weak + * (optional) Set to TRUE to enable a weak entity-tag. + * When $entity_tag is NULL, the first 9 characters of the generated entity-tag will be used for the 'weak' entity-tag. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7232#section-2.3 + */ + public function set_response_etag($entity_tag = NULL, $weak = FALSE) { + if (!is_null($entity_tag) && !is_string($entity_tag) && $entity_tag !== FALSE) { + return c_base_return_error::s_false(); + } + + $response = array( + 'tag' => '', + 'weak' => FALSE, + ); + + if (is_null($entity_tag)) { + + if ($weak) { + $response['tag'] = 'partial:sha256:'; + } + else { + $response['tag'] = 'full:sha256:'; + } + + if ($this->content_is_file) { + // @todo: determine how to handle multiple files and the checksum! + // this might be inefficient in that the files will likely need to be buffered and then have the string checksumed. + // look into: hash_update_file() + if (!is_array($this->content)) { + unset($response); + + // @todo: report warning about no content file specified. + return new c_base_return_false(); + } + + $hash = hash_init('sha256'); + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($filename); + unset($hash); + unset($response); + + // @todo: report file not found or other related errors. + return c_base_return_error::s_false(); + } + + $success = hash_update_file($hash, $filename); + if (!$success) { + unset($success); + unset($filename); + unset($hash); + unset($response); + + // @todo: report failure to hash file. + return c_base_return_error::s_false(); + } + } + unset($filename); + unset($success); + + $response['tag'] = hash_final($hash, FALSE); + unset($hash); + + if ($weak) { + // Keep the first 15 characters for 'partial:sha256:' plus the first 9 characters of the checksum. + $response['tag'] = substr($response['tag'], 0, 24); + } + } + else { + if (!is_string($this->content)) { + unset($response); + + // @todo: report warning about no content specified. + return new c_base_return_false(); + } + + $response['tag'] = hash('sha256', $this->content, FALSE); + } + } + elseif ($entity_tag !== FALSE) { + $response['tag'] = $entity_tag; + } + + $response['weak'] = $weak; + + $this->response[self::RESPONSE_ETAG] = $response; + unset($response); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: expires. + * + * From the RFC: "The value in Expires is only intended for recipients that have not yet implemented the Cache-Control field.". + * + * @param int|float $timestamp + * The unix timestamp to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.3 + */ + public function set_response_expires($timestamp) { + if (!is_int($timestamp) && !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_EXPIRES] = $timestamp; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: last-modified. + * + * @param int|float $timestamp + * The Unix timestamp to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7232#section-2.2 + */ + public function set_response_last_modified($timestamp) { + if (!is_int($timestamp) && !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_LAST_MODIFIED] = $timestamp; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: link. + * + * Use self::SCHEME_LOCAL for a local filesystem link. + * + * @param string $uri + * The URI to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc5988#section-5 + * @see: https://tools.ietf.org/html/rfc3986 + */ + public function set_response_link($uri) { + if (!is_string($uri)) { + return c_base_return_error::s_false(); + } + + #$parsed = $this->p_parse_uri($uri); + + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: location. + * + * Use self::SCHEME_LOCAL for a local filesystem link. + * + * @param string $uri + * The URI to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc3986 + */ + public function set_response_location($uri) { + if (!is_string($uri)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_parse_uri($uri); + if ($parsed['invalid']) { + unset($parsed); + return c_base_return_error::s_false(); + } + + unset($parsed['invalid']); + + $this->response[self::RESPONSE_LOCATION] = $parsed; + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: pragma. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.4 + */ + public function set_response_pragma($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: proxy-authenticate. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.3 + */ + public function set_response_proxy_authenticate($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: public-key-pins. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7469 + */ + public function set_response_public_key_pins($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: refresh. + * + * This is not defined in any RFC but is supported by many browser. + * It is recommended to never use this. + * Instead, try using the HTTP header: location. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: set_response_location() + * @see: https://en.wikipedia.org/wiki/Meta_refresh + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + public function set_response_refresh($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: retry-after. + * + * @param int|float $date + * When $seconds is FALSE, a unix timestamp representing the retry date. + * When $seconds is TRUE, the number of seconds to retry after. + * + * @param bool $seconds + * (optional) Used to change the interpretation of the $data parameter. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.3 + */ + public function set_response_retry_after($date, $seconds = FALSE) { + if (!is_int($date) || !is_float($timestamp)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($seconds)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_RETRY_AFTER] = array( + 'value' => $date, + 'is_seconds' => $seconds, + ); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: server. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.2 + */ + public function set_response_server($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: set_cookie. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_set_cookie($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: status. + * + * @param int $code + * The status code to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7232#section-4 + */ + public function set_response_status($code) { + if (!is_int($code)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_STATUS] = $code; + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: strict-transport-security. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_strict_transport_security($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: trailer. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_trailer($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: transfer-encoding. + * + * Read the function comments for self::set_response_transfer_encoding(). + * + * @fixme: this should be an array of options that are essentially identical to content-encoding. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE without error bit set is returned when the Content-Length header field is assigned. + * FALSE with error bit set is returned on error. + * + * @see: self::set_response_content_encoding() + * @see: self::set_response_content_length() + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.2 + */ + public function set_response_transfer_encoding($value) { + // From the RFC: "A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header field." + if (array_key_exists(self::RESPONSE_CONTENT_LENGTH, $this->response)) { + unset($this->response[self::RESPONSE_CONTENT_LENGTH]); + } + + // RESPONSE_TRANSFER_ENCODING + + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: upgrade. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_upgrade($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: vary. + * + * @param string $header_name + * The name of a header field to assign to the specified header. + * @param bool $append + * (optional) Set to TRUE to append values instead of assigning. + * Set to FALSE to assign a new value. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.4 + */ + public function set_response_vary($header_name, $append = TRUE) { + if (!is_string($header_name)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($header_name); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_VARY][$parsed] = $parsed; + } + else { + $this->response[self::RESPONSE_VARY] = array($parsed => $parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: warning. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.5 + */ + public function set_response_warning($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: www_authenticate. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.1 + */ + public function set_response_www_authenticate($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: HTTP Protocol. + * + * @param string $protocol + * A string representing the HTTP protocol, such as: "HTTP 1.1". + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_protocol($protocol) { + if (!is_string($protocol)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_PROTOCOL] = $protocol; + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content-security-policy. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_content_security_policy($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: x-content-type-options. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_x_content_type_options($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: x-ua-compatible. + * + * @param ?? $value + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_x_ua_compatible($value) { + // @todo + return new c_base_return_false(); + } + + /** + * Assign HTTP response header: checksum_header. + * + * @param int $action + * (optional) Define how the checksum is to be processed. + * Can only be one of: + * - self::CHECKSUM_ACTION_NONE + * - self::CHECKSUM_ACTION_AUTO + * - self::CHECKSUM_ACTION_MANUAL + * @param int|null $what + * (optional) An integer representing the checksum what, can be one of: + * - self::CHECKSUM_WHAT_FULL + * - self::CHECKSUM_WHAT_PARTIAL + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * @param int|null $type + * (optional) An integer representing the checksum algorithm type. + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * @param string|null $checksum + * (optional) A checksum that represents the content. + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_checksum_header($action = self::CHECKSUM_ACTION_AUTO, $what = NULL, $type = NULL, $checksum = NULL) { + if (!is_int($action)) { + return c_base_return_error::s_false(); + } + + if ($action != self::CHECKSUM_ACTION_NONE && $action != self::CHECKSUM_ACTION_AUTO && $action != self::CHECKSUM_ACTION_MANUAL) { + return c_base_return_error::s_false(); + } + + if ($action == self::CHECKSUM_ACTION_MANUAL) { + if (!is_int($what) || !is_int($type) || !is_string($checksum)) { + return c_base_return_error::s_false(); + } + + if ($what != self::CHECKSUM_WHAT_PARTIAL && $what != self::CHECKSUM_WHAT_FULL) { + return c_base_return_error::s_false(); + } + + switch ($type) { + case CHECKSUM_MD2: + case CHECKSUM_MD4: + case CHECKSUM_MD5: + case CHECKSUM_SHA1: + case CHECKSUM_SHA224: + case CHECKSUM_SHA256: + case CHECKSUM_SHA384: + case CHECKSUM_SHA512: + case CHECKSUM_CRC32: + case CHECKSUM_PG: + break; + default: + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CHECKSUM_HEADER] = array( + 'checksum' => $checksum, + 'action' => $action, + 'what' => $what, + 'type' => $type, + ); + } + else { + if (!is_null($what) || !is_null($type) || !is_null($checksum)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CHECKSUM_HEADER] = array( + 'checksum' => NULL, + 'action' => $action, + 'what' => self::CHECKSUM_WHAT_FULL, + 'type' => self::CHECKSUM_SHA256, + ); + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: checksum_headers. + * + * @param string $header_name + * The header name to assign to the specified header. + * @param bool $append + * (optional) If TRUE, then append the header name. + * If FALSE, then assign the header name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_checksum_headers($header_name, $append = TRUE) { + if (!is_string($header_name)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($append)) { + return c_base_return_error::s_false(); + } + + $parsed = $this->p_prepare_token($header_name); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + if ($append) { + $this->response[self::RESPONSE_CHECKSUM_HEADERS][$parsed] = $parsed; + } + else { + $this->response[self::RESPONSE_CHECKSUM_HEADERS] = array($parsed => $parsed); + } + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: checksum_content. + * + * @param int $action + * (optional) Define how the checksum is to be processed. + * Can only be one of: + * - self::CHECKSUM_ACTION_NONE + * - self::CHECKSUM_ACTION_AUTO + * - self::CHECKSUM_ACTION_MANUAL + * When $action is self::CHECKSUM_ACTION_AUTO, the checksum will not be calculated at this point in time. + * @param int|null $what + * (optional) An integer representing the checksum what, can be one of: + * - self::CHECKSUM_WHAT_FULL + * - self::CHECKSUM_WHAT_PARTIAL + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * @param int|null $type + * (optional) An integer representing the checksum algorithm type. + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * @param string|null $checksum + * (optional) A checksum that represents the content. + * This is only processed when $action is set to CHECKSUM_ACTION_MANUAL. + * Otherwise, this must be NULL. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_checksum_content($action = self::CHECKSUM_ACTION_AUTO, $what = NULL, $type = NULL, $checksum = NULL) { + if (!is_int($action)) { + return c_base_return_error::s_false(); + } + + if ($action != self::CHECKSUM_ACTION_NONE && $action != self::CHECKSUM_ACTION_AUTO && $action != self::CHECKSUM_ACTION_MANUAL) { + return c_base_return_error::s_false(); + } + + if ($action == self::CHECKSUM_ACTION_MANUAL) { + if (!is_int($what) || !is_int($type) || !is_string($checksum)) { + return c_base_return_error::s_false(); + } + + if ($what != self::CHECKSUM_WHAT_PARTIAL && $what != self::CHECKSUM_WHAT_FULL) { + return c_base_return_error::s_false(); + } + + switch ($type) { + case CHECKSUM_MD2: + case CHECKSUM_MD4: + case CHECKSUM_MD5: + case CHECKSUM_SHA1: + case CHECKSUM_SHA224: + case CHECKSUM_SHA256: + case CHECKSUM_SHA384: + case CHECKSUM_SHA512: + case CHECKSUM_CRC32: + case CHECKSUM_PG: + break; + default: + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CHECKSUM_CONTENT] = array( + 'checksum' => $checksum, + 'action' => $action, + 'what' => $what, + 'type' => $type, + ); + } + else { + if (!is_null($what) || !is_null($type) || !is_null($checksum)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CHECKSUM_CONTENT] = array( + 'checksum' => NULL, + 'action' => $action, + 'what' => self::CHECKSUM_WHAT_FULL, + 'type' => self::CHECKSUM_SHA256, + ); + } + + return new c_base_return_true(); + } + + /** + * Assign HTTP response header: content_revision. + * + * The content-revision is used to tell clients what revision this document or file is. + * + * @param int $revision + * The value to assign to the specified header. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_response_content_revision($revision) { + if (!is_int($revision)) { + return c_base_return_error::s_false(); + } + + $this->response[self::RESPONSE_CONTENT_REVISION] = $revision; + + return new c_base_return_true(); + } + + /** + * Ensures that a given response value is not assigned. + * + * @param int $response_id + * The ID representing a given response to unassign + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function unset_response_value($response_id) { + if (!is_int($response_id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($response_id, $this->response)) { + unset($this->response[$response_id]); + } + + return new c_base_return_true(); + } + + /** + * Obtain HTTP response header: access-control-allow-origin. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_array|bool + * A decoded uri split into its different parts inside an array. + * This array also contains a key called 'wildcard' which may be either TRUE or FALSE. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function get_response_access_control_allow_origin() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN]); + } + + /** + * Obtain HTTP response header: access-control-allow-credentials. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_bool|c_base_return_status + * A boolean representing whether or not to allow credentials. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function get_response_access_control_allow_credentials() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_bool::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS]); + } + + /** + * Obtain HTTP response header: access-control-expose-headers. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_array|c_base_return_status + * An array of headers to expose. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function get_response_access_control_expose_headers() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS]); + } + + /** + * Obtain HTTP response header: access-control-max-age. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_int|c_base_return_status + * An Unix timestamp representing the specified header. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function get_response_access_control_max_age() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_MAX_AGE, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_MAX_AGE]); + } + + /** + * Obtain HTTP response header: access-control-allow-methods. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_array|bool + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + public function get_response_access_control_allow_methods() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS]); + } + + /** + * Obtain HTTP response header: access-control-allow-headers. + * + * @todo: I only glanced at the documentation (which was incomplete on wikipedia). + * More work is likely needed to be done with this and its structure is subject to change. + * + * @return c_base_return_arrayl|c_base_return_status + * An array of allowed headers is returned. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_access_control_allow_headers() { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS]); + } + + /** + * Obtain HTTP response header: accept-patch. + * + * @return c_base_return_array|c_base_return_status + * An array containing the header values. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc5789#section-3.1 + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + public function get_response_accept_patch() { + if (!array_key_exists(self::RESPONSE_ACCEPT_PATCH, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ACCEPT_PATCH]); + } + + /** + * Obtain HTTP response header: accept_ranges. + * + * @return c_base_return_string|c_base_return_status + * A string representing the header value. + * + * Common ranges are: + * - bytes + * - none + * + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7233#section-2.3 + * @see: https://tools.ietf.org/html/rfc7233#section-3.1 + */ + public function get_response_accept_ranges() { + if (!array_key_exists(self::RESPONSE_ACCEPT_RANGES, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($this->response[self::RESPONSE_ACCEPT_RANGES]); + } + + /** + * Obtain HTTP response header: age. + * + * @return c_base_return_int|c_base_return_status + * A Unix timestamp representing the header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.1 + */ + public function get_response_age() { + if (!array_key_exists(self::RESPONSE_AGE, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_AGE]); + } + + /** + * Obtain HTTP response header: allow. + * + * @return c_base_return_array|c_base_return_status + * An array of allow method codes. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.1 + */ + public function get_response_allow() { + if (!array_key_exists(self::RESPONSE_ALLOW, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ALLOW]); + } + + /** + * Obtain HTTP response header: cache-control. + * + * According to the standard, the cache-control directive has the following structure: + * - 1*(tchar) *("=" (1*(tchar) / quoted-string)) + * + * It then later describes the use of multiple-cache control directives using commas to separate. + * This is very misleading and so the standards own definition of "cache-directive" is inconsistent with itself. + * + * Based on what I have seen in practice, the cache-control directive should instead be treated as: + * 1*(tchar) *("=" 1*(1*(tchar) / quoted-string) *(*(wsp) "," *(wsp) 1*(tchar) *("=" 1*(1*(tchar) / quoted-string)) + * + * @return c_base_return_array|c_base_return_status + * An array containing the cache-control directives. + * Each array key is a name and if that directive has no value, then the related directive name will have a NULL value. + * For example, a directive of "no-cache" will have the following array structure: + * - array("no-cache" => NULL) + * For example, a directive of "private, max-age=32" will have the following array structure: + * - array("private" => NULL, "max-age" => 32) + * + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.2 + * @see: https://tools.ietf.org/html/rfc7234#section-5.2.3 + */ + public function get_response_cache_control() { + if (!array_key_exists(self::RESPONSE_CACHE_CONTROL, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CACHE_CONTROL]); + } + + /** + * Obtain HTTP response header: connection. + * + * @return c_base_return_array|bool + * An array of header names assigned to the connection header. + * The header name format is: + * - 1*(tchar) + * + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7230#section-6.1 + */ + public function get_response_connection() { + if (!array_key_exists(self::RESPONSE_CONNECTION, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CONNECTION]); + } + + /** + * Obtain HTTP response header: content-disposition. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc6266#section-4 + */ + public function get_response_content_disposition() { + if (!array_key_exists(self::RESPONSE_CONTENT_DISPOSITION, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: content-encoding. + * + * @return c_base_return_int|c_base_return_status + * An integer representing the content length value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_content_encoding() { + if (!array_key_exists(self::RESPONSE_CONTENT_ENCODING, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_CONTENT_ENCODING]); + } + + /** + * Obtain HTTP response header: content-language. + * + * @return c_base_return_int|c_base_return_status + * An integer representing the content length value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.3.2 + */ + public function get_response_content_language() { + if (!array_key_exists(self::RESPONSE_CONTENT_LANGUAGE, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_CONTENT_LANGUAGE]); + } + + /** + * Obtain HTTP response header: content-length. + * + * @return c_base_return_int|c_base_return_status + * An integer containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_content_length() { + if (!array_key_exists(self::RESPONSE_CONTENT_LENGTH, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_CONTENT_LENGTH]); + } + + /** + * Obtain HTTP response header: content_range. + * + * @todo: probably an array. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_content_range() { + if (!array_key_exists(self::RESPONSE_CONTENT_RANGE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: content_type. + * + * @return c_base_return_array|c_base_return_status + * An array containing the following keys: + * - 'type': the content type string, such as 'text/html'. + * - 'charset': the character set integer, such as: c_base_charset::UTF_8. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.1.5 + */ + public function get_response_content_type() { + if (!array_key_exists(self::RESPONSE_CONTENT_TYPE, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CONTENT_TYPE]); + } + + /** + * Obtain HTTP response header: date. + * + * @return c_base_return_int|c_base_return_float|c_base_return_status + * A unix timestamp integer. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + */ + public function get_response_date() { + if (!array_key_exists(self::RESPONSE_DATE, $this->response)) { + return c_base_return_error::s_false(); + } + + if (is_float($this->response[self::RESPONSE_DATE])) { + return c_base_return_float::s_new($this->response[self::RESPONSE_DATE]); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_DATE]); + } + + /** + * Obtain HTTP response header: date_actual. + * + * This is identical to the HTTP response header: date. + * The purpose of this is to allow clients to still receive the correct/actual date when HTTP servers, such as apache, overwrite or alter the HTTP date response header. + * This should therefore be used and calculated with when the date variable. + * + * @return c_base_return_int|c_base_return_float|c_base_return_status + * A unix timestamp integer. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + */ + public function get_response_date_actual() { + if (!array_key_exists(self::RESPONSE_DATE_ACTUAL, $this->response)) { + return c_base_return_error::s_false(); + } + + if (is_float($this->response[self::RESPONSE_DATE_ACTUAL])) { + return c_base_return_float::s_new($this->response[self::RESPONSE_DATE_ACTUAL]); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_DATE_ACTUAL]); + } + + /** + * Obtain HTTP response header: etag. + * + * @return c_base_return_array|c_base_return_status + * An array containing the following: + * - tag: The entity tag string (without weakness). + * - weak: A boolean representing whether or not the entity tag is weak. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7232#section-2.3 + */ + public function get_response_etag() { + if (!array_key_exists(self::RESPONSE_ETAG, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_ETAG]); + } + + /** + * Obtain HTTP response header: expires. + * + * @return c_base_return_int|c_base_return_float|c_base_return_status + * A unix timestamp integer. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.3 + */ + public function get_response_expires() { + if (!array_key_exists(self::RESPONSE_EXPIRES, $this->response)) { + return c_base_return_error::s_false(); + } + + if (is_float($this->response[self::RESPONSE_EXPIRES])) { + return c_base_return_float::s_new($this->response[self::RESPONSE_EXPIRES]); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_EXPIRES]); + } + + /** + * Obtain HTTP response header: last-modified. + * + * @return c_base_return_int|c_base_return_float|c_base_return_status + * A unix timestamp integer. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7232#section-2.2 + */ + public function get_response_last_modified() { + if (!array_key_exists(self::RESPONSE_LAST_MODIFIED, $this->response)) { + return c_base_return_error::s_false(); + } + + if (is_float($this->response[self::RESPONSE_LAST_MODIFIED])) { + return c_base_return_float::s_new($this->response[self::RESPONSE_LAST_MODIFIED]); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_LAST_MODIFIED]); + } + + /** + * Obtain HTTP response header: link. + * + * @todo: break this into an array of the differnt parts. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc5988#section-5 + * @see: https://tools.ietf.org/html/rfc3986 + */ + public function get_response_link() { + if (!array_key_exists(self::RESPONSE_LINK, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: location. + * + * @todo: consider changing this to an array containing the entire url parts broken into each key. + * + * @return c_base_return_array|c_base_return_status + * A decoded uri split into its different parts inside an array. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc3986 + */ + public function get_response_location() { + if (!array_key_exists(self::RESPONSE_LOCATION, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($this->response[self::RESPONSE_LOCATION]); + } + + /** + * Obtain HTTP response header: pragma. + * + * @todo: the cache specific options, probably an array. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.4 + */ + public function get_response_pragma() { + if (!array_key_exists(self::RESPONSE_PRAGMA, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: proxy-authenticate. + * + * No specific content structure is defined for this response header. + * Should a specific structure be defined in the future, then the return value is subject to change. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.3 + */ + public function get_response_proxy_authenticate() { + if (!array_key_exists(self::RESPONSE_PROXY_AUTHENTICATE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: public-key-pins. + * + * No specific content structure is defined for this response header. + * Should a specific structure be defined in the future, then the return value is subject to change. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7469 + */ + public function get_response_public_key_pins() { + if (!array_key_exists(self::RESPONSE_PUBLIC_KEY_PINS, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: refresh. + * + * This is not defined in any RFC but is supported by many browser. + * It is recommended to never use this. + * Instead, try using the HTTP header: location. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: get_response_location() + * @see: https://en.wikipedia.org/wiki/Meta_refresh + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + public function get_response_refresh() { + if (!array_key_exists(self::RESPONSE_REFRESH, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: retry-after. + * + * @return c_base_return_array|c_base_return_status + * An array containing the following: + * - value: When 'is_seconds' is FALSE, this is the unix timestamp representing when the page expires. + * When 'is_seconds' is FALSE, this is the relative number of seconds until the content expires. + * - is_seconds: A boolean that when true changes the interpretation of the 'value' key. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.3 + */ + public function get_response_retry_after() { + if (!array_key_exists(self::RESPONSE_RETRY_AFTER, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_RETRY_AFTER]); + } + + /** + * Obtain HTTP response header: server. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.2 + */ + public function get_response_server() { + if (!array_key_exists(self::RESPONSE_SERVER, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: get_cookie. + * + * @todo: cookie might needs special handling because PHP handles it in a different way from other headers. + * + * @return ??|c_base_return_status + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_cookie() { + if (!array_key_exists(self::RESPONSE_COOKIE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: status. + * + * @return c_base_return_int|c_base_return_status + * An integer representing the HTTP status code. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7232#section-4 + */ + public function get_response_status() { + if (!array_key_exists(self::RESPONSE_STATUS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_STATUS]); + } + + /** + * Obtain HTTP response header: strict-transport-security. + * + * @return c_base_return_string|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc6797#section-6.1 + */ + public function get_response_strict_transport_security() { + if (!array_key_exists(self::RESPONSE_STRICT_TRANSPORT_SECURITY, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: trailer. + * + * @todo: this appears to no longer be directly specified in the headers. + * There is a 'trailer-part' mentioned along with the transfer encoding information. + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc2616#section-14.40 + * @see: https://tools.ietf.org/html/rfc7230#section-4.1.2 + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.1 + */ + public function get_response_trailer() { + if (!array_key_exists(self::RESPONSE_TRAILER, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: transfer-encoding. + * + * @return ???|c_base_return_status + * An integer representing the encoding used to transfer this content. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.1 + */ + public function get_response_transfer_encoding() { + if (!array_key_exists(self::RESPONSE_TRANSFER_ENCODING, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: upgrade. + * + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_upgrade() { + if (!array_key_exists(self::RESPONSE_UPGRADE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: vary. + * + * @return c_base_return_array|c_base_return_status + * An array containing the response header values. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.4 + */ + public function get_response_vary() { + if (!array_key_exists(self::RESPONSE_VARY, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_VARY]); + } + + /** + * Obtain HTTP response header: warning. + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.5 + */ + public function get_response_warning() { + if (!array_key_exists(self::RESPONSE_WARNING, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: www-authenticate. + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.1 + */ + public function get_response_www_authenticate() { + if (!array_key_exists(self::RESPONSE_WWW_AUTHENTICATE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: HTTP Protocol. + * + * @return c_base_return_string|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_protocol() { + if (!array_key_exists(self::RESPONSE_PROTOCOL, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($this->response[self::RESPONSE_PROTOCOL]); + } + + /** + * Obtain HTTP response header: content-security-policy. + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_content_security_policy() { + if (!array_key_exists(self::RESPONSE_X_CONTENT_SECURITY_POLICY, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: x-content-type-options. + * + * @return ???|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_x_content_type_options() { + if (!array_key_exists(self::RESPONSE_X_CONTENT_TYPE_OPTIONS, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: x-ua-compatible. + * + * @return c_base_return_string|c_base_return_status + * A string containing the response header value. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_x_ua_compatible() { + if (!array_key_exists(self::RESPONSE_X_UA_COMPATIBLE, $this->response)) { + return c_base_return_error::s_false(); + } + + // @todo + return c_base_return_error::s_false(); + } + + /** + * Obtain HTTP response header: checksum_header. + * + * @return c_base_return_array|c_base_return_status + * An array containing: + * - 'what': A specific way in which to interpret the checksum. + * - 'type': The algorithm type of the checksum. + * - 'checksum': The checksum value after it has been base64 decoded. + * - 'action': An integer representing how this checksum is processed when generating the HTTP response. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_checksum_header() { + if (!array_key_exists(self::RESPONSE_CHECKSUM_HEADER, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CHECKSUM_HEADERS]); + } + + /** + * Obtain HTTP response header: checksum_headers. + * + * @return c_base_return_array|c_base_return_status + * An array containing a list of header field names. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_checksum_headers() { + if (!array_key_exists(self::RESPONSE_CHECKSUM_HEADERS, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CHECKSUM_HEADERS]); + } + + /** + * Obtain HTTP response header: checksum_content. + * + * @return c_base_return_array|c_base_return_status + * An array containing: + * - 'what': A specific way in which to interpret the checksum. + * - 'type': The algorithm type of the checksum. + * - 'checksum': The checksum value after it has been base64 decoded. + * - 'action': An integer representing how this checksum is processed when generating the HTTP response. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_checksum_content() { + if (!array_key_exists(self::RESPONSE_CHECKSUM_CONTENT, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($this->response[self::RESPONSE_CHECKSUM_CONTENT]); + } + + /** + * Obtain HTTP response header: content_revision. + * + * @return c_base_return_int|c_base_return_status + * An integer representing a revision number. + * FALSE with error bit set is returned on error, including when the key is not defined. + */ + public function get_response_content_revision() { + if (!array_key_exists(self::RESPONSE_CONTENT_REVISION, $this->response)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($this->response[self::RESPONSE_CONTENT_REVISION]); + } + + /** + * Return an array for mapping HTTP request header strings to header ids. + * + * @return c_base_return_array + * An array for mapping HTTP request header strings to header ids. + * + * @see: p_get_header_request_mapping() + */ + public function get_header_request_mapping() { + return c_base_return_array::s_new($this->p_get_header_request_mapping()); + } + + /** + * Return an array for mapping HTTP response header strings to header ids. + * + * @return c_base_return_array + * An array for mapping HTTP response header strings to header ids. + * + * @see: p_get_header_response_mapping() + */ + public function get_header_response_mapping() { + return c_base_return_array::s_new($this->p_get_header_response_mapping()); + } + + /** + * Return an array for mapping HTTP method strings to ids. + * + * @return c_base_return_array + * An array for mapping HTTP response header method strings to header method ids. + * + * @see: p_get_http_method_mapping() + */ + public function get_http_method_mapping() { + return c_base_return_array::s_new($this->p_get_http_method_mapping()); + } + + /** + * Send the response HTTP headers. + * + * This function will not correct incorrect behavior. + * The idea is to provide the caller with as much control as possible to handle different situations. + * + * This sends checksum header fields and any change in the headers or content may result in request checksum failing. + * + * @param bool $shuffle + * (optional) When TRUE, this will randomize the order in which header fields are defined, except for the status header (which is always first). + * This helps resist fingerprinting techniques thereby helping increase security. + * + * @return c_base_return_status + * TRUE is returned when headers are sent. + * FALSE is returned when headers have already been sent. + * FALSE with error bit set is returned on error. + * + * If headers were already sent, but not by this class, then FALSE with the error bit set is returned. + * + * @see: self::set_response_checksum_header() + * @see: self::set_response_checksum_headers() + * @see: self::set_response_checksum_content() + * @see: header() + * @see: headers_sent() + */ + public function send_response_headers($shuffle = TRUE) { + if (!is_bool($shuffle)) { + return c_base_return_error::s_false(); + } + + if ($this->headers_sent) { + return c_base_return_false; + } + + if (headers_sent()) { + return c_base_return_error::s_false(); + } + + $header_id_to_names = $this->p_get_header_response_mapping(TRUE); + + if ($shuffle) { + $headers = array(); + + // shuffle() alters the array keys, so some additional processing must be done to protect the array keys. + $shuffled = array_flip($this->p_get_header_response_mapping()); + shuffle($shuffled); + + $unshuffled = $this->p_get_header_response_mapping(); + foreach ($shuffled as $header_code) { + $headers[$header_code] = $unshuffled[$header_code]; + } + unset($header_code); + unset($unshuffled); + unset($shuffled); + } + else { + $headers = $this->p_get_header_response_mapping(); + } + + // this is used to perform checksums. + $header_output = array(); + + + // response status, this must always be first. + unset($headers[self::RESPONSE_STATUS]); + $status_string = NULL; + if (array_key_exists(self::RESPONSE_PROTOCOL, $this->response) && array_key_exists(self::RESPONSE_STATUS, $this->response)) { + $status_string = $this->response[self::RESPONSE_PROTOCOL] . ' '; + + $status_text = c_base_http_status::to_text($this->response[self::RESPONSE_STATUS]); + if ($status_text instanceof c_base_return_false) { + $status_string .= $this->response[self::RESPONSE_STATUS]; + } + else { + $status_string .= $this->response[self::RESPONSE_STATUS] . ' ' . $status_text->get_value_exact(); + } + unset($status_text); + + header($status_string, TRUE, $this->response[self::RESPONSE_STATUS]); + } + + $this->p_prepare_header_response_access_control_allow_origin($header_output); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS], $header_id_to_names[self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS], self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS); + $this->p_prepare_header_response_access_control_expose_headers($header_output); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_ACCESS_CONTROL_MAX_AGE], $header_id_to_names[self::RESPONSE_ACCESS_CONTROL_MAX_AGE], self::RESPONSE_ACCESS_CONTROL_MAX_AGE); + $this->p_prepare_header_response_access_control_allow_methods($header_output); + $this->p_prepare_header_response_access_control_allow_headers($header_output); + $this->p_prepare_header_response_accept_patch($header_output); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_ACCEPT_RANGES], $header_id_to_names[self::RESPONSE_ACCEPT_RANGES], self::RESPONSE_ACCEPT_RANGES); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_AGE], $header_id_to_names[self::RESPONSE_AGE], self::RESPONSE_AGE); + $this->p_prepare_header_response_allow($header_output); + $this->p_prepare_header_response_cache_control($header_output); + $this->p_prepare_header_response_connection($header_output); + $this->p_prepare_header_response_content_disposition($header_output); + $this->p_prepare_header_response_content_encoding($header_output); + $this->p_prepare_header_response_content_language($header_output); + // @todo: this is now an array of values. + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_CONTENT_LENGTH], $header_id_to_names[self::RESPONSE_CONTENT_LENGTH], self::RESPONSE_CONTENT_LENGTH); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_CONTENT_RANGE], $header_id_to_names[self::RESPONSE_CONTENT_RANGE], self::RESPONSE_CONTENT_RANGE); + $this->p_prepare_header_response_content_type($header_output); + $this->p_prepare_header_response_timestamp_value($header_output, $headers[self::RESPONSE_DATE], $header_id_to_names[self::RESPONSE_DATE], self::RESPONSE_DATE); + $this->p_prepare_header_response_timestamp_value($header_output, $headers[self::RESPONSE_DATE_ACTUAL], $header_id_to_names[self::RESPONSE_DATE_ACTUAL], self::RESPONSE_DATE_ACTUAL); + $this->p_prepare_header_response_etag($header_output); + $this->p_prepare_header_response_timestamp_value($header_output, $header_id_to_names[self::RESPONSE_EXPIRES], $header_id_to_names[self::RESPONSE_EXPIRES], self::RESPONSE_EXPIRES); + $this->p_prepare_header_response_timestamp_value($header_output, $header_id_to_names[self::RESPONSE_LAST_MODIFIED], $header_id_to_names[self::RESPONSE_LAST_MODIFIED], self::RESPONSE_LAST_MODIFIED); + $this->p_prepare_header_response_link($header_output); + $this->p_prepare_header_response_location($header_output); + $this->p_prepare_header_response_pragma($header_output); + $this->p_prepare_header_response_proxy_authenticate($header_output); + $this->p_prepare_header_response_public_key_pins($header_output); + $this->p_prepare_header_response_refresh($header_output); + $this->p_prepare_header_response_retry_after($header_output); + $this->p_prepare_header_response_server($header_output); + + // @todo: how is cookie going to be handled? + + $this->p_prepare_header_response_strict_transport_security($header_output); + $this->p_prepare_header_response_trailer($header_output); + $this->p_prepare_header_response_transfer_encoding($header_output); + $this->p_prepare_header_response_upgrade($header_output); + $this->p_prepare_header_response_vary($header_output); + $this->p_prepare_header_response_warning($header_output); + $this->p_prepare_header_response_www_authenticate($header_output); + $this->p_prepare_header_response_x_content_security_policy($header_output); + $this->p_prepare_header_response_x_content_type_options($header_output); + $this->p_prepare_header_response_x_ua_compatible($header_output); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_CONTENT_LENGTH], $header_id_to_names[self::RESPONSE_CONTENT_LENGTH], self::RESPONSE_CONTENT_LENGTH); + $this->p_prepare_header_response_simple_value($header_output, $headers[self::RESPONSE_CONTENT_REVISION], $header_id_to_names[self::RESPONSE_CONTENT_REVISION], self::RESPONSE_CONTENT_REVISION); + $this->p_prepare_header_response_checksum_content($header_output); + $this->p_prepare_header_response_checksum_headers($header_output, $status_string); + unset($status_string); + unset($header_id_to_names); + + + // send header output. + foreach ($headers as $header_id => $header_name) { + if (array_key_exists($header_id, $header_output)) { + header($header_output[$header_id]); + } + } + unset($output); + unset($header_output); + + + $this->headers_sent = TRUE; + return new c_base_return_true(); + } + + /** + * Get the HTTP response content. + * + * The term 'content' and 'body' seem to be used interchangably. + * The header fields often refer to this as 'content', therefore this will be called 'content' and neither 'body' nor 'data'. + * + * @return c_base_return_string|c_base_return_array + * A string representing the response content. + * If the content is represented via one or more files, then an array of filenames is returned. + */ + public function get_response_content() { + if ($this->content_is_file) { + return c_base_return_array::s_new($this->content); + } + + return c_base_return_string::s_new($this->content); + } + + /** + * Encode the HTTP response content. + * + * This must be performed after all content has been buffered and before the HTTP headers are sent. + * This must be called before sending the response content. + * + * This will alter the 'content-encoding' header. + * This will alter the 'content-length' header only if the encoding was altered. + * This will alter the 'transfer-encoding' header (if not already defined) and set it to 'chunked', when 'content-length' is not specified. + * + * If called when the content is a file, then that file will be compressed into memory. + * - This allows for the compressed file length to be calculated, but at the cost of additional memory consumption. + * - Do not call this when handling large files. + * + * @fixme: this needs to properly handle content-length when transfer-encoding is specified. + * + * @param int|null $compression + * (optional) The compression integer. + * This is specific to the algorithm used, but in general could be considered as follows: + * - NULL = use default. + * - -1 = use library default if possible or default if not. + * - 0 = no compression. + * - 1 -> 9 = compress level with 1 being weakest and 9 being strongest. + * Some algorithms, such as lzo, have their own predefined constants that can be specified here, such as LZO1X_999. + * @param int|null $max_filesize + * If content is a file and this is some value greater than 0, then files whose filesize is less than this will be loaded and compressed into memory. + * Otherwise, the content-length header will be unset, preventing an incorrect filesize from being transmitted. + * This represents filesizes in number of bytes. + * When NULL, the file size will not be calculated. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE without error bit set is returned if there is no assigned content to send. + * FALSE with error bit set is returned on error. + * + * @see: self::send_response_content() + * @see: self::set_response_content_encoding() + * @see: self::set_response_content_length() + */ + public function encode_response_content($compression = NULL, $max_filesize = NULL) { + if (!is_null($compression) && !is_int($compression)) { + return c_base_return_error::s_false(); + } + + if (!is_null($max_filesize) && !(is_int($max_filesize) && $max_filesize > 0)) { + return c_base_return_error::s_false(); + } + + $encoding = $this->p_determine_response_encoding(); + + if ($this->content_is_file) { + if (empty($this->content)) { + unset($encoding); + return c_base_return_false(); + } + + if (is_null($max_filesize)) { + $content = ''; + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($encoding); + unset($filename); + unset($content); + // @todo: provide a warning about missing files. + return c_base_return_error::s_false(); + } + + $content .= file_get_contents($filename); + } + unset($filename); + + if (empty($content)) { + unset($encoding); + unset($content); + return c_base_return_false(); + } + + $this->p_encode_content($content, $encoding, $compression, TRUE); + unset($content); + unset($encoding); + } + else { + $content = ''; + $content_length = 0; + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($encoding); + unset($filename); + unset($content); + // @todo: provide a warning about missing files. + return c_base_return_error::s_false(); + } + + $content_length += filesize($filename); + if ($content_length >= $max_filesize) { + break; + } + + $content .= file_get_contents($filename); + } + unset($filename); + + if (empty($content) || $content_length >= $max_filesize) { + unset($encoding); + unset($content); + return c_base_return_false(); + } + unset($content_length); + + $this->p_encode_content($content, $encoding, $compression, $compression, FALSE); + unset($content); + unset($encoding); + + // the content-length cannot be specified in this case. + unset($this->response[self::RESPONSE_CONTENT_LENGTH]); + } + } + else { + $this->p_encode_content($this->content, $encoding, $compression, TRUE); + unset($encoding); + } + + return new c_base_return_true(); + } + + /** + * Send the HTTP response content. + * + * The term 'content' and 'body' seem to be used interchangably. + * The header fields often refer to this as 'content', therefore this will be called 'content' and neither 'body' nor 'data'. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with the error bit set is returned on error. + * + * @see: self::encode_response_content() + */ + public function send_response_content() { + if ($this->content_is_file) { + // @todo: the $this->content may be an array of filenames instead of string data, handle that here. + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($filename); + // @todo: provide a warning about missing files. + return c_base_return_error::s_false(); + } + + $opened_file = fopen($filename, 'rb'); + if ($opened_file === FALSE) { + unset($filename); + // @todo: provide a warning about unable to open file. + return c_base_return_error::s_false(); + } + + fpassthru($opened_file); + fclose($opened_file); + } + unset($opened_file); + unset($filename); + } + else { + print($this->content); + } + + return new c_base_return_true(); + } + + /** + * Calculate the 8-bit length (octet) of the content/body. + * + * RFC 7230 describes the content-length as referring to the count based on every 8-bits, which is a single octet. + * Using strlen() would be incorrect because characters are 7-bit. + * Using mb_strlen() would be incorrect because it contains mixed lengths. + * Using any string test would be incorrect because the content may already be binary data. + * + * The solution is to break apart the string into 8-bit chunks and then calculate the length. + * + * Errata: It turns out this solution is expensive, and so is disabled for now. + * - Because this only uses strlen(), there is no way to protect against strlen() becoming mb_strlen() via the mbstring.func_overload setting. + * - Therefore, mbstring.func_overload must never be enabled or risk causing security issues. + * + * @return int + * Total number of octals on success. + * + * @see: set_response_content_length() + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.2 + */ + private function p_calculate_content_length($text) { + return strlen($text); + } + + /** + * Load and process the HTTP request parameter: accept. + * + * @see: self::pr_rfc_string_is_negotiation() + * @see: https://tools.ietf.org/html/rfc7231#section-5.3.2 + */ + private function p_load_request_accept() { + if (empty($this->headers['accept'])) { + $this->request[self::REQUEST_ACCEPT]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['accept']); + if ($text['invalid']) { + $this->request[self::REQUEST_ACCEPT]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_ACCEPT]['data'] = $this->pr_rfc_string_is_negotiation($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_ACCEPT]['data']['invalid']) { + $this->request[self::REQUEST_ACCEPT]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_ACCEPT]['defined'] = TRUE; + $this->request[self::REQUEST_ACCEPT]['data']['accept'] = NULL; + $this->request[self::REQUEST_ACCEPT]['data']['category'] = NULL; + $this->request[self::REQUEST_ACCEPT]['data']['weight'] = array(); + $this->request[self::REQUEST_ACCEPT]['data']['types'] = array(); + #$this->request[self::REQUEST_ACCEPT]['data']['categories'] = array(); + + // convert the known values into integers for improved processing. + foreach ($this->request[self::REQUEST_ACCEPT]['data']['choices'] as $weight => &$choice) { + foreach ($choice as $key => &$c) { + $result = c_base_mime::s_identify($c['choice'], TRUE); + if ($result instanceof c_base_return_false) { + // there is no valid value to process. + continue; + } + + $identified = $result->get_value_exact(); + unset($result); + + $c['accept'] = $identified['id_type']; + $c['category'] = $identified['id_category']; + $this->request[self::REQUEST_ACCEPT]['data']['types'][$weight][$identified['id_type']] = $identified['id_type']; + #$this->request[self::REQUEST_ACCEPT]['data']['categories'][$weight][$identified['id_category']] = $identified['id_category']; + $this->request[self::REQUEST_ACCEPT]['data']['weight'][$weight][$identified['id_type']] = $identified['name_category'] . '/' . $identified['name_type']; + + // @todo: should this be used or not? + #if ($identified['id_category'] == c_base_mime::CATEGORY_UNKNOWN || $identified['id_type'] == c_base_mime::TYPE_UNKNOWN) { + # $c['accept'] = NULL; + #} + + unset($identified); + } + } + unset($choice); + unset($weight); + unset($c); + unset($key); + + // sort the weight array. + krsort($this->request[self::REQUEST_ACCEPT]['data']['weight']); + + // The NULL key should be the first key in the weight. + $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT]['data']['weight']); + + // rename 'choices' array key to 'accept'. + $this->request[self::REQUEST_ACCEPT]['data']['accept'] = $this->request[self::REQUEST_ACCEPT]['data']['choices']; + unset($this->request[self::REQUEST_ACCEPT]['data']['choices']); + } + unset($this->request[self::REQUEST_ACCEPT]['data']['invalid']); + unset($this->request[self::REQUEST_ACCEPT]['data']['current']); + + $this->request[self::REQUEST_ACCEPT]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: accept-language. + * + * @see: self::pr_rfc_string_is_negotiation() + * @see: https://tools.ietf.org/html/rfc7231#section-5.3.5 + */ + private function p_load_request_accept_language() { + if (empty($this->headers['accept_language'])) { + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['accept_language']); + if ($text['invalid']) { + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data'] = $this->pr_rfc_string_is_negotiation($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['invalid']) { + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['defined'] = TRUE; + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight'] = array(); + + if (is_null($this->language_class) || !class_exists($this->language_class)) { + // PHP does not allow "new self::ACCEPT_LANGUAGE_CLASS_DEFAULT()", but using a variable is allowed. + $class = self::ACCEPT_LANGUAGE_CLASS_DEFAULT; + $languages = new $class(); + unset($class); + } + else { + $languages = new $this->language_class(); + } + + // convert the known values into integers for improved processing. + foreach ($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['choices'] as $weight => &$choice) { + foreach ($choice as $key => &$c) { + $id = $languages->s_get_id_by_name($c['choice']); + + if ($id instanceof c_base_return_false) { + $c['language'] = NULL; + } + else { + $c['language'] = $id->get_value_exact(); + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight'][$weight][$c['language']] = mb_strtolower($c['choice']); + unset($c['choice']); + } + } + } + unset($languages); + + // sort the weight array. + krsort($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight']); + + // The NULL key should be the first key in the weight. + $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['weight']); + + // rename 'choices' array key to 'language'. + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['language'] = $this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['choices']; + unset($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['choices']); + } + unset($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['invalid']); + unset($this->request[self::REQUEST_ACCEPT_LANGUAGE]['data']['current']); + + $this->request[self::REQUEST_ACCEPT_LANGUAGE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: accept-encoding. + * + * @see: self::pr_rfc_string_is_negotiation() + * @see: https://tools.ietf.org/html/rfc7231#section-5.3.4 + */ + private function p_load_request_accept_encoding() { + if (empty($this->headers['accept_encoding'])) { + $this->request[self::REQUEST_ACCEPT_ENCODING]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['accept_encoding']); + if ($text['invalid']) { + $this->request[self::REQUEST_ACCEPT_ENCODING]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_ACCEPT_ENCODING]['data'] = $this->pr_rfc_string_is_negotiation($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['invalid']) { + $this->request[self::REQUEST_ACCEPT_ENCODING]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_ACCEPT_ENCODING]['defined'] = TRUE; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'] = array(); + + // convert the known values into integers for improved processing. + foreach ($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['choices'] as $weight => &$choice) { + foreach ($choice as $key => &$c) { + $lowercase = mb_strtolower($c['choice']); + if ($lowercase == 'chunked') { + $c['encoding'] = self::ENCODING_CHUNKED; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_CHUNKED] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'compress') { + $c['encoding'] = self::ENCODING_COMPRESS; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_COMPRESS] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'deflate') { + $c['encoding'] = self::ENCODING_DEFLATE; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_DEFLATE] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'gzip') { + $c['encoding'] = self::ENCODING_GZIP; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_GZIP] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'bzip') { + $c['encoding'] = self::ENCODING_BZIP; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_BZIP] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'lzo') { + $c['encoding'] = self::ENCODING_LZO; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_LZO] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'xz') { + $c['encoding'] = self::ENCODING_XZ; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_XZ] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'exit') { + $c['encoding'] = self::ENCODING_EXI; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_EXI] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'identity') { + $c['encoding'] = self::ENCODING_IDENTITY; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_IDENTITY] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'sdch') { + $c['encoding'] = self::ENCODING_SDCH; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_SDCH] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'pg') { + $c['encoding'] = self::ENCODING_PG; + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'][$weight][self::ENCODING_PG] = $lowercase; + unset($c['choice']); + } + else { + $c['encoding'] = NULL; + } + } + } + unset($lowercase); + + // sort the weight array. + krsort($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight']); + + // The NULL key should be the first key in the weight. + $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight']); + + // rename 'choices' array key to 'encoding'. + $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['encoding'] = $this->request[self::REQUEST_ACCEPT_ENCODING]['data']['choices']; + unset($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['choices']); + } + unset($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['invalid']); + unset($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['current']); + + $this->request[self::REQUEST_ACCEPT_ENCODING]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: accept-charset. + * + * @see: self::pr_rfc_string_is_negotiation() + * @see: https://tools.ietf.org/html/rfc7231#section-5.3.3 + */ + private function p_load_request_accept_charset() { + if (empty($this->headers['accept_charset'])) { + $this->request[self::REQUEST_ACCEPT_CHARSET]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['accept_charset']); + if ($text['invalid']) { + $this->request[self::REQUEST_ACCEPT_CHARSET]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_ACCEPT_CHARSET]['data'] = $this->pr_rfc_string_is_negotiation($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['invalid']) { + $this->request[self::REQUEST_ACCEPT_CHARSET]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_ACCEPT_CHARSET]['defined'] = TRUE; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'] = array(); + + // convert the known values into integers for improved processing. + foreach ($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['choices'] as $weight => &$choice) { + foreach ($choice as $key => &$c) { + $lowercase = mb_strtolower($c['choice']); + if ($lowercase == 'ascii') { + $c['charset'] = c_base_charset::ASCII; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ASCII] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'utf-8') { + $c['charset'] = c_base_charset::UTF_8; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::UTF_8] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'utf-16') { + $c['charset'] = c_base_charset::UTF_16; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::UTF_16] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'utf-32') { + $c['charset'] = c_base_charset::UTF_32; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::UTF_32] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-1') { + $c['charset'] = c_base_charset::ISO_8859_1; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_1] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-2') { + $c['charset'] = c_base_charset::ISO_8859_2; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_2] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-3') { + $c['charset'] = c_base_charset::ISO_8859_3; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_3] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-4') { + $c['charset'] = c_base_charset::ISO_8859_4; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_4] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-5') { + $c['charset'] = c_base_charset::ISO_8859_5; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_5] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-6') { + $c['charset'] = c_base_charset::ISO_8859_6; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_6] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-7') { + $c['charset'] = c_base_charset::ISO_8859_7; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_7] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-8') { + $c['charset'] = c_base_charset::ISO_8859_8; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_8] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-9') { + $c['charset'] = c_base_charset::ISO_8859_9; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_9] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-10') { + $c['charset'] = c_base_charset::ISO_8859_10; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_10] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-11') { + $c['charset'] = c_base_charset::ISO_8859_11; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_11] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-12') { + $c['charset'] = c_base_charset::ISO_8859_12; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_12] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-13') { + $c['charset'] = c_base_charset::ISO_8859_13; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_13] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-14') { + $c['charset'] = c_base_charset::ISO_8859_14; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_14] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-15') { + $c['charset'] = c_base_charset::ISO_8859_15; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_15] = $lowercase; + unset($c['choice']); + } + elseif ($lowercase == 'iso-8859-16') { + $c['charset'] = c_base_charset::ISO_8859_16; + $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight'][$weight][c_base_charset::ISO_8859_16] = $lowercase; + unset($c['choice']); + } + else { + $c['charset'] = NULL; + } + } + } + unset($lowercase); + + // sort the weight array. + krsort($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight']); + + // The NULL key should be the first key in the weight. + $this->p_prepend_array_value(NULL, $this->request[self::REQUEST_ACCEPT_CHARSET]['data']['weight']); + } + unset($this->request[self::REQUEST_ACCEPT_CHARSET]['data']['invalid']); + + $this->request[self::REQUEST_ACCEPT_CHARSET]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: accept-datetime. + * + * This is not part of an official standard, it is provided to support a separate web archive standard. + * + * @see: http://www.mementoweb.org/guide/rfc/ID/#Accept-Memento-Datetime + */ + private function p_load_request_accept_datetime() { + if (p_validate_date_is_valid_rfc($this->headers['accept_datetime']) === FALSE) { + $this->request[self::REQUEST_DATE]['invalid'] = TRUE; + return; + } + + $timestamp = strtotime($this->headers['accept_datetime']); + if ($timestamp === FALSE) { + $this->request[self::REQUEST_DATE]['invalid'] = TRUE; + unset($timestamp); + return; + } + + $this->request[self::REQUEST_ACCEPT_DATETIME]['defined'] = TRUE; + $this->request[self::REQUEST_ACCEPT_DATETIME]['data'] = $timestamp; + $this->request[self::REQUEST_ACCEPT_DATETIME]['invalid'] = FALSE; + + unset($timestamp); + } + + /** + * Load and process the HTTP request parameter: cache-control. + * + * Only 'no-cache' is supported at this time for requests. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.2 + */ + private function p_load_request_cache_control() { + $cache_control = $this->headers['cache_control']; + if (empty($cache_control)) { + $this->request[self::REQUEST_CACHE_CONTROL]['invalid'] = TRUE; + unset($cache_control); + return; + } + + $this->request[self::REQUEST_CACHE_CONTROL]['data'] = array( + 'methods' => array(), + ); + + $integer_types = array( + SELF::CACHE_CONTROL_MAX_AGE => 'max-age=', + SELF::CACHE_CONTROL_MAX_STALE => 'max-stale=', + SELF::CACHE_CONTROL_MIN_FRESH => 'min-fresh=', + ); + + $parts = mb_split(', ', $cache_control); + foreach ($parts as $part) { + $cleaned_up = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $part)); + if ($cleaned_up == 'no-cache') { + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data']['methods'][SELF::CACHE_CONTROL_NO_CACHE] = SELF::CACHE_CONTROL_NO_CACHE; + } + elseif ($cleaned_up == 'no-store') { + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data']['methods'][SELF::CACHE_CONTROL_NO_STORE] = SELF::CACHE_CONTROL_NO_STORE; + } + elseif ($cleaned_up == 'no-transform') { + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data']['methods'][SELF::CACHE_CONTROL_NO_TRANSFORM] = SELF::CACHE_CONTROL_NO_TRANSFORM; + } + elseif ($cleaned_up == 'only-if-cached') { + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data']['methods'][SELF::CACHE_CONTROL_ONLY_IF_CACHED] = SELF::CACHE_CONTROL_ONLY_IF_CACHED; + } + else { + foreach ($integer_types as $type_id => $type_string) { + if (mb_strpos($cleaned_up, $type_string) === FALSE) { + continue; + } + + $pieces = mb_split('=', $cleaned_up); + if (!isset($pieces[1]) || !is_numeric($pieces[1]) || count($pieces) > 2) { + $this->request[self::REQUEST_CACHE_CONTROL]['invalid'] = TRUE; + unset($pieces); + continue; + } + + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data']['methods'][$type_id] = (int) $pieces[1]; + + unset($pieces); + } + + unset($type_id); + unset($type_string); + } + + unset($cleaned_up); + } + unset($part); + unset($parts); + unset($cache_control); + + $this->request[self::REQUEST_CACHE_CONTROL]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: connection. + * + * @see: self::pr_rfc_string_is_commad_token() + * @see: https://tools.ietf.org/html/rfc7230 + * @see: https://tools.ietf.org/html/rfc7230#appendix-B + */ + private function p_load_request_connection() { + if (empty($this->headers['connection'])) { + $this->request[self::REQUEST_CONNECTION]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['connection']); + if ($text['invalid']) { + $this->request[self::REQUEST_CONNECTION]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_CONNECTION]['data'] = $this->pr_rfc_string_is_commad_token($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_CONNECTION]['data']['invalid']) { + $this->request[self::REQUEST_CONNECTION]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_CONNECTION]['defined'] = TRUE; + + // rename 'tokens' array key to 'connection'. + $this->request[self::REQUEST_CONNECTION]['data']['connection'] = $this->request[self::REQUEST_CONNECTION]['data']['tokens']; + unset($this->request[self::REQUEST_CONNECTION]['data']['tokens']); + } + unset($this->request[self::REQUEST_CONNECTION]['data']['invalid']); + unset($this->request[self::REQUEST_CONNECTION]['data']['current']); + + $this->request[self::REQUEST_CONNECTION]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: pragma. + * + * This is an older version of cache_control that supports 'no-cache'. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.4 + */ + private function p_load_request_pragma() { + if ($this->request[self::REQUEST_CACHE_CONTROL]['defined']) { + // this is a conflict, favor 'cache-control' over 'pragma'. + return; + } + + $pragma = $this->headers['pragma']; + if (empty($pragma)) { + // silently fail on invalid pragma. + unset($pragma); + return; + } + + $cleaned_up = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $pragma)); + if ($cleaned_up == 'no-cache') { + $this->request[self::REQUEST_CACHE_CONTROL]['defined'] = TRUE; + $this->request[self::REQUEST_CACHE_CONTROL]['data'][SELF::CACHE_CONTROL_NO_CACHE] = SELF::CACHE_CONTROL_NO_CACHE; + } + unset($cleaned_up); + unset($pragma); + + $this->request[self::REQUEST_CACHE_CONTROL]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: cookie. + * + * @see: https://tools.ietf.org/html/rfc6265 + */ + private function p_load_request_cookies() { + $this->request[self::REQUEST_COOKIE]['data'] = array(); + + foreach ($_COOKIE as $cookie_name => $cookie_values) { + $cookie = new c_base_cookie(); + $result = $cookie->set_name($cookie_name); + + if ($result instanceof c_base_return_false) { + unset($cookie); + unset($result); + continue; + } + + $cookie->do_pull(); + $this->request[self::REQUEST_COOKIE]['data'][$cookie_name] = $cookie; + $this->request[self::REQUEST_COOKIE]['defined'] = TRUE; + unset($cookie); + } + unset($cookie_name); + unset($cookie_values); + + $this->request[self::REQUEST_COOKIE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: content-length. + * + * This value is represented in octets (8-bit bytes). + * + * @see: self::pr_rfc_string_is_digit() + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.2 + */ + private function p_load_request_content_length() { + if (is_int($this->headers['content_length'])) { + $this->request[self::REQUEST_CONTENT_LENGTH]['defined'] = TRUE; + $this->request[self::REQUEST_CONTENT_LENGTH]['data'] = (int) $this->headers['content_length']; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['content_length']); + if ($text['invalid']) { + $this->request[self::REQUEST_CONTENT_LENGTH]['invalid'] = TRUE; + unset($text); + return; + } + + $parsed = $this->pr_rfc_string_is_digit($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + $this->request[self::REQUEST_CONTENT_LENGTH]['invalid'] = TRUE; + unset($parsed); + return; + } + + $this->request[self::REQUEST_CONTENT_LENGTH]['defined'] = TRUE; + $this->request[self::REQUEST_CONTENT_LENGTH]['data'] = intval($parsed['text']); + unset($parsed); + + $this->request[self::REQUEST_CONTENT_LENGTH]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: content-type. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.1.5 + */ + private function p_load_request_content_type() { + $content_type = $this->headers['content_type']; + if (empty($content_type)) { + $this->request[self::REQUEST_CONTENT_TYPE]['invalid'] = TRUE; + unset($content_type); + return; + } + + $content_type_parts = mb_split(';', $content_type); + $content_type_part = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $content_type_parts[0])); + + $this->request[self::REQUEST_CONTENT_TYPE]['defined'] = TRUE; + $this->request[self::REQUEST_CONTENT_TYPE]['data'] = $content_type_part; + + unset($content_type_part); + unset($content_type_parts); + unset($content_type); + + $this->request[self::REQUEST_CONTENT_TYPE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: date. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 + */ + private function p_load_request_date() { + if (p_validate_date_is_valid_rfc($this->headers['date']) === FALSE) { + $this->request[self::REQUEST_DATE]['invalid'] = TRUE; + return; + } + + $timestamp = strtotime($this->headers['date']); + if ($timestamp === FALSE) { + $this->request[self::REQUEST_DATE]['invalid'] = TRUE; + unset($timestamp); + return; + } + + $this->request[self::REQUEST_DATE]['defined'] = TRUE; + $this->request[self::REQUEST_DATE]['data'] = $timestamp; + + unset($timestamp); + + $this->request[self::REQUEST_DATE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: expect. + * + * Only '100-continue' is defined by the standard and the server may respond with a 416 (Expectation Failed) as the status code. + * + * This is essentially a kind way to inform the server that 'hey, I've got a packet coming your way, will you accept it?'. + * The server may also respond with 401 (Unauthorized) or 405 (Methid Not Allowed). + * + * The server should expect header fields like content-type, content-length, and even the operation, such as PUT /some/path. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.1.1 + */ + private function p_load_request_expect() { + $expect = $this->headers['expect']; + if (empty($expect)) { + $this->request[self::REQUEST_EXPECT]['invalid'] = TRUE; + unset($expect); + return; + } + + $this->request[self::REQUEST_EXPECT]['defined'] = TRUE; + $this->request[self::REQUEST_EXPECT]['data'] = $expect; + + unset($expect); + + $this->request[self::REQUEST_EXPECT]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: from. + * + * Warning: never use PHP's filter_var('', FILTER_VALIDATE_EMAIL), it is non-compliant and fails to properly validate. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.1 + * @see: https://tools.ietf.org/html/rfc5322#section-3.4 + */ + private function p_load_request_from() { + if (empty($this->headers['from'])) { + $this->request[self::REQUEST_FROM]['invalid'] = TRUE; + return; + } + + // @todo: write a custom validation to ensure that the from email address is valid. + $this->request[self::REQUEST_FROM]['defined'] = TRUE; + $this->request[self::REQUEST_FROM]['data'] = $this->headers['from']; + + unset($from); + + $this->request[self::REQUEST_FROM]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: host. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.1 + * @see: https://tools.ietf.org/html/rfc5322#section-3.4 + */ + private function p_load_request_host() { + if (empty($this->headers['host'])) { + $this->request[self::REQUEST_HOST]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_HOST]['data'] = $this->p_parse_uri($this->headers['host']); + + if ($this->request[self::REQUEST_HOST]['data']['invalid']) { + $this->request[self::REQUEST_HOST]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_HOST]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_HOST]['data']['invalid']); + + $this->request[self::REQUEST_HOST]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: if-match. + * + * @see: https://tools.ietf.org/html/rfc7234 + * @see: https://tools.ietf.org/html/rfc7232#section-3.1 + * @see: https://tools.ietf.org/html/rfc7232#section-2.3 + */ + private function p_load_request_if_match() { + if (empty($this->headers['if_match'])) { + $this->request[self::REQUEST_IF_MATCH]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_IF_MATCH]['data'] = $this->p_parse_if_entity_tag($this->headers['if_match']); + + if ($this->request[self::REQUEST_IF_MATCH]['data']['invalid']) { + $this->request[self::REQUEST_IF_MATCH]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_IF_MATCH]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_IF_MATCH]['data']['current']); + unset($this->request[self::REQUEST_IF_MATCH]['data']['invalid']); + + $this->request[self::REQUEST_IF_MATCH]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: if-none-match. + * + * @see: https://tools.ietf.org/html/rfc7234 + * @see: https://tools.ietf.org/html/rfc7232#section-3.2 + * @see: https://tools.ietf.org/html/rfc7232#section-2.3 + */ + private function p_load_request_if_none_match() { + if (empty($this->headers['if_none_match'])) { + $this->request[self::REQUEST_IF_NONE_MATCH]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_IF_NONE_MATCH]['data'] = $this->p_parse_if_entity_tag_and_weak($this->headers['if_none_match']); + + if ($this->request[self::REQUEST_IF_NONE_MATCH]['data']['invalid']) { + $this->request[self::REQUEST_IF_NONE_MATCH]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_IF_NONE_MATCH]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_IF_NONE_MATCH]['data']['invalid']); + + $this->request[self::REQUEST_IF_NONE_MATCH]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: if-modified-since. + * + * @see: https://tools.ietf.org/html/rfc7232#section-3.3 + */ + private function p_load_request_if_modified_since() { + if ($this->p_validate_date_is_valid_rfc($this->headers['if_modified_since']) === FALSE) { + $this->request[self::REQUEST_IF_MODIFIED_SINCE]['invalid'] = TRUE; + return; + } + + $timestamp = strtotime($this->headers['if_modified_since']); + if ($timestamp === FALSE) { + $this->request[self::REQUEST_IF_MODIFIED_SINCE]['invalid'] = TRUE; + unset($timestamp); + return; + } + + $this->request[self::REQUEST_IF_MODIFIED_SINCE]['defined'] = TRUE; + $this->request[self::REQUEST_IF_MODIFIED_SINCE]['data'] = $timestamp; + + unset($timestamp); + + $this->request[self::REQUEST_IF_MODIFIED_SINCE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: if-unmodified-since. + * + * @see: https://tools.ietf.org/html/rfc7232#section-3.4 + */ + private function p_load_request_if_unmodified_since() { + if (p_validate_date_is_valid_rfc($this->headers['if_unmodified_since']) === FALSE) { + $this->request[self::REQUEST_IF_UNMODIFIED_SINCE]['invalid'] = TRUE; + return; + } + + $timestamp = strtotime($this->headers['if_unmodified_since']); + if ($timestamp === FALSE) { + $this->request[self::REQUEST_IF_UNMODIFIED_SINCE]['invalid'] = TRUE; + unset($timestamp); + return; + } + + $this->request[self::REQUEST_IF_UNMODIFIED_SINCE]['defined'] = TRUE; + $this->request[self::REQUEST_IF_UNMODIFIED_SINCE]['data'] = $timestamp; + + unset($timestamp); + + $this->request[self::REQUEST_IF_UNMODIFIED_SINCE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: if-range. + * + * The range can be either a date or an entity-tag. + * + * @see: https://tools.ietf.org/html/rfc7232#section-3.5 + * @see: https://tools.ietf.org/html/rfc7233#section-3.2 + */ + private function p_load_request_if_range() { + if (p_validate_date_is_valid_rfc($this->headers['if_range'])) { + $timestamp = strtotime($this->headers['if_range']); + if ($timestamp === FALSE) { + $this->request[self::REQUEST_IF_RANGE]['invalid'] = TRUE; + $this->request[self::REQUEST_IF_RANGE]['data'] = array( + 'is_date' => TRUE, + ); + unset($timestamp); + return; + } + + $this->request[self::REQUEST_IF_RANGE]['defined'] = TRUE; + $this->request[self::REQUEST_IF_RANGE]['data'] = array( + 'range' => $timestamp, + 'is_date' => TRUE, + ); + + unset($timestamp); + return; + } + + // at this point, assume the if-range is an entity tag. + $if_range = $this->headers['if_range']; + if (empty($if_range)) { + $this->request[self::REQUEST_IF_RANGE]['if_range'] = TRUE; + $this->request[self::REQUEST_IF_RANGE]['data']['is_date'] = FALSE; + unset($if_range); + return; + } + + $this->request[self::REQUEST_IF_RANGE]['data'] = $this->p_parse_if_entity_tag_and_weak($if_range); + $this->request[self::REQUEST_IF_RANGE]['data']['is_date'] = FALSE; + + if ($this->request[self::REQUEST_IF_RANGE]['data']['invalid']) { + $this->request[self::REQUEST_IF_RANGE]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_IF_RANGE]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_IF_RANGE]['data']['invalid']); + + unset($if_range); + + $this->request[self::REQUEST_IF_RANGE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: range. + * + * @see: https://tools.ietf.org/html/rfc7233#section-3.1 + */ + private function p_load_request_range() { + if (empty($this->headers['range'])) { + $this->request[self::REQUEST_RANGE]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['range']); + if ($text['invalid']) { + $this->request[self::REQUEST_RANGE]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_RANGE]['data'] = $this->pr_rfc_string_is_range($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_RANGE]['data']['invalid']) { + $this->request[self::REQUEST_RANGE]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_RANGE]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_RANGE]['data']['invalid']); + + $this->request[self::REQUEST_RANGE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: max-forwards. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.1.2 + */ + private function p_load_request_max_forwards() { + if (is_int($this->headers['max_forwards'])) { + $this->request[self::REQUEST_MAX_FORWARDS]['defined'] = TRUE; + $this->request[self::REQUEST_MAX_FORWARDS]['data'] = (int) $this->headers['max_forwards']; + } + + $text = $this->pr_rfc_string_prepare($this->headers['max_forwards']); + if ($text['invalid']) { + $this->request[self::REQUEST_MAX_FORWARDS]['invalid'] = TRUE; + unset($text); + return; + } + + $parsed = pr_rfc_string_is_digit($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + $this->request[self::REQUEST_MAX_FORWARDS]['invalid'] = TRUE; + unset($parsed); + return; + } + + $this->request[self::REQUEST_MAX_FORWARDS]['defined'] = TRUE; + $this->request[self::REQUEST_MAX_FORWARDS]['data'] = intval($parsed['text']); + + unset($parsed); + + $this->request[self::REQUEST_MAX_FORWARDS]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: origin. + * + * Errata: I cannot find Origin specified in the RFC's that I looked at. + * - Either I am completely overlooking it or its defined in some other standard. + * - I will use wikipedia's notes to define and utilize this field. + * + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + private function p_load_request_origin() { + if (empty($this->headers['origin'])) { + $this->request[self::REQUEST_ORIGIN]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_ORIGIN]['data'] = $this->p_parse_uri($this->headers['origin']); + + if ($this->request[self::REQUEST_ORIGIN]['data']['invalid']) { + $this->request[self::REQUEST_ORIGIN]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_ORIGIN]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_ORIGIN]['data']['invalid']); + + $this->request[self::REQUEST_ORIGIN]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: referer. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.2 + */ + private function p_load_request_referer() { + if (empty($this->headers['referer'])) { + $this->request[self::REQUEST_REFERER]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_REFERER]['data'] = $this->p_parse_uri($this->headers['referer']); + + if ($this->request[self::REQUEST_REFERER]['data']['invalid']) { + $this->request[self::REQUEST_REFERER]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_REFERER]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_REFERER]['data']['invalid']); + + $this->request[self::REQUEST_REFERER]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: te. + * + * @see: https://tools.ietf.org/html/rfc7230#section-4.3 + */ + private function p_load_request_te() { + if (empty($this->headers['te'])) { + $this->request[self::REQUEST_TE]['te'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['te']); + if ($text['invalid']) { + $this->request[self::REQUEST_TE]['invalid'] = TRUE; + unset($text); + return; + } + + $this->request[self::REQUEST_TE]['data'] = $this->pr_rfc_string_is_negotiation($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_TE]['data']['invalid']) { + $this->request[self::REQUEST_TE]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_TE]['defined'] = TRUE; + + // convert the known values into integers for improved processing. + foreach ($this->request[self::REQUEST_TE]['data']['choices'] as $weight => &$choice) { + foreach ($choice as $key => &$c) { + $lowercase = mb_strtolower($c['choice']); + if ($c['choice'] == 'compress') { + $c['encoding'] = self::ENCODING_COMPRESS; + } + elseif ($c['choice'] == 'deflate') { + $c['encoding'] = self::ENCODING_DEFLATE; + } + elseif ($c['choice'] == 'gzip') { + $c['encoding'] = self::ENCODING_GZIP; + } + elseif ($c['choice'] == 'bzip') { + $c['encoding'] = self::ENCODING_BZIP; + } + elseif ($c['choice'] == 'lzo') { + $c['encoding'] = self::ENCODING_LZO; + } + elseif ($c['choice'] == 'xz') { + $c['encoding'] = self::ENCODING_XZ; + } + elseif ($c['choice'] == 'exit') { + $c['encoding'] = self::ENCODING_EXI; + } + elseif ($c['choice'] == 'identity') { + $c['encoding'] = self::ENCODING_IDENTITY; + } + elseif ($c['choice'] == 'sdch') { + $c['encoding'] = self::ENCODING_SDCH; + } + elseif ($c['choice'] == 'pg') { + $c['encoding'] = self::ENCODING_PG; + } + else { + $c['encoding'] = NULL; + } + } + } + unset($lowercase); + } + unset($this->request[self::REQUEST_TE]['data']['invalid']); + + $this->request[self::REQUEST_TE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: user-agent. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3 + */ + private function p_load_request_user_agent() { + if (empty($this->headers['user_agent'])) { + $this->request[self::REQUEST_USER_AGENT]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['user_agent']); + if ($text['invalid']) { + $this->request[self::REQUEST_USER_AGENT]['invalid'] = TRUE; + unset($text); + return; + } + + // make sure agent is valid text. + $agent = $this->pr_rfc_string_is_basic($text['ordinals'], $text['characters']); + unset($text); + + if ($agent['invalid']) { + $this->request[self::REQUEST_USER_AGENT]['invalid'] = TRUE; + unset($agent); + return; + } + + $this->request[self::REQUEST_USER_AGENT]['data'] = $this->p_parse_user_agent($agent['text']); + unset($agent); + + if ($this->request[self::REQUEST_USER_AGENT]['data']['invalid']) { + $this->request[self::REQUEST_USER_AGENT]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_USER_AGENT]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_USER_AGENT]['data']['invalid']); + + $this->request[self::REQUEST_USER_AGENT]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: upgrade. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3 + */ + private function p_load_request_upgrade() { + if (empty($this->headers['upgrade'])) { + $this->request[self::REQUEST_UPGRADE]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['upgrade']); + if ($text['invalid']) { + $this->request[self::REQUEST_UPGRADE]['invalid'] = TRUE; + unset($text); + return; + } + + // @todo: future versions may do something with this, until then just check for text. + $this->request[self::REQUEST_UPGRADE]['data'] = $this->pr_rfc_string_is_basic($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_UPGRADE]['data']['invalid']) { + $this->request[self::REQUEST_UPGRADE]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_UPGRADE]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_UPGRADE]['data']['invalid']); + + $this->request[self::REQUEST_UPGRADE]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: via. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3 + */ + private function p_load_request_via() { + if (empty($this->headers['via'])) { + $this->request[self::REQUEST_VIA]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['via']); + if ($text['invalid']) { + $this->request[self::REQUEST_VIA]['invalid'] = TRUE; + unset($text); + return; + } + + // @todo: future versions may do something with this, until then just check for text. + $this->request[self::REQUEST_VIA]['data'] = $this->pr_rfc_string_is_basic($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_VIA]['data']['invalid']) { + $this->request[self::REQUEST_VIA]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_VIA]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_VIA]['data']['invalid']); + + $this->request[self::REQUEST_VIA]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: warning. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.5 + */ + private function p_load_request_warning() { + if (empty($this->headers['warning'])) { + $this->request[self::REQUEST_WARNING]['invalid'] = TRUE; + return; + } + + $text = $this->pr_rfc_string_prepare($this->headers['warning']); + if ($text['invalid']) { + $this->request[self::REQUEST_WARNING]['invalid'] = TRUE; + unset($text); + return; + } + + // @todo: future versions may do something with this, until then just check for text. + $this->request[self::REQUEST_WARNING]['data'] = $this->pr_rfc_string_is_basic($text['ordinals'], $text['characters']); + unset($text); + + if ($this->request[self::REQUEST_WARNING]['data']['invalid']) { + $this->request[self::REQUEST_WARNING]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_WARNING]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_WARNING]['data']['invalid']); + + $this->request[self::REQUEST_WARNING]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: checksum_header. + * + * The following format is expected: + * - 1*(atext):1*(atext):1*(atext) + * + * The header should have either 'partial:checksum_type:checksum_value' or 'complete:checksum_type:checksum_value'. + * The checksum_value is stored in base64. + * + * The content checksum represents the checksum of the HTTP content (HTTP packet body). + * This should already be defined if and when a checksum_header is used. + * + * The header checksum represents the checksum of the HTTP header when only the checksum_header field is missing. + * The checksum_content header should not be removed when creating or validating the checksum_header. + * + * @see: self::p_parse_checksum() + */ + private function p_load_request_checksum_header() { + if (empty($this->headers['checksum_header'])) { + $this->request[self::REQUEST_CHECKSUM_HEADER]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_CHECKSUM_HEADER]['data'] = $this->p_parse_checksum($this->headers['checksum_header']); + + if ($this->request[self::REQUEST_CHECKSUM_HEADER]['data']['invalid']) { + $this->request[self::REQUEST_CHECKSUM_HEADER]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_CHECKSUM_HEADER]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_CHECKSUM_HEADER]['data']['invalid']); + + $this->request[self::REQUEST_CHECKSUM_HEADER]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: checksum_headers. + * + * The following format is expected: + * - 1*(atext)*(*(wsp) "," *(wsp)1*(atext)) + * + * The headers is a comma separated list of http headers present at the time the header checksum was generated. + * This is necessary because anything en-route may add or alter headers and the checksum needs to still validate for the checksum provided. + * This also gives the client or server and idea on what was added and what was not. + * + * @see: self::p_parse_checksum_headers() + */ + private function p_load_request_checksum_headers() { + if (empty($this->headers['checksum_header'])) { + $this->request[self::REQUEST_CHECKSUM_HEADERS]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_CHECKSUM_HEADERS]['data'] = $this->p_parse_checksum_headers($this->headers['checksum_headers']); + + if ($this->request[self::REQUEST_CHECKSUM_HEADERS]['data']['invalid']) { + $this->request[self::REQUEST_CHECKSUM_HEADERS]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_CHECKSUM_HEADERS]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_CHECKSUM_HEADERS]['data']['invalid']); + + $this->request[self::REQUEST_CHECKSUM_HEADERS]['invalid'] = FALSE; + } + + /** + * Load and process the HTTP request parameter: checksum_content. + * + * The following format is expected: + * - 1*(atext):1*(atext):1*(atext) + * + * The header should have either 'partial:checksum_type:checksum_value' or 'complete:checksum_type:checksum_value'. + * The checksum_value is stored in base64. + * + * The content checksum represents the checksum of the HTTP content (HTTP packet body). + * This should already be defined if and when a checksum_header is used. + * + * This is not part of any official standard, but is added as an additional feature of this project. + * Unlike content_md5, this does not require additional headers. + * + * @see: self::p_parse_checksum() + */ + private function p_load_request_checksum_content() { + if (empty($this->headers['checksum_content'])) { + $this->request[self::REQUEST_CHECKSUM_CONTENT]['invalid'] = TRUE; + return; + } + + $this->request[self::REQUEST_CHECKSUM_CONTENT]['data'] = $this->p_parse_checksum($this->headers['checksum_content']); + + if ($this->request[self::REQUEST_CHECKSUM_CONTENT]['data']['invalid']) { + $this->request[self::REQUEST_CHECKSUM_CONTENT]['invalid'] = TRUE; + } + else { + $this->request[self::REQUEST_CHECKSUM_CONTENT]['defined'] = TRUE; + } + unset($this->request[self::REQUEST_CHECKSUM_CONTENT]['data']['invalid']); + + $this->request[self::REQUEST_CHECKSUM_CONTENT]['invalid'] = FALSE; + } + + /** + * Store raw values for fields that will not have specific parsing done to them. + * + * The 'raw' value will be made lower case and then trimmed. + * + * @param string $field + * The name of the field as it is defined in $this->headers. + * @param int $key + * The array key in which to store the data in. + * @param int $max_length + * The maximum length that should be allowed in the data. + */ + private function p_load_request_rawish($field, $key, $max_length = NULL) { + $raw = $this->headers[$field]; + if (empty($raw)) { + unset($raw); + return; + } + + $this->request[$key]['defined'] = TRUE; + $this->request[$key]['invalid'] = FALSE; + + if (is_null($max_length)) { + $this->request[$key]['data'] = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $raw)); + } + else { + $this->request[$key]['data'] = mb_substr(mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $raw)), 0, $max_length); + } + unset($raw); + } + + /** + * Store raw values for unknown fields + * + * This is identical to self::p_load_request_rawish() except the results are stored in an array. + * + * @param string $field + * The name of the field as it is defined in $this->headers. + * @param int $key + * The array key in which to store the data in. + * @param int $max_length + * The maximum length that should be allowed in the data. + */ + private function p_load_request_unknown($field, $key, $max_length = NULL) { + $raw = $this->headers[$field]; + if (empty($raw)) { + unset($raw); + return; + } + + $this->request[$key]['defined'] = TRUE; + $this->request[$key]['invalid'] = FALSE; + + if (is_null($max_length)) { + $this->request[$key]['data'][$field] = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $raw)); + } + else { + $this->request[$key]['data'][$field] = mb_substr(mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $raw)), 0, $max_length); + } + unset($raw); + } + + /** + * Validate that the given date string is in one of the supported rfc formats. + * + * This function is necessary because simply converting the date string to a timestamp via strtotime() allows to many other possibilities. + * To prevent unwanted dates, such as 'now', convert the passed timestamp into an rfd1123 into a date string. + * The converted timestamp and the original datestring must match exactly. + * + * @todo: review this function to see if any utf8 support needs to be added. + * + * @param string $original + * Tne original, unaltered, date string. + * + * @return bool + * TRUE on success, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.1.1 + * @see: https://tools.ietf.org/html/rfc5322#section-3.3 + */ + private function p_validate_date_is_valid_rfc($original) { + $timestamp = strtotime($original); + if ($timestamp === FALSE) { + unset($timestamp); + return FALSE; + } + + $raw = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $original)); + + // rfc5322 is the preferred/recommended format. + $rfc5322 = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', date(self::TIMESTAMP_RFC_5322, $timestamp))); + if ($raw == $rfc5322) { + unset($raw); + unset($timestamp); + unset($rfc5322); + return TRUE; + } + unset($rfc5322); + + $rfc1123 = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', date(self::TIMESTAMP_RFC_1123, $timestamp))); + if ($raw == $rfc1123) { + unset($raw); + unset($timestamp); + unset($rfc1123); + return TRUE; + } + unset($rfc1123); + + $rfc850 = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', date(self::TIMESTAMP_RFC_850, $timestamp))); + if ($raw == $rfc850) { + unset($raw); + unset($timestamp); + unset($rfc850); + return TRUE; + } + unset($rfc850); + + unset($raw); + unset($timestamp); + return FALSE; + } + + /** + * Process the accept header value sub parts. + * + * @param string $super + * The string that contains the value and the priority. + * @param string $value + * The value to be returned. + * @param string $priority + * The priority to be returned. + */ + private function p_process_accept_parts_sub($super, &$value, &$priority) { + $parts_sub = mb_split(self::DELIMITER_ACCEPT_SUB, $super); + + $part_sub_value = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $parts_sub[0])); + $part_sub_priority = NULL; + if (count($parts_sub) > 1) { + $part_sub_priority = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $parts_sub[1])); + } + + if (self::p_length_string($part_sub_value) > 2) { + if ($part_sub_value[0] == self::DELIMITER_ACCEPT_SUB_0 && $part_sub_value[1] == self::DELIMITER_ACCEPT_SUB_1) { + $value = $part_sub_priority; + $parts_sub_priority = $part_sub_value; + } + } + else { + $value = $part_sub_value; + } + unset($part_sub_value); + + if (!is_null($part_sub_priority) && self::p_length_string($part_sub_priority) > 2 && $part_sub_priority == 'q' && $part_sub_priority == '=') { + $part = preg_replace('/(^\s+)|(\s+$)/us', '', str_replace(self::DELIMITER_ACCEPT_SUB_0 . self::DELIMITER_ACCEPT_SUB_1, '', $part_sub_priority)); + + if (is_numeric($part)) { + $priority = sprintf('%.1f', (float) $part); + } + else { + $priority = '1.0'; + } + + unset($part); + } + else { + $priority = '1.0'; + } + + unset($part_sub_priority); + unset($parts_sub); + } + + /** + * Decode and check that the given uri is valid. + * + * This does not decode the uri, it separates it into its individual parts. + * + * Validation is done according to rfc3986. + * + * foo://example.com:8042/over/there?name=ferret#nose + * \_/ \______________/\_________/ \_________/ \__/ + * | | | | | + * scheme authority path query fragment + * | _____________________|__ + * / \ / \ + * urn:example:animal:ferret:nose + * + * + * @param string $uri + * The url to validate and decode. + * + * @return array + * A decoded uri split into its different parts inside an array. + * An array key called 'invalid' exists to designate that the uri is invalid. + * + * @see: https://tools.ietf.org/html/rfc3986 + */ + private function p_parse_uri($uri) { + $result = array( + 'scheme' => array(), + 'authority' => array(), + 'path' => array(), + 'query' => array(), + 'fragment' => array(), + 'invalid' => FALSE, + ); + + // @todo: completely rewrite below. + $result['invalid'] = TRUE; +/* + $matches = array(); + $matched = preg_match('!^((\w[=|\w|\d|\s|\.|-|_|~|%]*)*:)*([^#|?]*)(\?([^#]+))*(#(.+))*$!iu', $uri, $matches); + + if ($matched == FALSE || !array_key_exists(3, $matches)) { + unset($address); + unset($matches); + unset($matched); + $result['invalid'] = TRUE; + return $result; + } + unset($matched); + + + // process scheme. + if (array_key_exists(2, $matches) && self::p_length_string($matches[2]) > 0) { + $combined = $matches[3]; + if (array_key_exists(4, $matches)) { + $combined .= $matches[4]; + } + if (array_key_exists(6, $matches)) { + $combined .= $matches[6]; + } + + $scheme_string = preg_replace('!:' . $combined . '$!iu', '', $matches[0]); + $result['scheme'] = mb_split(':', $scheme_string); + unset($scheme_string); + unset($combined); + + foreach ($result['scheme'] as &$s) { + $s = urldecode($s); + } + unset($s); + } + + + // process authority. + if (self::p_length_string($matches[3]) > 0) { + // rfc6854 designates multiple uris, separated by commas. + $authority = mb_split(',', $matches[3]); + foreach ($authority as $a) { + $sub_matches = array(); + $sub_matched = preg_match('!^(//|/|)(([^@]+)@)*(.*)$!iu', $a, $sub_matches); + + if ($sub_matched === FALSE || !isset($sub_matches[4])) { + $result['invalid'] = TRUE; + + unset($sub_matches); + unset($sub_matched); + continue; + } + + + // process user information. + $information_matches = array(); + if (preg_match('@^([=|!|$|&|\'|(|)|\*|\+|,|;|\w|\d|-|\.|_|~|%|\s]*)(:|)$@iu', $sub_matches[3], $information_matches) === FALSE || !isset($information_matches[1])) { + $result['invalid'] = TRUE; + + unset($information_matches); + unset($sub_matches); + unset($sub_matched); + continue; + } + + $authority_setting = array( + 'type_path' => self::URI_PATH_THIS, + 'type_host' => 0, + 'user' => urldecode($information_matches[1]), + 'host' => NULL, + 'port' => NULL, + ); + unset($information_matches); + + + // process host information. + if ($sub_matches[1] == '//') { + $authority_setting['type_path'] = self::URI_PATH_SITE; + } + elseif ($sub_matches[1] == '/') { + $authority_setting['type_path'] = self::URI_PATH_BASE; + } + + $ipv6_matches = array(); + if (preg_match('@^\[([^\]]+)\](:\d+$|$)@iu', $sub_matches[4], $ipv6_matches) !== FALSE && isset($ipv6_matches[1])) { + $authority_setting['type_host'] = self::URI_HOST_IPV6; + + $ip = inet_pton($ipv6_matches[1]); + if ($ip === FALSE) { + $result['invalid'] = TRUE; + + unset($ip); + unset($ipv6_matches); + continue; + } + + $authority_setting['host'] = inet_ntop($ip); + unset($ip); + + if (isset($ipv6_matches[2]) && self::p_length_string($ipv6_matches[2]) > 0) { + $authority_setting['port'] = (int) $ipv6_matches[2]; + } + + // @todo: ipvfuture is actually embedded inside of the the double brackets used by ipv6. + // to support this, the ipv6 regex must be modified to check for the ipvfuture parameters. + // $authority_setting['type_host'] = self::URI_HOST_IPVX; + // '@v[\d|a|b|c|d|e|f]\.([=|!|$|&|\'|(|)|\*|\+|,|;|\w|\d|-|\.|_|~|%|:]*)@i' + } + unset($ipv6_matches); + + $ipv4_matches = array(); + if (is_null($authority_setting['host']) && preg_match('@(\d+\.\d+\.d+\.d+)(:(\d+)|)$@iu', $sub_matches[4], $ipv4_matches) !== FALSE && isset($ipv4_matches[1])) { + $authority_setting['type_host'] = self::URI_HOST_IPV4; + + $ip = inet_pton($ipv4_matches[1]); + if ($ip === FALSE) { + $result['invalid'] = TRUE; + + unset($ip); + unset($ipv4_matches); + continue; + } + + $authority_setting['host'] = inet_ntop($ip); + unset($ip); + + if (isset($ipv4_matches[3]) && self::p_length_string($ipv4_matches[3]) > 0) { + $authority_setting['port'] = (int) $ipv4_matches[3]; + } + } + unset($ipv4_matches); + + $ipv4_matches = array(); + if (is_null($authority_setting['host']) && preg_match('@(\d+\.\d+\.d+\.d+)(:(\d+)|)$@iu', $sub_matches[4], $ipv4_matches) !== FALSE && isset($ipv4_matches[1])) { + $authority_setting['type_host'] = self::URI_HOST_IPV4; + + $ip = inet_pton($ipv4_matches[1]); + if ($ip === FALSE) { + $result['invalid'] = TRUE; + + unset($ip); + unset($ipv4_matches); + continue; + } + + $authority_setting['host'] = inet_ntop($ip); + unset($ip); + + if (isset($ipv4_matches[3]) && self::p_length_string($ipv4_matches[3]) > 0) { + $authority_setting['port'] = (int) $ipv4_matches[3]; + } + } + unset($ipv4_matches); + + $name_matches = array(); + if (is_null($authority_setting['host']) && preg_match('@((=|\w|\d|-|\.|_|~|\!|$|&|\'|(|)|\*|\+|,|;)+)(:(\d+)|)$@iu', $sub_matches[4], $name_matches) !== FALSE && isset($name_matches[1])) { + $authority_setting['type_host'] = self::URI_HOST_NAME; + $authority_setting['host'] = $name_matches[2]; + + if (isset($name_matches[4]) && self::p_length_string($name_matches[4]) > 0) { + $authority_setting['port'] = (int) $name_matches[4]; + } + } + unset($name_matches); + + $result['authority'][] = $authority_setting; + + unset($authority_setting); + unset($sub_matches); + unset($sub_matched); + } + + unset($a); + unset($authority); + } + + + // process query. + if (array_key_exists(5, $matches) && self::p_length_string($matches[5]) > 0) { + $query_parts = mb_split(',', $matches[5]); + + foreach ($query_parts as $qp) { + $qp_parts = mb_split('=', $qp, 2); + + if (is_array($qp_parts) && isset($qp_parts[0])) { + $decoded = urldecode($qp_parts[0]); + if (isset($qp_parts[1])) { + $result['query'][$decoded] = urldecode($qp_parts[1]); + } + else { + $result['query'][$decoded] = NULL; + } + unset($decoded); + } + } + unset($qp); + unset($query_parts); + } + + + // process fragment. + if (array_key_exists(7, $matches) && self::p_length_string($matches[7]) > 0) { + $result['fragment'][] = urldecode($matches[7]); + } + + unset($matches); +*/ + return $result; + } + + /** + * Decode and check that the given string is a valid entity tag such as with if-match (but do not test for weakness). + * + * Validation is done according to rfc7232. + * + * @param string $match + * The string to validate and decode. + * + * @return array + * The processed information: + * - 'matches': An array of processed entity tags. + * - 'any': A boolean that when TRUE means any matches are allowed and the 'matches' key will be an empty array. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: https://tools.ietf.org/html/rfc7232 + */ + private function p_parse_if_entity_tag($match) { + $result = array( + 'matches' => array(), + 'any' => FALSE, + 'invalid' => FALSE, + ); + + $stop = self::p_length_string($match) + 1; + if ($stop == 0) { + unset($stop); + + $result['invalid'] = TRUE; + return $result; + } + + $text = $this->pr_rfc_string_prepare($match); + if ($text['invalid']) { + unset($stop); + unset($text); + + $result['invalid'] = TRUE; + return $result; + } + + $current = 0; + + // The standard specifies the use of a wildcard '*', but only accept wildcard if it is the only character (with no whitespace). + if ($stop == 1 && $text['ordinals'][$current] == c_base_ascii::ASTERISK) { + unset($stop); + unset($text); + unset($current); + + $result['any'] = TRUE; + return $result; + } + + while ($current < $stop) { + $parsed = $this->pr_rfc_string_is_quoted_string($text['ordinals'], $text['characters'], $current, self::STOP_AT_CLOSING_CHARACTER); + $current = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + $result['matches'][] = $parsed['text']; + + // The standard does not immediately define what the rules are for a list of entity tags, but they do imply comma separated. + // therefore, until I learn the specifics, I am assuming that only FWS and comma are allowed. + $current++; + while ($current < $stop) { + if ($text['ordinals'][$current] == c_base_ascii::COMMA) { + $current++; + break; + } + + if (!$this->pr_rfc_char_is_fws($text['ordinals'][$current])) { + $result['invalid'] = TRUE; + break; + } + + $current++; + } + + if ($result['invalid']) { + break; + } + } + unset($current); + unset($stop); + unset($text); + + return $result; + } + + /** + * Decode and check for weak or strong entity tag matches, such as with: if-none-match or if-range. + * + * Validation is done according to rfc7232. + * + * @param string $match + * The string to validate and decode. + * + * @return array + * The processed information: + * - 'matches': An array of processed entity tags. + * - 'weak': An array of booleans defining whether the corrosponding match in the matches array is weak or not. + * - 'any': A boolean that when TRUE means any matches are allowed and the 'matches' key will be an empty array. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: https://tools.ietf.org/html/rfc7232 + */ + private function p_parse_if_entity_tag_and_weak($match) { + $result = array( + 'matches' => array(), + 'weak' => array(), + 'any' => FALSE, + 'invalid' => FALSE, + ); + + $stop = self::p_length_string($match) + 1; + if ($stop == 0) { + unset($stop); + + $result['invalid'] = TRUE; + return $result; + } + + $text = $this->pr_rfc_string_prepare($match); + if ($text['invalid']) { + unset($stop); + unset($text); + + $result['invalid'] = TRUE; + return $result; + } + + $current = 0; + + // The standard specifies the use of a wildcard '*', but only accept wildcard if it is the only character (with no whitespace). + if ($stop == 1 && $text['ordinals'][$current] == c_base_ascii::ASTERISK) { + unset($stop); + unset($text); + unset($current); + + $result['any'] = TRUE; + return $result; + } + + while ($current < $stop) { + $weak = FALSE; + if ($text['ordinals'][$current] == c_base_ascii::W) { + $current++; + if ($current >= $stop) { + $result['invalid'] = TRUE; + break; + } + + if ($text['ordinals'][$current] == c_base_ascii::SLASH_FORWARD) { + $weak = TRUE; + $current++; + continue; + } + + $result['invalid'] = TRUE; + break; + } + elseif ($text['ordinals'][$current] == c_base_ascii::QUOTE_DOUBLE) { + $current++; + if ($current >= $stop) { + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_entity_tag($text['ordinals'], $text['characters'], $current, $stop); + $current = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + $result['matches'][] = $parsed['text']; + $result['weak'][] = $weak; + + // handle comma separated values. + $current++; + while ($current < $stop) { + if ($text['ordinals'][$current] == c_base_ascii::COMMA) { + $current++; + + // seek past all WS. + while ($current < $stop) { + if (!$this->pr_rfc_char_is_wsp($text['ordinals'][$current])) { + break; + } + + $current++; + } + + break; + } + + if (!$this->pr_rfc_char_is_wsp($text['ordinals'][$current])) { + $result['invalid'] = TRUE; + break; + } + + $current++; + } + + if ($result['invalid']) { + break; + } + } + else { + $result['invalid'] = TRUE; + break; + } + } + unset($current); + unset($stop); + unset($text); + unset($weak); + + return $result; + } + + /** + * Decode and check for weak or strong entity tag matches, such as with: user-agent. + * + * The user agent structure very poorly designed and provides no straight-forward and consistent way to present information. + * The approach used here is a quick and simply approach that is intended to be good enough. + * Do not expect it to be completely accuarete. + * + * Being to detailed on agents can cause significant performance penalties that has in the past forced me to implement this particular design. + * + * @param string $match + * The string to validate and decode. + * + * @return array + * The processed information: + * - 'full': The entire agent string. + * - 'name_machine': a machine-friendly name for the client, or null if undefined or unknown. + * - 'name_human': a human-friendly name for the client, or null if undefined or unknown. + * - 'engine_name_machine': a machine-friendly name for the client's engine, or null if undefined or unknown. + * - 'engine_name_human': a human-friendly name for the client's engine, or null if undefined or unknown. + * - 'version_major': A major version number of the client, or null if undefined or unknown. + * - 'version_engine': A major version number of the client's engine, or null if undefined or unknown. + * - 'is_ie_edge': a boolean representing whether or not this is IE Edge. + * - 'is_ie_compatibility': a boolean representing whether or not this is IE in "compatibility mode". + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: https://tools.ietf.org/html/rfc7232 + */ + private function p_parse_user_agent($agent) { + $result = array( + 'full' => $agent, + 'name_machine' => NULL, + 'name_human' => NULL, + 'engine_name_machine' => NULL, + 'engine_name_human' => NULL, + 'version_major' => NULL, + 'version_engine' => NULL, + 'is_ie_edge' => FALSE, + 'is_ie_compatibility' => FALSE, + 'invalid' => FALSE, + ); + + $agent_matches = array(); + $agent_matched = preg_match('/^[^(]*\(([^)]*)\)(.*)$/iu', $agent, $agent_matches); + + if (!$agent_matched) { + $result['invalid'] = TRUE; + unset($agent_matches); + unset($agent_matched); + return $result; + } + unset($agent_matched); + + if (!isset($agent_matches[1])) { + unset($agent_matches); + return $result; + } + + + // preprocess the agent in an attempt to determine the engine and therefore basic information. + $agent_pieces = mb_split(';', $agent_matches[1]); + + if (empty($agent_pieces)) { + unset($agent_pieces); + unset($agent_matches); + return $result; + } + + foreach ($agent_pieces as $agent_piece) { + $pieces = mb_split('/', $agent_piece); + + // ignore unknown structure. + if (count($pieces) > 2) { + continue; + } + + if (isset($pieces[1])) { + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[1])); + + if ($lower_piece_1 == 'trident') { + $result['engine_name_machine'] = 'trident'; + $result['engine_name_human'] = 'Trident'; + $result['version_engine'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + + $result['name_machine'] = 'ie'; + $result['name_human'] = 'Internet Explorer'; + } + elseif ($lower_piece_1 == 'gecko') { + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + $result['version_engine'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + elseif ($lower_piece_1 == 'presto') { + $result['engine_name_machine'] = 'presto'; + $result['engine_name_human'] = 'Presto'; + $result['version_engine'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + unset($lower_piece_1); + unset($lower_piece_2); + } + elseif (isset($pieces[0])) { + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + + if (!empty($lower_piece_1)) { + if (preg_match('/^msie \d/iu', $lower_piece_1)) { + $lower_piece_2 = preg_replace('/^msie /iu', '', $lower_piece_1); + + $result['name_machine'] = 'ie'; + $result['name_human'] = 'Internet Explorer'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + elseif (strpos($lower_piece_1, 'midori')) { + $result['name_machine'] = 'midori'; + $result['name_human'] = 'Midori'; + $result['engine_name_machine'] = 'webkit'; + } + else { + // Browsers, such as Internet Explorer, use 'rv:number', such as: 'rv:11'. + $revision_parts = explode(':', $lower_piece_1); + if (count($revision_parts) == 2 && $revision_parts[0] == 'rv' && is_numeric($revision_parts[1])) { + if (is_null($result['version_major'])) { + $result['version_major'] = (int) $revision_parts[1]; + } + } + unset($revision_parts); + } + } + + unset($lower_piece_1); + } + } + unset($pieces); + unset($agent_pieces); + + + // determine the client agent information. + if (isset($agent_matches[2])) { + $agent_pieces = mb_split('\s', $agent_matches[2]); + + if (!empty($agent_pieces)) { + foreach ($agent_pieces as $agent_piece) { + $pieces = mb_split('/', $agent_piece); + + // ignore unknown structure. + if (count($pieces) > 3) { + continue; + } + + if (isset($pieces[1])) { + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[1])); + + if ($lower_piece_1 == 'applewebkit') { + $result['engine_name_machine'] = 'webkit'; + $result['engine_name_human'] = 'Webkit'; + $result['version_engine'] = (int) $lower_piece_2; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + elseif ($lower_piece_1 == 'safari') { + // safari is used in a lot of places that is not safari, so use safari only if it is the only agent detected. + if (is_null($result['name_machine'])) { + $result['name_machine'] = 'safari'; + $result['name_human'] = 'Safari'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + if (is_null($result['engine_name_machine'])) { + $result['engine_name_machine'] = 'webkit'; + $result['engine_name_human'] = 'Webkit'; + } + } + } + elseif ($lower_piece_1 == 'firefox') { + $result['name_machine'] = 'firefox'; + $result['name_human'] = 'Firefox'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + } + elseif ($lower_piece_1 == 'seamonkey') { + $result['name_machine'] = 'seamonkey'; + $result['name_human'] = 'Seamonkey'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + } + elseif ($lower_piece_1 == 'gecko') { + if (is_null($result['version_engine']) && (is_null($result['engine_name_machine']) || $result['engine_name_machine'] == 'gecko')) { + $result['version_engine'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + } + } + elseif ($lower_piece_1 == 'chrome') { + // the newer internet explorer uses safari/webkit based agent names, assign chrome conditionally. + if (is_null($result['name_machine']) || $result['name_machine'] == 'safari') { + $result['name_machine'] = 'chrome'; + $result['name_human'] = 'Google Chrome'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + } + elseif ($lower_piece_1 == 'chromium') { + $result['name_machine'] = 'chrome'; + $result['name_human'] = 'Google Chrome'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + elseif ($lower_piece_1 == 'epiphany') { + $result['name_machine'] = 'epiphany'; + $result['name_human'] = 'Ephiphany'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + if (is_null($result['engine_name_machine'])) { + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + } + } + elseif ($lower_piece_1 == 'konqueror') { + $result['name_machine'] = 'konqueror'; + $result['name_human'] = 'Konqueror'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + if (is_null($result['engine_name_machine'])) { + $result['engine_name_machine'] = 'gecko'; + $result['engine_name_human'] = 'Gecko'; + } + } + elseif ($lower_piece_1 == 'khtml') { + $result['name_machine'] = 'konqueror'; + $result['name_human'] = 'Konqueror'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + elseif ($lower_piece_1 == 'opr') { + $result['name_machine'] = 'opera'; + $result['name_human'] = 'Opera'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + if (is_null($result['engine_name_machine'])) { + $result['engine_name_machine'] = 'presto'; + $result['engine_name_human'] = 'Presto'; + } + } + elseif ($lower_piece_1 == 'edge') { + $result['name_machine'] = 'ie'; + $result['name_human'] = 'Internet Explorer'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + + $result['is_ie_edge'] = TRUE; + } + elseif ($lower_piece_1 == 'midori') { + $result['name_machine'] = 'midori'; + $result['name_human'] = 'Midori'; + + if (is_null($result['version_major'])) { + $result['version_major'] = (int) preg_replace('/\..*$/iu', '', $lower_piece_2); + } + } + + unset($lower_piece_1); + unset($lower_piece_2); + } + elseif (isset($pieces[0])) { + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + + if ($lower_piece_1 == 'opera') { + $result['name_machine'] = 'opera'; + $result['name_human'] = 'Opera'; + + if (is_null($result['engine_name_machine'])) { + $result['engine_name_machine'] = 'presto'; + $result['engine_name_human'] = 'Presto'; + } + } + elseif ($lower_piece_1 == '(khtml,') { + // khtml is used in a lot of places that is not safari, so use only when necessary. + if (is_null($result['engine_name_machine']) || $result['name_machine'] == 'epiphany' || $result['name_machine'] == 'konqueror') { + $result['engine_name_machine'] = 'webkit'; + $result['engine_name_human'] = 'Webkit'; + } + + if (is_null($result['name_machine'])) { + $result['name_machine'] = 'safari'; + $result['name_human'] = 'Safari'; + } + } + unset($lower_piece_1); + } + } + unset($pieces); + } + unset($agent_pieces); + } + unset($agent_matches); + + + // attempt to determine internet explorer versions if not already found. + if ($result['engine_name_machine'] == 'trident' && (is_null($result['name_machine']) || ($result['name_machine'] == 'ie' && is_null($result['version_major'])))) { + $result['name_machine'] = 'ie'; + $result['human_name'] = 'Internet Explorer'; + + if (isset($result['is_ie_edge'])) { + $result['version_major'] = 12; + } + elseif ($result['version_engine'] == 7) { + $result['version_major'] = 11; + } + elseif ($result['version_engine'] == 6) { + $result['version_major'] = 10; + } + elseif ($result['version_engine'] == 5) { + $result['version_major'] = 9; + } + elseif ($result['version_engine'] == 4) { + $result['version_major'] = 8; + } + } + + + // detect internet explorers compatibility mode (for old versions) where possible to allow clients to better handle. + if ($result['name_machine'] == 'ie') { + if ($result['version_major'] <= 8) { + if ($result['version_major'] == 7) { + if ($result['engine_name_machine'] == 'trident') { + $result['is_ie_compatibility'] = TRUE; + } + } + } + + // alter the (faked) agent version to properly reflect the current browser. + if ($result['is_ie_compatibility'] && isset($result['version_engine'])) { + if (isset($result['is_ie_edge'])) { + $result['version_major'] = 12; + } + elseif ($result['version_engine'] == 7) { + $result['version_major'] = 11; + } + elseif ($result['version_engine'] == 6) { + $result['version_major'] = 10; + } + elseif ($result['version_engine'] == 5) { + $result['version_major'] = 9; + } + elseif ($result['version_engine'] == 4) { + $result['version_major'] = 8; + } + elseif (preg_match("/; EIE10;/iu", $agent) > 0) { + $result['version_major'] = 10; + } + } + + // added later on to allow for compatibility mode tests to be properly processed. + $result['engine_name_machine'] = 'trident'; + $result['engine_name_human'] = 'Trident'; + } + + + // if the agent wasn't identified, check to see if this is a bot or a known command line tool. + if (is_null($result['engine_name_machine'])) { + $agent_matches = array(); + preg_match('/^([^(]+)/iu', $agent, $agent_matches); + + if (isset($agent_matches[0])) { + $pieces = mb_split('/', $agent_matches[0]); + $total_pieces = count($pieces); + if ($total_pieces == 2) { + $lower_piece_1 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + $lower_piece_2 = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[1])); + + if ($lower_piece_1 == 'curl') { + $result['engine_name_machine'] = 'curl'; + $result['engine_name_human'] = 'Curl'; + + if (preg_match('/^(\d|\.)+$/iu', $lower_piece_2) > 0) { + $result['version_engine'] = $lower_piece_2; + } + else { + $result['version_engine'] = 0; + } + + $result['name_machine'] = 'curl'; + $result['human_name'] = 'Curl'; + $result['version_major'] = (int) $lower_piece_2; + } + elseif ($lower_piece_1 == 'wget') { + $result['engine_name_machine'] = 'wget'; + $result['engine_name_human'] = 'WGet'; + + if (preg_match('/^(\d|\.)+$/iu', $lower_piece_2) > 0) { + $result['version_engine'] = $lower_piece_2; + } + else { + $result['version_engine'] = 0; + } + + $result['name_machine'] = 'wget'; + $result['human_name'] = 'WGet'; + $result['version_major'] = (int) $lower_piece_2; + } + elseif ($lower_piece_1 == 'elinks') { + $result['engine_name_machine'] = 'elinks'; + $result['engine_name_human'] = 'Elimks'; + + if (preg_match('/^(\d|\.)+$/iu', $lower_piece_2) > 0) { + $result['version_engine'] = $lower_piece_2; + } + else { + $result['version_engine'] = 0; + } + + $result['name_machine'] = 'elinks'; + $result['human_name'] = 'Elimks'; + $result['version_major'] = (int) $lower_piece_2; + } + elseif ($lower_piece_1 == 'lynx') { + $result['engine_name_machine'] = 'lynx'; + $result['engine_name_human'] = 'Lynx'; + + if (preg_match('/^(\d|\.)+$/iu', $lower_piece_2) > 0) { + $result['version_engine'] = $lower_piece_2; + } + else { + $result['version_engine'] = 0; + } + + $result['name_machine'] = 'lynx'; + $result['human_name'] = 'Lynx'; + $result['version_major'] = (int) $lower_piece_2; + } + + unset($lower_piece_1); + unset($lower_piece_2); + } + elseif ($total_pieces == 1) { + $lower_piece = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($pieces[0])); + + if ($lower_piece == 'links') { + $result['engine_name_machine'] = 'links'; + $result['engine_name_human'] = 'Links'; + $result['name_machine'] = 'links'; + $result['human_name'] = 'Links'; + } + + unset($lower_piece); + } + unset($pieces); + unset($total_pieces); + } + unset($agent_matches); + } + + return $result; + } + + /** + * Load and process the HTTP request parameter: checksum_content. + * + * The following format is expected: + * - 1*(atext):1*(atext):1*(atext) + * + * The header should have either 'partial:checksum_type:checksum_value' or 'complete:checksum_type:checksum_value'. + * The checksum_value is stored in base64 and will be decoded by this function. + * + * This is not part of any official standard, but is added as an additional feature of this project. + * Unlike content_md5, this does not require additional headers. + * + * @param string $checksum + * The checksum string to validate and decode. + * + * @return array + * The processed information: + * - 'what': A specific way in which to interpret the checksum, currently either: 'partial' or 'full'. + * - 'type': The type of the checksum, such as 'sha256'. + * - 'checksum': The checksum value after it has been base64 decoded. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + */ + private function p_parse_checksum($checksum) { + $result = array( + 'what' => NULL, + 'type' => NULL, + 'checksum' => NULL, + 'invalid' => FALSE, + ); + + $fixed_checksum = mb_strtolower(preg_replace('/(^\s+)|(\s+$)/us', '', $checksum)); + if (empty($fixed_checksum)) { + $result['invalid'] = TRUE; + unset($fixed_checksum); + return $result; + } + + $parts = mb_split(':', $fixed_checksum); + unset($fixed_checksum); + if (count($parts) != 3) { + $result['invalid'] = TRUE; + unset($parts); + return $result; + } + + + // process the partial/complete option. + $text = $this->pr_rfc_string_prepare($parts[0]); + if ($text['invalid']) { + $result['invalid'] = TRUE; + unset($text); + unset($parts); + return $result; + } + + $parsed = $this->pr_rfc_string_is_atext($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + unset($parts); + return $result; + } + + if ($parsed['text'] == 'partial') { + $result['what'] = self::CHECKSUM_WHAT_FULL; + } + elseif ($parsed['text'] == 'complete') { + $result['what'] = self::CHECKSUM_WHAT_COMPLETE; + } + else { + $result['invalid'] = TRUE; + unset($parsed); + unset($parts); + return $result; + } + unset($parsed); + + + // process the checksum option. + $text = $this->pr_rfc_string_prepare($parts[1]); + if ($text['invalid']) { + $result['invalid'] = TRUE; + unset($text); + unset($parts); + return $result; + } + + $parsed = $this->pr_rfc_string_is_atext($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + unset($parts); + return $result; + } + + if ($parsed['text'] == 'md2') { + $result['type'] = self::CHECKSUM_MD2; + } + elseif ($parsed['text'] == 'md4') { + $result['type'] = self::CHECKSUM_MD4; + } + elseif ($parsed['text'] == 'md5') { + $result['type'] = self::CHECKSUM_MD5; + } + elseif ($parsed['text'] == 'sha1') { + $result['type'] = self::CHECKSUM_SHA1; + } + elseif ($parsed['text'] == 'sha224') { + $result['type'] = self::CHECKSUM_SHA224; + } + elseif ($parsed['text'] == 'sha256') { + $result['type'] = self::CHECKSUM_SHA256; + } + elseif ($parsed['text'] == 'sha384') { + $result['type'] = self::CHECKSUM_SHA384; + } + elseif ($parsed['text'] == 'sha512') { + $result['type'] = self::CHECKSUM_SHA512; + } + elseif ($parsed['text'] == 'crc32') { + $result['type'] = self::CHECKSUM_CRC32; + } + else { + $result['invalid'] = TRUE; + unset($parsed); + unset($parts); + return $result; + } + unset($parsed); + + + // process the checksum value. + $text = $this->pr_rfc_string_prepare($parts[1]); + unset($parts); + if ($text['invalid']) { + $result['invalid'] = TRUE; + unset($text); + return $result; + } + + $parsed = $this->pr_rfc_string_is_atext($text['ordinals'], $text['characters']); + unset($text); + + $result['checksum'] = base64_decode($parsed['text']); + unset($parsed); + if ($result['checksum'] === FALSE || empty($result['checksum'])) { + $result['invalid'] = TRUE; + return $result; + } + + return $result; + } + + /** + * Load and process the HTTP request parameter: checksum_headers. + * + * The following format is expected: + * - 1*(atext)*(*(wsp) "," *(wsp)1*(atext)) + * + * The headers is a comma separated list of http headers present at the time the header checksum was generated. + * This is necessary because anything en-route may add or alter headers and the checksum needs to still validate for the checksum provided. + * This also gives the client or server and idea on what was added and what was not. + * + * @param string $checksum_headers + * The checksum headers string to validate and decode. + * + * @return array + * The processed information: + * - 'headers': An array containing (all checksum header names are forced to become lower case): + * - 'known': An array of checksum header strings whose keys are the header ids. + * - 'unknown': An array of checksum header strings whose keys are numerically sorted. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + */ + private function p_parse_checksum_headers($checksum_headers) { + $result = array( + 'headers' => array( + 'known' => array(), + 'unknown' => array(), + ), + 'invalid' => FALSE, + ); + + $fixed_checksum = mb_strtolower(preg_replace("/(^( |\t)+)|(( |\t)+$)/us", '', $checksum_headers)); + if (empty($fixed_checksum)) { + $result['invalid'] = TRUE; + unset($fixed_checksum); + return $result; + } + + $parts = mb_split(',', $fixed_checksum); + unset($fixed_checksum); + + if (empty($parts)) { + // this is not an error, it simply means that no headers are associated with the header checksum (effectively making the header checksum pointless). + return $result; + } + + $mapping_headers = $this->p_get_header_request_mapping(); + foreach ($parts as $part) { + // strip out leading or trailing whitespace. + $sanitized = preg_replace("/(^( |\t)+)|(( |\t)+$)/us", '', $part); + + $text = $this->pr_rfc_string_prepare($sanitized); + unset($sanitized); + if ($text['invalid']) { + $result['invalid'] = TRUE; + unset($text); + unset($parts); + unset($part); + unset($mapping_headers); + return $result; + } + + $parsed = $this->pr_rfc_string_is_atext($text['ordinals'], $text['characters']); + unset($text); + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + unset($parts); + unset($part); + unset($mapping_headers); + return $result; + } + + if (array_key_exists($parsed['text'], $mapping_headers)) { + $result['headers']['known'][$mapping_headers[$parsed['text']]] = $parsed['text']; + } + else { + $result['headers']['unknown'][] = $parsed['text']; + } + unset($parsed); + } + unset($parts); + unset($part); + unset($mapping_headers); + + return $result; + } + + /** + * Simplify handling of the multibyte string length processing. + * + * @param string $string + * The string to get the length of. + * + * @return int + * The string length. + * 0 is returned on any error. + */ + private function p_length_string($string) { + $length = c_base_utf8::s_length_string($string); + if ($length instanceof c_base_return_false) { + unset($length); + return 0; + } + + return $length->get_value_exact(); + } + + /** + * Obtain all headers whether or not apache is used. + * + * One of the problems is that PHP will clobber the request headers in the following ways: + * - make them all uppercase. + * - Replace '-' with '_'. + * + * The uppercase situation is not a problem, they just need to be all lowercased. + * The '-' to '_' is a problem because the '_' may be the intended functionality. + * + * There are also some values stored in $_SERVER that may be useful, so additional fields may be created and used. + * - These will be prefixed with 'environment-'. + * + * @fixme: do something about converting '_' to '-'. + */ + private function p_get_all_headers() { + $this->headers = array(); + + // this works with apache. + if (function_exists('getallheaders')) { + $all_headers = getallheaders(); + + foreach ($all_headers as $key => $value) { + // break the header name so that it is consistent until such time that PHP stops clobbering the header names. + $broken = preg_replace('/-/u', '_', $key); + $this->headers[mb_strtolower($broken)] = $value; + unset($broken); + } + unset($broken); + unset($key); + unset($value); + } + else { + // non-apache, or calling php from command line. + if (isset($_SERVER) && is_array($_SERVER) && !empty($_SERVER)) { + foreach ($_SERVER as $key => $value) { + $part = mb_strtolower(mb_substr($key, 0, 5)); + + if ($part != 'http_') { + continue; + } + + $part = mb_strtolower(mb_substr($key, 5)); + $this->headers[$part] = $value; + } + unset($part); + unset($key); + unset($value); + } + } + + if (isset($_SERVER) && is_array($_SERVER) && !empty($_SERVER)) { + // find and process potentially useful additional environment variables. + if (array_key_exists('REQUEST_TIME_FLOAT', $_SERVER)) { + $this->request_time = $_SERVER['REQUEST_TIME_FLOAT']; + } + elseif (array_key_exists('REQUEST_TIME', $_SERVER)) { + $this->request_time = $_SERVER['REQUEST_TIME']; + } + } + + if (is_null($this->request_time)) { + $this->request_time = microtime(TRUE); + } + } + + /** + * Return an array for mapping HTTP request header strings to header ids. + * + * @return array + * An array for mapping HTTP request header strings to header ids. + */ + private function p_get_header_request_mapping() { + return array( + 'accept' => self::REQUEST_ACCEPT, + 'accept-charset' => self::REQUEST_ACCEPT_CHARSET, + 'accept-encoding' => self::REQUEST_ACCEPT_ENCODING, + 'accept-language' => self::REQUEST_ACCEPT_LANGUAGE, + 'accept-datetime' => self::REQUEST_ACCEPT_DATETIME, + 'access-control-request-method' => self::REQUEST_ACCESS_CONTROL_REQUEST_METHOD, + 'access-control-request-headers' => self::REQUEST_ACCESS_CONTROL_REQUEST_HEADERS, + 'authorization' => self::REQUEST_AUTHORIZATION, + 'cache-control' => self::REQUEST_CACHE_CONTROL, + 'connection' => self::REQUEST_CONNECTION, + 'cookie' => self::REQUEST_COOKIE, + 'content-length' => self::REQUEST_CONTENT_LENGTH, + 'content-type' => self::REQUEST_CONTENT_TYPE, + 'date' => self::REQUEST_DATE, + 'expect' => self::REQUEST_EXPECT, + 'from' => self::REQUEST_FROM, + 'host' => self::REQUEST_HOST, + 'if-match' => self::REQUEST_IF_MATCH, + 'if-modified-since' => self::REQUEST_IF_MODIFIED_SINCE, + 'if-none-match' => self::REQUEST_IF_NONE_MATCH, + 'if-range' => self::REQUEST_IF_RANGE, + 'if-unmodified-since' => self::REQUEST_IF_UNMODIFIED_SINCE, + 'max-forwards' => self::REQUEST_MAX_FORWARDS, + 'origin' => self::REQUEST_ORIGIN, + 'pragma' => self::REQUEST_PRAGMA, + 'proxy-authorization' => self::REQUEST_PROXY_AUTHORIZATION, + 'request-range' => self::REQUEST_RANGE, + 'referer' => self::REQUEST_REFERER, + 'te' => self::REQUEST_TE, + 'user-agent' => self::REQUEST_USER_AGENT, + 'upgrade' => self::REQUEST_UPGRADE, + 'via' => self::REQUEST_VIA, + 'warning' => self::REQUEST_WARNING, + 'x-requested-with' => self::REQUEST_X_REQUESTED_WITH, + 'x-forwarded-for' => self::REQUEST_X_FORWARDED_FOR, + 'x-forwarded-host' => self::REQUEST_X_FORWARDED_HOST, + 'x-forwarded-proto' => self::REQUEST_X_FORWARDED_PROTO, + 'checksum_header' => self::REQUEST_CHECKSUM_HEADER, + 'checksum_headers' => self::REQUEST_CHECKSUM_HEADERS, + 'checksum_content' => self::REQUEST_CHECKSUM_CONTENT, + ); + } + + /** + * Return an array for mapping HTTP response header ids to header strings. + * + * Note: self::RESPONSE_PROTOCOL is not provided here because it is included in self::RESPONSE_STATUS. + * + * @param bool $case_first + * (optional) When TRUE, the first character of each word will be capitalized. + * The internal code uses lower case for processing, so in those cases, this should be set to FALSE. + * Many clients do this with HTTP headers, so to reduce the ability to fingerprint this project, one should set this to TRUE for HTTP responses. + * + * @return array + * An array for mapping HTTP response header strings to header ids. + */ + private function p_get_header_response_mapping($case_first = FALSE) { + if ($case_first) { + return array( + self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN => 'Access-Control-Allow-Origin', + self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS => 'Access-Control-Allow-Credentials', + self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS => 'Access-Control-Expose-Headers', + self::RESPONSE_ACCESS_CONTROL_MAX_AGE => 'Access-Control-Max-Age', + self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS => 'Access-Control-Allow-Methods', + self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS => 'Access-Control-Allow-Headers', + self::RESPONSE_ACCEPT_PATCH => 'Accept-Patch', + self::RESPONSE_ACCEPT_RANGES => 'Accept-Ranges', + self::RESPONSE_AGE => 'Age', + self::RESPONSE_ALLOW => 'Allow', + self::RESPONSE_CACHE_CONTROL => 'Cache-Control', + self::RESPONSE_CONNECTION => 'Connection', + self::RESPONSE_CONTENT_DISPOSITION => 'Content-Disposition', + self::RESPONSE_CONTENT_ENCODING => 'Content-Encoding', + self::RESPONSE_CONTENT_LANGUAGE => 'Content-Language', + self::RESPONSE_CONTENT_LENGTH => 'Content-Length', + self::RESPONSE_CONTENT_LOCATION => 'Content-Location', + self::RESPONSE_CONTENT_RANGE => 'Content-Range', + self::RESPONSE_CONTENT_TYPE => 'Content-Type', + self::RESPONSE_DATE => 'Date', + self::RESPONSE_DATE_ACTUAL => 'Date_Actual', + self::RESPONSE_ETAG => 'Etag', + self::RESPONSE_EXPIRES => 'Expires', + self::RESPONSE_LAST_MODIFIED => 'Last-Modified', + self::RESPONSE_LINK => 'Link', + self::RESPONSE_LOCATION => 'Location', + self::RESPONSE_PRAGMA => 'Pragma', + self::RESPONSE_PROXY_AUTHENTICATE => 'Proxy-Authenticate', + self::RESPONSE_PUBLIC_KEY_PINS => 'Public-Key-Pins', + self::RESPONSE_REFRESH => 'Refresh', + self::RESPONSE_RETRY_AFTER => 'Retry-After', + self::RESPONSE_SERVER => 'Server', + self::RESPONSE_SET_COOKIE => 'Set-Cookie', + self::RESPONSE_STATUS => 'Status', + self::RESPONSE_STRICT_TRANSPORT_SECURITY => 'Strict-Transport-Security', + self::RESPONSE_TRAILER => 'Trailer', + self::RESPONSE_TRANSFER_ENCODING => 'Transfer-Encoding', + self::RESPONSE_UPGRADE => 'Upgrade', + self::RESPONSE_VARY => 'Vary', + self::RESPONSE_WARNING => 'Warning', + self::RESPONSE_WWW_AUTHENTICATE => 'Www-Authenticate', + self::RESPONSE_X_CONTENT_SECURITY_POLICY => 'X-Content-Security-Policy', + self::RESPONSE_X_CONTENT_TYPE_OPTIONS => 'X-Content-Type-Options', + self::RESPONSE_X_UA_COMPATIBLE => 'X-UA-Compatible', + self::RESPONSE_CHECKSUM_HEADER => 'Checksum_Header', + self::RESPONSE_CHECKSUM_HEADERS => 'Checksum_Headers', + self::RESPONSE_CHECKSUM_CONTENT => 'Checksum_Content', + self::RESPONSE_CONTENT_REVISION => 'Content_Revision', + ); + } + + return array( + self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN => 'access-control-allow-origin', + self::RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS => 'access-control-allow-credentials', + self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS => 'access-control-expose-headers', + self::RESPONSE_ACCESS_CONTROL_MAX_AGE => 'access-control-max-age', + self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS => 'access-control-allow-methods', + self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS => 'access-control-allow-headers', + self::RESPONSE_ACCEPT_PATCH => 'accept-patch', + self::RESPONSE_ACCEPT_RANGES => 'accept-ranges', + self::RESPONSE_AGE => 'age', + self::RESPONSE_ALLOW => 'allow', + self::RESPONSE_CACHE_CONTROL => 'cache-control', + self::RESPONSE_CONNECTION => 'connection', + self::RESPONSE_CONTENT_DISPOSITION => 'content-disposition', + self::RESPONSE_CONTENT_ENCODING => 'content-encoding', + self::RESPONSE_CONTENT_LANGUAGE => 'content-language', + self::RESPONSE_CONTENT_LENGTH => 'content-length', + self::RESPONSE_CONTENT_LOCATION => 'content-location', + self::RESPONSE_CONTENT_RANGE => 'content-range', + self::RESPONSE_CONTENT_TYPE => 'content-type', + self::RESPONSE_DATE => 'date', + self::RESPONSE_DATE_ACTUAL => 'date_actual', + self::RESPONSE_ETAG => 'etag', + self::RESPONSE_EXPIRES => 'expires', + self::RESPONSE_LAST_MODIFIED => 'last-modified', + self::RESPONSE_LINK => 'link', + self::RESPONSE_LOCATION => 'location', + self::RESPONSE_PRAGMA => 'pragma', + self::RESPONSE_PROXY_AUTHENTICATE => 'proxy-authenticate', + self::RESPONSE_PUBLIC_KEY_PINS => 'public-key-pins', + self::RESPONSE_REFRESH => 'refresh', + self::RESPONSE_RETRY_AFTER => 'retry-after', + self::RESPONSE_SERVER => 'server', + self::RESPONSE_SET_COOKIE => 'set-cookie', + self::RESPONSE_STATUS => 'status', + self::RESPONSE_STRICT_TRANSPORT_SECURITY => 'strict-transport-security', + self::RESPONSE_TRAILER => 'trailer', + self::RESPONSE_TRANSFER_ENCODING => 'transfer-encoding', + self::RESPONSE_UPGRADE => 'upgrade', + self::RESPONSE_VARY => 'vary', + self::RESPONSE_WARNING => 'warning', + self::RESPONSE_WWW_AUTHENTICATE => 'www-authenticate', + self::RESPONSE_X_CONTENT_SECURITY_POLICY => 'x-content-security-policy', + self::RESPONSE_X_CONTENT_TYPE_OPTIONS => 'x-content-type-options', + self::RESPONSE_X_UA_COMPATIBLE => 'x-ua-compatible', + self::RESPONSE_CHECKSUM_HEADER => 'checksum_header', + self::RESPONSE_CHECKSUM_HEADERS => 'checksum_headers', + self::RESPONSE_CHECKSUM_CONTENT => 'checksum_content', + self::RESPONSE_CONTENT_REVISION => 'content_revision', + ); + } + + /** + * Return an array for mapping HTTP method strings to numeric ids. + * + * @return array + * An array for mapping HTTP response header strings to header ids. + */ + private function p_get_http_method_mapping() { + return array( + 'get' => self::HTTP_METHOD_GET, + 'head' => self::HTTP_METHOD_HEAD, + 'post' => self::HTTP_METHOD_POST, + 'put' => self::HTTP_METHOD_PUT, + 'delete' => self::HTTP_METHOD_DELETE, + 'trace' => self::HTTP_METHOD_TRACE, + 'options' => self::HTTP_METHOD_OPTIONS, + 'connect' => self::HTTP_METHOD_CONNECT, + 'patch' => self::HTTP_METHOD_PATCH, + 'track' => self::HTTP_METHOD_TRACK, + ); + } + + /** + * Sanitize a token field, such as a header name, and make sure it is valid. + * + * A valid header name has the following structure: + * - 1*(tchar) + * + * @param string $token_name + * The string to sanitize as a token name. + * + * @return string|bool + * A sanitized string is return on success. + * FALSE is returned on error or if the header name is invalid. + */ + private function p_prepare_token($token_name) { + $trimmed = preg_replace('/(^\s+)|(\s+$)/us', '', mb_strtolower($token_name)); + if ($trimmed === FALSE) { + unset($trimmed); + return FALSE; + } + + $text = $this->pr_rfc_string_prepare($trimmed); + unset($trimmed); + + if ($text['invalid']) { + unset($text); + return FALSE; + } + + $sanitized = $this->pr_rfc_string_is_token($text['ordinals'], $text['characters']); + unset($text); + + if ($sanitized['invalid']) { + unset($sanitized); + return FALSE; + } + + return $sanitized['text']; + } + + /** + * Prepare HTTP response header: access-control-allow-origin. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + private function p_prepare_header_response_access_control_allow_origin(&$header_output) { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: access-control-expose-headers. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + private function p_prepare_header_response_access_control_expose_headers(&$header_output) { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: access-control-allow-methods. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + private function p_prepare_header_response_access_control_allow_methods(&$header_output) { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_METHODS, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: access-control-allow-headers. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing + */ + private function p_prepare_header_response_access_control_allow_headers(&$header_output) { + if (!array_key_exists(self::RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: accept-patch. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: self::pr_rfc_string_is_media_type() + * @see: https://tools.ietf.org/html/rfc5789#section-3.1 + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + private function p_prepare_header_response_accept_patch(&$header_output) { + if (!array_key_exists(self::RESPONSE_ACCEPT_PATCH, $this->response)) { + return; + } + + $header_output[self::RESPONSE_ACCEPT_PATCH] = 'Accept-Patch: ' . array_shift($this->response[self::RESPONSE_ACCEPT_PATCH]); + + if (!empty($this->response[self::RESPONSE_ACCEPT_PATCH])) { + foreach ($this->response[self::RESPONSE_ACCEPT_PATCH] as $media_type) { + $header_output[self::RESPONSE_ACCEPT_PATCH] .= ', ' . $media_type; + } + unset($media_type); + } + } + + /** + * Prepare HTTP response header: allow. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.1 + */ + private function p_prepare_header_response_allow(&$header_output) { + if (!array_key_exists(self::RESPONSE_ALLOW, $this->response)) { + return; + } + + if (array_key_exists(self::HTTP_METHOD_NONE, $this->response[self::RESPONSE_ALLOW])) { + // An empty Allow field value indicates that the resource allows no methods, which might occur in a 405 response if the resource has been temporarily disabled by configuration. + $header_output[self::RESPONSE_ALLOW] = 'Allow: '; + return; + } + + $mapping = array_flip($this->p_get_http_method_mapping()); + + $header_output[self::RESPONSE_ALLOW] = 'Allow: '; + + $allow = array_shift($this->response[self::RESPONSE_ALLOW]); + $header_output[self::RESPONSE_ALLOW] .= $mapping[$allow]; + + if (!empty($this->response[self::RESPONSE_ALLOW])) { + foreach ($this->response[self::RESPONSE_ALLOW] as $allow) { + $header_output[self::RESPONSE_ALLOW] .= ', ' . $mapping[$allow]; + } + } + unset($allow); + unset($mapping); + } + + /** + * Prepare HTTP response header: cache-control. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.2 + * @see: https://tools.ietf.org/html/rfc7234#section-5.2.3 + */ + private function p_prepare_header_response_cache_control(&$header_output) { + if (!array_key_exists(self::RESPONSE_CACHE_CONTROL, $this->response) || empty($this->response[self::RESPONSE_CACHE_CONTROL])) { + return; + } + + $header_output[self::RESPONSE_CACHE_CONTROL] = NULL; + foreach ($this->response[self::RESPONSE_CACHE_CONTROL] as $cache_control_directive => $cache_control_value) { + if (is_null($header_output[self::RESPONSE_CACHE_CONTROL])) { + $header_output[self::RESPONSE_CACHE_CONTROL] = 'Cache-Control: '; + } + else { + $header_output[self::RESPONSE_CACHE_CONTROL] .= ', '; + } + + switch ($cache_control_directive) { + case self::CACHE_CONTROL_NO_CACHE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'no-cache'; + break; + + case self::CACHE_CONTROL_NO_STORE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'no-store'; + break; + + case self::CACHE_CONTROL_NO_TRANSFORM: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'no-transform'; + break; + + case self::CACHE_CONTROL_MAX_AGE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'max-age'; + break; + + case self::CACHE_CONTROL_MAX_AGE_S: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 's-max-age'; + break; + + case self::CACHE_CONTROL_MAX_STALE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'max-statle'; + break; + + case self::CACHE_CONTROL_MIN_FRESH: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'min-fresh'; + break; + + case self::CACHE_CONTROL_ONLY_IF_CACHED: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'only-if-cached'; + break; + + case self::CACHE_CONTROL_PUBLIC: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'public'; + break; + + case self::CACHE_CONTROL_PRIVATE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'private'; + break; + + case self::CACHE_CONTROL_MUST_REVALIDATE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'must-revalidate'; + break; + + case self::CACHE_CONTROL_PROXY_REVALIDATE: + $header_output[self::RESPONSE_CACHE_CONTROL] .= 'proxy-revalidate'; + break; + + default: + break; + } + + if (!is_null($cache_control_value)) { + $header_output[self::RESPONSE_CACHE_CONTROL] .= '=' . $cache_control_value; + } + } + unset($cache_control_directive); + unset($cache_control_value); + } + + /** + * Prepare HTTP response header: connection. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7230#section-6.1 + */ + private function p_prepare_header_response_connection(&$header_output) { + if (!array_key_exists(self::RESPONSE_CONNECTION, $this->response)) { + return; + } + + $header_output[self::RESPONSE_CONNECTION] = 'Connection: '; + + $connection = array_shift($this->response[self::RESPONSE_CONNECTION]); + $header_output[self::RESPONSE_CONNECTION] .= $connection; + + if (!empty($this->response[self::RESPONSE_CONNECTION])) { + foreach ($this->response[self::RESPONSE_CONNECTION] as $connection) { + $header_output[self::RESPONSE_CONNECTION] .= ', ' . $connection; + } + } + unset($connection); + } + + /** + * Prepare HTTP response header: content-disposition. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc6266#section-4 + */ + private function p_prepare_header_response_content_disposition(&$header_output) { + if (!array_key_exists(self::RESPONSE_CONTENT_DISPOSITION, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: content-encoding. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.2.2 + */ + private function p_prepare_header_response_content_encoding(&$header_output) { + if (!array_key_exists(self::RESPONSE_CONTENT_ENCODING, $this->response)) { + return; + } + + $header_output[self::RESPONSE_CONTENT_ENCODING] = 'Content-Encoding: '; + + switch ($this->response[self::RESPONSE_CONTENT_ENCODING]) { + case self::ENCODING_CHUNKED: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'chubked'; + break; + case self::ENCODING_COMPRESS: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'compress'; + break; + case self::ENCODING_DEFLATE: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'deflate'; + break; + case self::ENCODING_GZIP: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'gzip'; + break; + case self::ENCODING_BZIP: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'bzip'; + break; + case self::ENCODING_LZO: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'lzo'; + break; + case self::ENCODING_XZ: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'xz'; + break; + case self::ENCODING_EXI: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'exi'; + break; + case self::ENCODING_IDENTITY: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'identity'; + break; + case self::ENCODING_SDCH: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'sdch'; + break; + case self::ENCODING_PG: + $header_output[self::RESPONSE_CONTENT_ENCODING] .= 'pg'; + break; + default: + unset($header_output[self::RESPONSE_CONTENT_ENCODING]); + } + } + + /** + * Prepare HTTP response header: content-language. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.3.2 + */ + private function p_prepare_header_response_content_language(&$header_output) { + if (!array_key_exists(self::RESPONSE_CONTENT_LANGUAGE, $this->response)) { + return; + } + + $language_array = $this->language_class->s_get_aliases_by_id($this->response[self::RESPONSE_CONTENT_LANGUAGE]); + if ($language_array instanceof c_base_return_array) { + $language_array = $language_array->get_value_exact(); + + if (!empty($language_array[0])) { + $header_output[self::RESPONSE_CONTENT_LANGUAGE] = 'Content-Language: ' . $language_array[0]; + } + } + unset($language_array); + } + + /** + * Prepare HTTP response header: content-type. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-3.1.1.5 + */ + private function p_prepare_header_response_content_type(&$header_output) { + if (!array_key_exists(self::RESPONSE_CONTENT_TYPE, $this->response)) { + return; + } + + $header_output[self::RESPONSE_CONTENT_TYPE] = 'Content-Type: ' . $this->response[self::RESPONSE_CONTENT_TYPE]['type'] . '; '; + + $encoding_string = c_base_charset::s_to_string($this->response[self::RESPONSE_CONTENT_TYPE]['charset']); + if ($encoding_string instanceof c_base_return_string) { + $header_output[self::RESPONSE_CONTENT_TYPE] .= $encoding_string->get_value_exact(); + } + unset($encoding_string); + } + + /** + * Prepare HTTP response header: etag. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc6266#section-4 + */ + private function p_prepare_header_response_etag(&$header_output) { + if (!array_key_exists(self::RESPONSE_ETAG, $this->response)) { + return; + } + + if ($this->response[self::RESPONSE_ETAG]['weak']) { + $header_output[self::RESPONSE_ETAG] = 'Etag: W/"' . $this->response[self::RESPONSE_ETAG]['tag'] . '"'; + } + else { + $header_output[self::RESPONSE_ETAG] = 'Etag: "' . $this->response[self::RESPONSE_ETAG]['tag'] . '"'; + } + } + + /** + * Prepare HTTP response header: link. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc5988#section-5 + * @see: https://tools.ietf.org/html/rfc3986 + */ + private function p_prepare_header_response_link(&$header_output) { + if (!array_key_exists(self::RESPONSE_LINK, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: location. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc3986 + */ + private function p_prepare_header_response_location(&$header_output) { + if (!array_key_exists(self::RESPONSE_LOCATION, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: pragma. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.4 + */ + private function p_prepare_header_response_pragma(&$header_output) { + if (!array_key_exists(self::RESPONSE_PRAGMA, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: proxy-authenticate. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.3 + */ + private function p_prepare_header_response_proxy_authenticate(&$header_output) { + if (!array_key_exists(self::RESPONSE_PROXY_AUTHENTICATE, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: public-key-pins. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7469 + */ + private function p_prepare_header_response_public_key_pins(&$header_output) { + if (!array_key_exists(self::RESPONSE_PUBLIC_KEY_PINS, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: refresh. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/Meta_refresh + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + private function p_prepare_header_response_refresh(&$header_output) { + if (!array_key_exists(self::RESPONSE_REFRESH, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: retry-after. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.3 + */ + private function p_prepare_header_response_retry_after(&$header_output) { + if (!array_key_exists(self::RESPONSE_RETRY_AFTER, $this->response)) { + return; + } + + $header_output[self::RESPONSE_RETRY_AFTER] = 'Retry-After: '; + + if ($this->response[self::RESPONSE_RETRY_AFTER]['is_seconds']) { + $header_output[self::RESPONSE_RETRY_AFTER] .= $this->response[self::RESPONSE_RETRY_AFTER]['is_seconds']; + } + else { + $timezone = date_default_timezone_get(); + date_default_timezone_set('GMT'); + + $header_output[self::RESPONSE_RETRY_AFTER] .= date(self::TIMESTAMP_RFC_5322, $this->response[self::RESPONSE_RETRY_AFTER]['value']); + + date_default_timezone_set($timezone); + unset($timezone); + } + } + + /** + * Prepare HTTP response header: server. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.4.2 + */ + private function p_prepare_header_response_server(&$header_output) { + if (!array_key_exists(self::RESPONSE_SERVER, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: strict-transport-security. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc6797#section-6.1 + */ + private function p_prepare_header_response_strict_transport_security(&$header_output) { + if (!array_key_exists(self::RESPONSE_STRICT_TRANSPORT_SECURITY, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: trailer. + * + * @todo: this appears to no longer be directly specified in the headers. + * There is a 'trailer-part' mentioned along with the transfer encoding information. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc2616#section-14.40 + * @see: https://tools.ietf.org/html/rfc7230#section-4.1.2 + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.1 + */ + private function p_prepare_header_response_trailer(&$header_output) { + if (!array_key_exists(self::RESPONSE_TRAILER, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: transfer-encoding. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7230#section-3.3.1 + */ + private function p_prepare_header_response_transfer_encoding(&$header_output) { + if (!array_key_exists(self::RESPONSE_TRANSFER_ENCODING, $this->response)) { + return; + } + + // according to the standard, content-length cannot be specified when transfer-encoding is defined. + if (array_key_exists(self::RESPONSE_CONTENT_LENGTH, $header_output)) { + unset($header_output[self::RESPONSE_CONTENT_LENGTH]); + } + + // @todo + // @fixme: transfer-encoding is now an array of values. + } + + /** + * Prepare HTTP response header: upgrade. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3 + */ + private function p_prepare_header_response_upgrade(&$header_output) { + if (!array_key_exists(self::RESPONSE_UPGRADE, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: vary. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7231#section-7.1.4 + */ + private function p_prepare_header_response_vary(&$header_output) { + if (!array_key_exists(self::RESPONSE_VARY, $this->response)) { + return; + } + + $header_output[self::RESPONSE_VARY] = 'Vary: '; + + $vary = array_shift($this->response[self::RESPONSE_VARY]); + $header_output[self::RESPONSE_VARY] .= $vary; + + if (!empty($this->response[self::RESPONSE_VARY])) { + foreach ($this->response[self::RESPONSE_VARY] as $vary) { + $header_output[self::RESPONSE_VARY] .= ', ' . $vary; + } + } + unset($vary); + } + + /** + * Prepare HTTP response header: warning. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7234#section-5.5 + */ + private function p_prepare_header_response_warning(&$header_output) { + if (!array_key_exists(self::RESPONSE_WARNING, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: www-authenticate. + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://tools.ietf.org/html/rfc7235#section-4.1 + */ + private function p_prepare_header_response_www_authenticate(&$header_output) { + if (!array_key_exists(self::RESPONSE_WWW_AUTHENTICATE, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: x-content-security-policy + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + private function p_prepare_header_response_x_content_security_policy(&$header_output) { + if (!array_key_exists(self::RESPONSE_X_CONTENT_SECURITY_POLICY, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: x-content-type-options + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + private function p_prepare_header_response_x_content_type_options(&$header_output) { + if (!array_key_exists(self::RESPONSE_X_CONTENT_TYPE_OPTIONS, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response header: x-ua-compatible + * + * @param array $header_output + * The header output array to make changes to. + * + * @see: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + */ + private function p_prepare_header_response_x_ua_compatible(&$header_output) { + if (!array_key_exists(self::RESPONSE_X_UA_COMPATIBLE, $this->response)) { + return; + } + + // @todo + } + + /** + * Prepare HTTP response headers for the content checksum fields. + * + * This will perform a checksum against the content. + * Be sure to perform this check before changing the content-encoding. + * + * This handles the following header fields: + * - checksum_content + * + * @param array $header_output + * The header output array to make changes to. + */ + private function p_prepare_header_response_checksum_content(&$header_output) { + if (!array_key_exists(self::RESPONSE_CHECKSUM_CONTENT, $this->response)) { + return; + } + + $what = NULL; + if ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['what'] == self::CHECKSUM_WHAT_FULL) { + $what = 'full'; + } + elseif ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['what'] == self::CHECKSUM_WHAT_PARTIAL) { + $what = 'partial'; + } + elseif ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['what'] == self::CHECKSUM_WHAT_SIGNED) { + $what = 'signed'; + } + elseif ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['what'] == self::CHECKSUM_WHAT_UNSIGNED) { + $what = 'unsigned'; + } + + if (is_null($what)) { + unset($what); + return; + } + + $algorithm = NULL; + $use_hash = FALSE; + switch ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['type']) { + case self::CHECKSUM_MD2: + $algorithm = 'md2'; + $use_hash = TRUE; + break; + case self::CHECKSUM_MD4: + $algorithm = 'md4'; + $use_hash = TRUE; + break; + case self::CHECKSUM_MD5: + $algorithm = 'md5'; + $use_hash = TRUE; + break; + case self::CHECKSUM_SHA1: + $algorithm = 'sha1'; + $use_hash = TRUE; + break; + case self::CHECKSUM_SHA224: + $algorithm = 'sha224'; + $use_hash = TRUE; + break; + case self::CHECKSUM_SHA256: + $algorithm = 'sha256'; + $use_hash = TRUE; + break; + case self::CHECKSUM_SHA384: + $algorithm = 'sha384'; + $use_hash = TRUE; + break; + case self::CHECKSUM_SHA512: + $algorithm = 'sha512'; + $use_hash = TRUE; + break; + case self::CHECKSUM_CRC32: + $algorithm = 'crc32'; + $use_hash = TRUE; + break; + case self::CHECKSUM_PG: + $algorithm = 'pg'; + break; + } + + // @todo: handle support for other algorithms. + if ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['action'] == self::CHECKSUM_ACTION_AUTO) { + if ($this->content_is_file) { + if ($use_hash) { + $hash = hash_init($algorithm); + foreach ($this->content as $filename) { + if (!file_exists($filename)) { + unset($filename); + unset($hash); + unset($what); + unset($algorithm); + unset($use_hash); + + // @todo: report file not found or other related errors. + return c_base_return_error::s_false(); + } + + $success = hash_update_file($hash, $filename); + if (!$success) { + unset($success); + unset($filename); + unset($hash); + unset($what); + unset($algorithm); + unset($use_hash); + + // failure + return; + } + } + unset($filename); + unset($success); + + $header_output[self::RESPONSE_CHECKSUM_CONTENT] = 'Checksum_Content: ' . $what . ':' . $algorithm . ':' . hash_final($hash, FALSE); + unset($hash); + } + else { + // @todo: handle CHECKSUM_PG in this case. + // if ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['type'] == self::CHECKSUM_PG) { + // } + } + } + else { + if ($use_hash) { + $header_output[self::RESPONSE_CHECKSUM_CONTENT] = 'Checksum_Content: ' . $what . ':' . $algorithm . ':' . hash($algorithm, $this->content); + } + else { + // @todo: handle CHECKSUM_PG in this case. + // if ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['type'] == self::CHECKSUM_PG) { + // } + } + } + } + elseif ($this->response[self::RESPONSE_CHECKSUM_CONTENT]['action'] == self::CHECKSUM_ACTION_MANUAL) { + $header_output[self::RESPONSE_CHECKSUM_CONTENT] = 'Checksum_Content: ' . $what . ':' . $algorithm . ':' . $this->response[self::RESPONSE_CHECKSUM_CONTENT]['checksum']; + } + unset($use_hash); + unset($what); + unset($algorithm); + } + + /** + * Prepare HTTP response headers for both the header checksum fields. + * + * @todo: implement custom functions for setting algorithms, checksum, and even enabled/disabling auto-checksuming for all checksum header fields. + * + * This handles the following header fields: + * - checksum_header + * - checksum_headers + * + * This must be performed after all other header fields have been prepared. + * + * @param array $header_output + * The header output array to make changes to. + * @param string|null $status_string + * When not NULL, this is prepended to the start of the header checksum string before the checksum is calculated. + * When NULL, this value is ignored. + */ + private function p_prepare_header_response_checksum_headers(&$header_output, $status_string) { + if (!array_key_exists(self::RESPONSE_CHECKSUM_HEADER, $this->response) || !array_key_exists(self::RESPONSE_CHECKSUM_HEADERS, $this->response)) { + return; + } + + $header_output_copy = $header_output; + + if (array_key_exists('date_actual', $header_output_copy)) { + // When date_actual is specified, the date parameter will not be processed, prevent the 'date' parameter from being used to calculate the header checksum. + unset($header_output_copy['date']); + } + + $header_output_keys = array_keys($header_output_copy); + unset($header_output_copy); + + if (!empty($header_output_keys)) { + $header_value = array_shift($header_output_keys); + if (!empty($header_output_keys)) { + foreach ($header_output_keys as $header_name) { + $header_value .= ', ' . $header_name; + } + } + } + unset($header_output_keys); + + $header_output[self::RESPONSE_CHECKSUM_HEADER] = $header_value; + unset($header_value); + + $header_output_copy = $header_output; + unset($header_output_copy['checkum_header']); + + $header_string = ''; + if (!is_null($status_string)) { + $header_string .= $status_string . "\n"; + } + + $header_string .= implode("\n", $header_output_copy); + unset($header_output_copy); + + // @todo: allow caller to specifiy which hash code and which settings. + $checkum_header = hash('sha256', $header_string); + unset($header_string); + + // @todo: handle support for other algorithms. + $header_output[self::RESPONSE_CHECKSUM_HEADER] = 'Checksum_Header: full:sha256:' . $checkum_header; + unset($checkum_header); + } + + /** + * Prepare HTTP response headers that are simple values. + * + * @param array $header_output + * The header output array to make changes to. + * @param string $name_lower + * The HTTP header name (all lowercase), such as: 'age'. + * @param string $name + * The HTTP header name, such as: 'Age'. + * @param int $code + * The HTTP header code, such as: self::RESPONSE_AGE. + */ + private function p_prepare_header_response_simple_value(&$header_output, $name_lower, $name, $code) { + if (!array_key_exists($code, $this->response)) { + return; + } + + $header_output[$code] = $name . ': ' . $this->response[$code]; + } + + /** + * Prepare HTTP response header: date + * + * @param array $header_output + * The header output array to make changes to. + * @param string $name_lower + * The HTTP header name (all lowercase), such as: 'age'. + * @param string $name + * The HTTP header name, such as: 'Age'. + * @param int $code + * The HTTP header code, such as: self::RESPONSE_AGE. + */ + private function p_prepare_header_response_timestamp_value(&$header_output, $name_lower, $name, $code) { + if (!array_key_exists($code, $this->response)) { + return; + } + + $timezone = date_default_timezone_get(); + date_default_timezone_set('GMT'); + + $header_output[$code] = $name . ': ' . date(self::TIMESTAMP_RFC_5322, $this->response[$code]); + + date_default_timezone_set($timezone); + unset($timezone); + } + + /** + * Process the accept-encoding HTTP header to determine which content-encoding to use. + * + * @return int + * The encoding to use. + */ + private function p_determine_response_encoding() { + if (!$this->request[self::REQUEST_ACCEPT_ENCODING]['defined'] || $this->request[self::REQUEST_ACCEPT_ENCODING]['invalid']) { + return self::ENCODING_CHUNKED; + } + + $encoding = self::ENCODING_CHUNKED; + foreach ($this->request[self::REQUEST_ACCEPT_ENCODING]['data']['weight'] as $weight => $choices) { + foreach ($choices as $key => $choice) { + if ($key == c_base_http::ENCODING_GZIP) { + $encoding = $key; + break 2; + } + + if ($key == self::ENCODING_DEFLATE) { + $encoding = $key; + break 2; + } + } + unset($key); + unset($choice); + } + unset($weight); + unset($choices); + + return $encoding; + } + + /** + * Encode and store the given content string. + * + * Don't pass $this->content directly to this function unless you know it is not an array of filenames. + * - For an array of filenames, load the files into memory and then pass the loaded content. + * + * @param string $content + * The content string to encode and store. + * @param int $encoding + * The encoding algorithm to perform. + * @param bool $calculate_content_length + * (optional) Determine how the content-length HTTP header is to be calculated: + * - FALSE = do not calculate the content length. + * - TRUE = calculate and store the content length. + */ + private function p_encode_content($content, $encoding, $compression = NULL, $calculate_content_length = TRUE) { + if ($encoding == self::ENCODING_GZIP) { + if (is_null($compression) || $compression < -1) { + $compression = -1; + } + elseif ($compression > 9) { + $compression = 9; + } + + $this->content = gzencode($this->content, $compression, FORCE_GZIP); + $this->content_is_file = FALSE; + $this->response[self::RESPONSE_CONTENT_ENCODING] = $encoding; + + if ($calculate_content_length) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = strlen($this->content); + } + + } + elseif ($encoding == self::ENCODING_DEFLATE) { + if (is_null($compression) || $compression < -1) { + $compression = -1; + } + elseif ($compression > 9) { + $compression = 9; + } + + $this->content = gzencode($content, $compression, FORCE_DEFLATE); + $this->content_is_file = FALSE; + $this->response[self::RESPONSE_CONTENT_ENCODING] = $encoding; + + if ($calculate_content_length) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = strlen($this->content); + } + } + elseif ($encoding == self::ENCODING_BZIP) { + if (is_null($compression) || $compression < -1) { + $compression = 4; + } + elseif ($compression > 9) { + $compression = 9; + } + + $this->content = bzcompress($content, $compression); + $this->content_is_file = FALSE; + $this->response[self::RESPONSE_CONTENT_ENCODING] = $encoding; + + if ($calculate_content_length) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = strlen($this->content); + } + } + elseif ($encoding == self::ENCODING_LZO) { + switch ($compression) { + case LZO1X_1: + case LZO1_1: + case LZO1_99: + case LZO1A_1: + case LZO1A_99: + case LZO1B_1: + case LZO1B_2: + case LZO1B_3: + case LZO1B_4: + case LZO1B_5: + case LZO1B_6: + case LZO1B_7: + case LZO1B_8: + case LZO1B_9: + case LZO1B_99: + case LZO1B_999: + case LZO1C_1: + case LZO1C_2: + case LZO1C_3: + case LZO1C_4: + case LZO1C_5: + case LZO1C_6: + case LZO1C_7: + case LZO1C_8: + case LZO1C_9: + case LZO1C_99: + case LZO1C_999: + case LZO1F_1: + case LZO1F_999: + case LZO1X_1_11: + case LZO1X_1_12: + case LZO1X_1_15: + case LZO1X_999: + case LZO1Y_1: + case LZO1Y_999: + case LZO1Z_999: + case LZO2A_999: + break; + default: + $compression = LZO2A_999; + // Note: according to (http://content.gpwiki.org/index.php/LZO) LZO1X tends to be the best (in general) consider that for the default. + } + + $this->content = lzo_compress($content, $compression); + $this->content_is_file = FALSE; + $this->response[self::RESPONSE_CONTENT_ENCODING] = $encoding; + + if ($calculate_content_length) { + $this->response[self::RESPONSE_CONTENT_LENGTH] = strlen($this->content); + } + } + elseif ($encoding == self::ENCODING_XZ) { + // @todo, see: https://github.com/payden/php-xz + } + elseif ($encoding == self::ENCODING_EXI) { + // @todo, maybe? (cannot seem to find a php library at this time) + } + elseif ($encoding == self::ENCODING_IDENTITY) { + // @todo, maybe? (cannot seem to find a php library at this time) + } + elseif ($encoding == self::ENCODING_SDCH) { + // @todo, maybe? (cannot seem to find a php library at this time) + } + elseif ($encoding == self::ENCODING_PG) { + // @todo, using ascii armor on entire body. + // should be a header field containing the public key. + } + } +} diff --git a/common/base/classes/base_http_status.php b/common/base/classes/base_http_status.php new file mode 100644 index 0000000..eb3e43b --- /dev/null +++ b/common/base/classes/base_http_status.php @@ -0,0 +1,349 @@ + array('en-us'), + self::ENGLISH => array('eng', 'en'), + self::UNDETERMINED => array('und'), + self::NOT_APPLICABLE => array('zxx'), + ); + + private static $s_names = array( + self::ENGLISH_US => array('US English'), + self::ENGLISH => array('English'), + self::UNDETERMINED => array('Undetermined'), + self::NOT_APPLICABLE => array('No Linguistic Content', 'Not Applicable'), + ); + + private static $s_ids = array( + 'en-us' => self::ENGLISH_US, + 'eng' => self::ENGLISH, + 'en' => self::ENGLISH, + 'und' => self::UNDETERMINED, + 'zxx' => self::NOT_APPLICABLE, + ); + + + /** + * Get the language names associated with the id. + * + * @param int $id + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given id. + */ + static function s_get_names_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_names)) { + return c_base_return_array::s_new(self::$s_names[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language names associated with the alias. + * + * @param string $alias + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given alias. + */ + static function s_get_names_by_alias($alias) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_alias($alias) { + if (!is_string($alias)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($alias, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$alias]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the id. + * + * @param int $id + * The id of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given id. + */ + static function s_get_aliases_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the name. + * + * @param string $name + * The language name of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given name. + */ + static function s_get_aliases_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_id() { + return c_base_return_int::s_new(self::ENGLISH_US); + } + + /** + * Get the name of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_string + * A string representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_name() { + return c_base_return_string::s_new($this->s_aliases[self::ENGLISH_US]); + } +} + +/** + * A generic class for managing the different supported languages. + * + * @see: http://www.loc.gov/standards/iso639-2/php/code_list.php + */ +final class c_base_language_us_limited implements i_base_language { + + private static $s_aliases = array( + self::ENGLISH_US => array('en-us'), + self::ENGLISH => array('eng', 'en'), + self::FRENCH => array('fre', 'fra', 'fr'), + self::SPANISH => array('spa', 'es'), + self::INDONESIAN => array('ind', 'id'), + self::RUSSIAN => array('rus', 'ru'), + self::CHINESE => array('chi', 'zho', 'zh'), + self::UNDETERMINED => array('und'), + self::NOT_APPLICABLE => array('zxx'), + ); + + private static $s_names = array( + self::ENGLISH_US => array('US English'), + self::ENGLISH => array('English'), + self::FRENCH => array('French'), + self::SPANISH => array('Spanish', 'Castilian'), + self::INDONESIAN => array('Indonesian'), + self::RUSSIAN => array('Russian'), + self::CHINESE => array('Chinese'), + self::UNDETERMINED => array('Undetermined'), + self::NOT_APPLICABLE => array('No Linguistic Content', 'Not Applicable'), + ); + + private static $s_ids = array( + 'en-us' => self::ENGLISH_US, + 'eng' => self::ENGLISH, + 'en' => self::ENGLISH, + 'fre' => self::FRENCH, + 'fra' => self::FRENCH, + 'fr' => self::FRENCH, + 'spa' => self::SPANISH, + 'es' => self::SPANISH, + 'ind' => self::INDONESIAN, + 'id' => self::INDONESIAN, + 'rus' => self::RUSSIAN, + 'ru' => self::RUSSIAN, + 'chi' => self::CHINESE, + 'zho' => self::CHINESE, + 'zh' => self::CHINESE, + 'und' => self::UNDETERMINED, + 'zxx' => self::NOT_APPLICABLE, + ); + + + /** + * Get the language names associated with the id. + * + * @param int $id + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given id. + */ + static function s_get_names_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_names)) { + return c_base_return_array::s_new(self::$s_names[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language names associated with the alias. + * + * @param string $alias + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given alias. + */ + static function s_get_names_by_alias($alias) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_alias($alias) { + if (!is_string($alias)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($alias, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$alias]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the id. + * + * @param int $id + * The id of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given id. + */ + static function s_get_aliases_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the name. + * + * @param string $name + * The language name of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given name. + */ + static function s_get_aliases_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_id() { + return c_base_return_int::s_new(self::ENGLISH_US); + } + + /** + * Get the name of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_string + * A string representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_name() { + return c_base_return_string::s_new($this->s_aliases[self::ENGLISH_US]); + } +} + +/** + * A generic class for managing the different supported languages. + * + * @see: http://www.loc.gov/standards/iso639-2/php/code_list.php + */ +final class c_base_language_us_all implements i_base_language { + + private static $s_aliases = array( + self::ENGLISH_US => array('en-us'), + self::ENGLISH => array('eng', 'en'), + self::ENGLISH_CA => array('en-ca'), + self::ENGLISH_GB => array('en-gb'), + self::AFAR => array('aar', 'aa'), + self::ABKHAZIAN => array('abk', 'ab'), + self::ACHINESE => array('ace'), + self::ACOLI => array('ach'), + self::ADANGME => array('ada'), + self::ADYGHE => array('ady'), + self::AFRO_ASIATIC => array('afa'), + self::AFRIHILI => array('afh'), + self::AFRIKAANS => array('afr', 'af'), + self::AINU => array('ain'), + self::AKAN => array('aka', 'ak'), + self::AKKADIAN => array('akk'), + self::ALBANIAN => array('alb', 'sqi', 'sq'), + self::ALEUT => array('ale'), + self::ALGONQUIAN => array('alg'), + self::SOUTHERN_ALTAI => array('alt'), + self::AMHARIC => array('amh', 'am'), + self::ENGLISH_OLD => array('ang'), + self::ANGIKA => array('anp'), + self::APACHE => array('apa'), + self::ARABIC => array('ara', 'ar'), + self::ARAMAIC => array('arc'), + self::ARAGONESE => array('arg', 'an'), + self::ARMENIAN => array('arm', 'hye', 'hy'), + self::MAPUDUNGUN => array('am'), + self::ARAPAHO => array('arp'), + self::ARTIFICIAL => array('art'), + self::ARAWAK => array('arw'), + self::ASSAMESE => array('asm', 'as'), + self::ASTURIAN => array('ast'), + self::ATHAPASCAN => array('ath'), + self::AUSTRALIAN => array('aus'), + self::AVARIC => array('ava', 'av'), + self::AVESTAN => array('ave', 'ae'), + self::AWADHI => array('awa'), + self::AYMARA => array('aym', 'ay'), + self::AZERBAIJANI => array('aze', 'az'), + self::BANDA => array('bad'), + self::BAMILEKE => array('bai'), + self::BASHKIR => array('bak', 'ba'), + self::BALUCHI => array('bal'), + self::BAMBARA => array('bam', 'bm'), + self::BALINESE => array('ban'), + self::BASQUE => array('baq', 'eus', 'eu'), + self::BASA => array('bas'), + self::BALTIC => array('bat'), + self::BEJA => array('bej'), + self::BELARUSIAN => array('bel', 'be'), + self::BEMBA => array('bem'), + self::BENGALI => array('ben', 'bn'), + self::BERBER => array('ber'), + self::BHOJPURI => array('bho'), + self::BIHARI => array('bih', 'bh'), + self::BIKOL => array('bik'), + self::BINI => array('bin'), + self::BISLAMA => array('bis', 'bi'), + self::SIKSIKA => array('bla'), + self::BANTU => array('bnt'), + self::TIBETAN => array('tib', 'bod', 'bo'), + self::BOSNIAN => array('bos', 'bs'), + self::BRAJ => array('bra'), + self::BRETON => array('bre'), + self::BATAK => array('btk'), + self::BURIAT => array('bua'), + self::BUGINESE => array('bug'), + self::BULGARIAN => array('bul'), + self::BURMESE => array('bur', 'mya', 'my'), + self::BLIN => array('byn'), + self::CADDO => array('cad'), + self::AMERICAN_INDIAN_CENTRAL => array('cai'), + self::GALIBI_CARIB => array('car'), + self::CATALAN => array('cat', 'ca'), + self::CAUCASIAN => array('cau'), + self::CEBUANO => array('ceb'), + self::CELTIC => array('cel'), + self::CZECH => array('cze', 'ces', 'cs'), + self::CHAMORRO => array('cha', 'ch'), + self::CHIBCHA => array('chb'), + self::CHECHEN => array('che', 'ce'), + self::CHAGATAI => array('chg'), + self::CHINESE => array('chi', 'zho', 'zh'), + self::CHUUKESE => array('chk'), + self::MARI => array('chm'), + self::CHINOOK_JARGON => array('chn'), + self::CHOCTAW => array('cho'), + self::CHIPEWYAN => array('chp'), + self::CHEROKEE => array('chr'), + self::CHURCH_SLAVIC => array('chu', 'cu'), + self::CHUVASH => array('chv', 'cv'), + self::CHEYENNE => array('chy'), + self::CHAMIC => array('cmc'), + self::COPTIC => array('cop'), + self::CORNISH => array('cor'), + self::CORSICAN => array('cos', 'co'), + self::CREOLES_ENGLISH => array('cpe'), + self::CREOLES_FRENCH => array('cpf'), + self::CREOLES_PORTUGESE => array('cpp'), + self::CREE => array('cre', 'cr'), + self::CRIMEAN_TATAR => array('crh'), + self::CREOLES => array('crp'), + self::KASHUBIAN => array('csb'), + self::CUSHITIC => array('cus'), + self::WELSH => array('wel', 'cym', 'cy'), + self::DAKOTA => array('dak'), + self::DANISH => array('dan', 'da'), + self::DARGWA => array('dar'), + self::LAND_DAYAK => array('day'), + self::DELAWARE => array('del'), + self::SLAVE => array('den'), + self::GERMAN => array('ger', 'deu', 'de'), + self::DOGRIB => array('dgr'), + self::DINKA => array('din'), + self::DIVEHI => array('div', 'dv'), + self::DOGRI => array('doi'), + self::DRAVIDIAN => array('dra'), + self::LOWER_SORBIAN => array('dsb'), + self::DUALA => array('dua'), + self::DUTCH_MIDDLE => array('dum'), + self::DUTCH_FLEMISH => array('dut', 'nld', 'nl'), + self::DYULA => array('dyu'), + self::DZONGKHA => array('dzo', 'dz'), + self::EFIK => array('efi'), + self::EGYPTIAN => array('egy'), + self::EKAJUK => array('eka'), + self::GREEK_MODERN => array('gre', 'ell', 'el'), + self::ELAMITE => array('elx'), + self::ENGLISH_MIDDLE => array('enm'), + self::ESPERANTO => array('epo', 'eo'), + self::ESTONIAN => array('est', 'et'), + self::EWE => array('ewe', 'ee'), + self::EWONDO => array('ewo'), + self::FANG => array('fan'), + self::FAROESE => array('fao', 'fo'), + self::PERSIAN => array('per', 'fas', 'fa'), + self::FANTI => array('fat'), + self::FIJIAN => array('fij', 'fj'), + self::FILIPINO => array('fil'), + self::FINNISH => array('fin', 'fi'), + self::FINNO_UGRIAN => array('fiu'), + self::FON => array('fon'), + self::FRENCH => array('fre', 'fra', 'fr'), + self::FRENCH_MIDDLE => array('frm'), + self::FRENCH_OLD => array('fro'), + self::FRISIAN_NORTHERN => array('frr'), + self::FRISIAN_EASTERN => array('frs'), + self::FRISIAN_WESTERN => array('fry', 'fy'), + self::FULAH => array('ful', 'ff'), + self::FRIULIAN => array('fur'), + self::GA => array('gaa'), + self::GAYO => array('gay'), + self::GBAYA => array('gba'), + self::GERMANIC => array('gem'), + self::GEORGIAN => array('geo', 'kat', 'ka'), + self::GEEZ => array('gez'), + self::GILBERTESE => array('gil'), + self::GAELIC => array('gla', 'gd'), + self::IRISH => array('gle', 'ga'), + self::GALICIAN => array('glg', 'gl'), + self::MANX => array('glv', 'gv'), + self::GERMAN_MIDDLE_HIGH => array('gmh'), + self::GERMAN_OLD_HIGH => array('goh'), + self::GONDI => array('gon'), + self::GORONTALO => array('gor'), + self::GOTHIC => array('got'), + self::GREBO => array('grb'), + self::GREEK_ANCIENT => array('grc'), + self::GUARANI => array('grm', 'gn'), + self::GERMAN_SWISS => array('gsw'), + self::GUJARATI => array('guj', 'gu'), + self::GWICHIN => array('gwi'), + self::HAIDA => array('hai'), + self::HAITIAN => array('hat', 'ht'), + self::HAUSA => array('hau', 'ha'), + self::HAWAIIAN => array('haw'), + self::HEBREW => array('heb', 'he'), + self::HERERO => array('her', 'hz'), + self::HILIGAYNON => array('hil'), + self::HIMACHALI => array('him'), + self::HINDI => array('hin', 'hi'), + self::HITTITE => array('hit'), + self::HMONG => array('hmn'), + self::HIRI_MOTU => array('hmo', 'ho'), + self::CROATIAN => array('hrv'), + self::SORBIAN_UPPER => array('hsb'), + self::HUNGARIAN => array('hun', 'hu'), + self::HUPA => array('hup'), + self::IBAN => array('iba'), + self::IGBO => array('ibo', 'ig'), + self::ICELANDIC => array('ice', 'isl', 'is'), + self::IDO => array('ido', 'io'), + self::SICHUAN_YI => array('iii', 'ii'), + self::IJO => array('ijo'), + self::INUKTITUT => array('iku', 'iu'), + self::INTERLINGUE => array('ile', 'ie'), + self::ILOKO => array('ilo'), + self::INTERLINGUA => array('ina', 'ia'), + self::INDIC => array('inc'), + self::INDONESIAN => array('ind', 'id'), + self::INDO_EUROPEAN => array('ine'), + self::INGUSH => array('inh'), + self::INUPIAQ => array('ipk', 'ik'), + self::IRANIAN => array('ira'), + self::IROQUOIAN => array('iro'), + self::ITALIAN => array('ita', 'it'), + self::JAVANESE => array('jav', 'jv'), + self::LOJBAN => array('jbo'), + self::JAPANESE => array('jpn', 'ja'), + self::JUDEO_PERSIAN => array('jpr'), + self::JUDEO_ARABIC => array('jrb'), + self::KARA_KALPAK => array('kaa'), + self::KABYLE => array('kab'), + self::KACHIN => array('kac'), + self::KALAALLISUT => array('kal', 'kl'), + self::KAMBA => array('kam'), + self::KANNADA => array('kan', 'kn'), + self::KAREN => array('kar'), + self::KASHMIRI => array('kas', 'ks'), + self::KANURI => array('kau', 'kr'), + self::KAWI => array('kaw'), + self::KAZAKH => array('kaz'), + self::KABARDIAN => array('kbd'), + self::KHASI => array('kha'), + self::KHOISAN => array('khi'), + self::CENTRAL_KHMER => array('khm', 'km'), + self::KHOTANESE => array('kho'), + self::KIKUYU => array('kik', 'ki'), + self::KINYARWANDA => array('kin', 'rw'), + self::KIRGHIZ => array('kir', 'ky'), + self::KIMBUNDU => array('kmb'), + self::KONKANI => array('kok'), + self::KOMI => array('kom', 'kv'), + self::KONGO => array('kon', 'kg'), + self::KOREAN => array('kor', 'ko'), + self::KOSRAEAN => array('kos'), + self::KPELLE => array('kpe'), + self::KARACHAY_BALKAR => array('krc'), + self::KARELIAN => array('krl'), + self::KRU => array('kro'), + self::KURUKH => array('kru'), + self::KUANYAMA => array('kua', 'kj'), + self::KUMYK => array('kum'), + self::KURDISH => array('kur', 'ku'), + self::KUTENAI => array('kut'), + self::LADINO => array('lad'), + self::LAHNDA => array('lah'), + self::LAMBA => array('lam'), + self::LAO => array('lao', 'lo'), + self::LATIN => array('lat', 'la'), + self::LATVIAN => array('lav', 'lv'), + self::LEZGHIAN => array('lez'), + self::LIMBURGAN => array('lim', 'li'), + self::LINGALA => array('lin', 'ln'), + self::LITHUANIAN => array('lit', 'lt'), + self::MONGO => array('lol'), + self::LOZI => array('loz'), + self::LUXEMBOURGISH => array('ltz', 'lb'), + self::LUBA_LULUA => array('lua'), + self::LUBA_KATANGA => array('lub', 'lu'), + self::GANDA => array('lug', 'lg'), + self::LUISENO => array('lui'), + self::LUNDA => array('lun'), + self::LUO => array('luo'), + self::LUSHAI => array('lus'), + self::MACEDONIAN => array('mac', 'mkd', 'mk'), + self::MADURESE => array('mad'), + self::MAGAHI => array('mag'), + self::MARSHALLESE => array('mah'), + self::MAITHILI => array('mai'), + self::MAKASAR => array('mak'), + self::MALAYALAM => array('mal'), + self::MANDINGO => array('man'), + self::MAORI => array('mao', 'mri', 'mi'), + self::AUSTRONESIAN => array('map'), + self::MARATHI => array('mar', 'mr'), + self::MASAI => array('mas'), + self::MALAY => array('may', 'msa', 'ms'), + self::MOKSHA => array('mdf'), + self::MANDAR => array('mdr'), + self::MENDE => array('men'), + self::IRISH_MIDDLE => array('mga'), + self::MIKMAQ => array('mic'), + self::MINANGKABAU => array('min'), + self::UNCODED => array('mis'), + self::MON_KHMER => array('mkh'), + self::MALAGASY => array('mlg'), + self::MALTESE => array('mlt'), + self::MANCHU => array('mnc'), + self::MANIPURI => array('mni'), + self::MANOBO => array('mno'), + self::MOHAWK => array('moh'), + self::MONGOLIAN => array('mon', 'mn'), + self::MOSSI => array('mos'), + self::MULTIPLE => array('mul'), + self::MUNDA => array('mun'), + self::CREEK => array('mus'), + self::MIRANDESE => array('mwl'), + self::MARWARI => array('mwr'), + self::MAYAN => array('myn'), + self::ERZYA => array('myv'), + self::NAHUATL => array('nah'), + self::AMERICAN_INDIAN_NORTH => array('nai'), + self::NEAPOLITAN => array('nap'), + self::NAURU => array('nau', 'na'), + self::NAVAJO => array('nav', 'nv'), + self::NDEBELE_SOUTH => array('nbl', 'nr'), + self::NDEBELE_NORTH => array('nde', 'nd'), + self::NDONGA => array('ndo', 'ng'), + self::LOW_GERMAN => array('nds'), + self::NEPALI => array('nep', 'ne'), + self::NEPAL_BHASA => array('new'), + self::NIAS => array('nia'), + self::NIGER_KORDOFANIAN => array('nic'), + self::NIUEAN => array('niu'), + self::NORWEGIAN_NYNORSK => array('nno', 'nn'), + self::BOKMAL => array('nob', 'nb'), + self::NOGAI => array('nog'), + self::NORSE_OLD => array('non'), + self::NORWEGIAN => array('nor', 'no'), + self::NKO => array('nqo'), + self::PEDI => array('nso'), + self::NUBIAN => array('nub'), + self::CLASSICAL_NEWARI => array('nwc'), + self::CHICHEWA => array('nya', 'ny'), + self::NYAMWEZI => array('nym'), + self::NYANKOLE => array('nyn'), + self::NYORO => array('nyo'), + self::NZIMA => array('nzi'), + self::OCCITAN => array('oci', 'oc'), + self::OJIBWA => array('oji', 'oj'), + self::ORIYA => array('ori', 'or'), + self::OROMO => array('orm', 'om'), + self::OSAGE => array('osa'), + self::OSSETIAN => array('oss', 'os'), + self::OTTOMAN => array('ota'), + self::OTOMIAN => array('oto'), + self::PAPUAN => array('paa'), + self::PANGASINAN => array('pag'), + self::PAHLAVI => array('pal'), + self::PAMPANGA => array('pam'), + self::PANJABI => array('pan', 'pa'), + self::PAPIAMENTO => array('pap'), + self::PALAUAN => array('pau'), + self::PERSIAN_OLD => array('peo'), + self::PHILIPPINE => array('phi'), + self::PHOENICIAN => array('phn'), + self::PALI => array('pli', 'pi'), + self::POLISH => array('pol', 'pl'), + self::POHNPEIAN => array('pon'), + self::PORTUGUESE => array('por', 'pt'), + self::PRAKRIT => array('pra'), + self::PROVENCAL => array('pro'), + self::PUSHTO => array('pus', 'ps'), + self::QUECHUA => array('que', 'qu'), + self::RAJASTHANI => array('raj'), + self::RAPANUI => array('rap'), + self::RAROTONGAN => array('rar'), + self::ROMANCE => array('roa'), + self::ROMANSH => array('roh', 'rm'), + self::ROMANY => array('rom'), + self::ROMANIAN => array('rum', 'ron', 'ro'), + self::RUNDI => array('run', 'rn'), + self::AROMANIAN => array('rup'), + self::RUSSIAN => array('rus', 'ru'), + self::SANDAWE => array('sad'), + self::SANGO => array('sag', 'sg'), + self::YAKUT => array('sah'), + self::AMERICAN_INDIAN_SOUTH => array('sai'), + self::SALISHAN => array('sal'), + self::SAMARITAN => array('sam'), + self::SANSKRIT => array('san', 'sa'), + self::SASAK => array('sas'), + self::SANTALI => array('sat'), + self::SICILIAN => array('scn'), + self::SCOTS => array('sco'), + self::SELKUP => array('sel'), + self::SEMITIC => array('sem'), + self::IRISH_OLD => array('sga'), + self::SIGN => array('sgn'), + self::SHAN => array('shn'), + self::SIDAMO => array('sid'), + self::SINHALA => array('sin', 'si'), + self::SIOUAN => array('sio'), + self::SINO_TIBETAN => array('sit'), + self::SLAVIC => array('sla'), + self::SLOVAK => array('slo', 'slk', 'sk'), + self::SLOVENIAN => array('slv', 'sl'), + self::SAMI_SOUTHERN => array('sma'), + self::SAMI_NORTHERN => array('sme', 'se'), + self::SAMI => array('smi'), + self::SAMI_LULE => array('smj'), + self::SAMI_IRARI => array('smn'), + self::SAMOAN => array('smo', 'sm'), + self::SAMI_SKOLT => array('sms'), + self::SHONA => array('sna', 'sn'), + self::SINDHI => array('snd', 'sd'), + self::SONINKE => array('snk'), + self::SOGDIAN => array('sog'), + self::SOMALI => array('som', 'so'), + self::SONGHAI => array('son'), + self::SOTHO_SOUTHERN => array('sot', 'st'), + self::SPANISH => array('spa', 'es'), + self::SARDINIAN => array('srd', 'sc'), + self::SRANAN_TONGO => array('sm'), + self::SERBIAN => array('srp', 'sr'), + self::SERER => array('srr'), + self::NILO_SAHARAN => array('ssa'), + self::SWATI => array('ssw', 'ss'), + self::SUKUMA => array('suk'), + self::SUNDANESE => array('sun', 'su'), + self::SUSU => array('sus'), + self::SUMERIAN => array('sux'), + self::SWAHILI => array('swa', 'sw'), + self::SWEDISH => array('swe', 'sv'), + self::SYRIAC_CLASSICAL => array('syc'), + self::SYRIAC => array('syr'), + self::TAHITIAN => array('tah', 'ty'), + self::TAI => array('tai'), + self::TAMIL => array('tam', 'ta'), + self::TATAR => array('tat', 'tt'), + self::TELUGU => array('tel', 'te'), + self::TIMNE => array('tem'), + self::TERENO => array('ter'), + self::TETUM => array('tet'), + self::TAJIK => array('tgk', 'tg'), + self::TAGALOG => array('tgl', 'tl'), + self::THAI => array('tha', 'th'), + self::TIGRE => array('tig'), + self::TIGRINYA => array('tir', 'ti'), + self::TIV => array('tiv'), + self::TOKELAU => array('tkl'), + self::KLINGON => array('tlh'), + self::TLINGIT => array('tli'), + self::TAMASHEK => array('tmh'), + self::TONGA_NYASA => array('tog'), + self::TONGA_ISLANDS => array('ton', 'to'), + self::TOK_PISIN => array('tpi'), + self::TSIMSHIAN => array('tsi'), + self::TSWANA => array('tsn', 'tn'), + self::TSONGA => array('tso', 'ts'), + self::TURKMEN => array('tuk', 'tk'), + self::TUMBUKA => array('tum'), + self::TUPI => array('tup'), + self::TURKISH => array('tur', 'tr'), + self::ALTAIC => array('tut'), + self::TUVALU => array('tvl'), + self::TWI => array('twi', 'tw'), + self::TUVINIAN => array('tyv'), + self::UDMURT => array('udm'), + self::UGARITIC => array('uga'), + self::UIGHUR => array('uig', 'ug'), + self::UKRAINIAN => array('ukr', 'uk'), + self::UMBUNDU => array('umb'), + self::UNDETERMINED => array('und'), + self::URDU => array('urd', 'ur'), + self::UZBEK => array('uzb', 'uz'), + self::VAI => array('vai'), + self::VENDA => array('ven', 've'), + self::VIETNAMESE => array('vie', 'vi'), + self::VOLAPUK => array('vol', 'vo'), + self::VOTIC => array('vot'), + self::WAKASHAN => array('wak'), + self::WOLAITTA => array('wal'), + self::WARAY => array('war'), + self::WASHO => array('was'), + self::SORBIAN => array('wen'), + self::WALLOON => array('wln', 'wa'), + self::WOLOF => array('wol', 'wo'), + self::KALMYK => array('xal'), + self::XHOSA => array('xho', 'xh'), + self::YAO => array('yao'), + self::YAPESE => array('yap'), + self::YIDDISH => array('yid', 'yi'), + self::YORUBA => array('yor', 'yo'), + self::YUPIK => array('ypk'), + self::ZAPOTEC => array('zap'), + self::BLISSYMBOLS => array('zbl'), + self::ZENAGA => array('zen'), + self::MOROCCAN_TAMAZIGHT => array('zgh'), + self::ZHUANG => array('zha', 'za'), + self::ZANDE => array('znd'), + self::ZULU => array('zul', 'zu'), + self::ZUNI => array('zun'), + self::NOT_APPLICABLE => array('zxx'), + self::ZAZA => array('zza'), + ); + + private static $s_names = array( + self::ENGLISH_US => array('US English'), + self::ENGLISH => array('English'), + self::ENGLISH_CA => array('Canadian English'), + self::ENGLISH_GB => array('British English'), + self::AFAR => array('Afar'), + self::ABKHAZIAN => array('Abkhazian'), + self::ACHINESE => array('Achinese'), + self::ACOLI => array('Acoli'), + self::ADANGME => array('Adangme'), + self::ADYGHE => array('Adyghe'), + self::AFRO_ASIATIC => array('Afro-Asiatic', 'Adygei'), + self::AFRIHILI => array('Afrihili'), + self::AFRIKAANS => array('Afrikaans'), + self::AINU => array('Ainu'), + self::AKAN => array('Akan'), + self::AKKADIAN => array('Akkadian'), + self::ALBANIAN => array('Albanian'), + self::ALEUT => array('Aleut'), + self::ALGONQUIAN => array('Algonquian'), + self::SOUTHERN_ALTAI => array('Southern Altai'), + self::AMHARIC => array('Amharic'), + self::ENGLISH_OLD => array('Old English'), + self::ANGIKA => array('Angika'), + self::APACHE => array('Apache'), + self::ARABIC => array('Arabic'), + self::ARAMAIC => array('Official Aramaic', 'Imperial Aramaic'), + self::ARAGONESE => array('Aragonese'), + self::ARMENIAN => array('Armenian'), + self::MAPUDUNGUN => array('Mapudungun', 'Mapuche'), + self::ARAPAHO => array('Arapaho'), + self::ARTIFICIAL => array('Artificial'), + self::ARAWAK => array('Arawak'), + self::ASSAMESE => array('Assamese'), + self::ASTURIAN => array('Asturian', 'Bable', 'Leonese', 'Asturleonese'), + self::ATHAPASCAN => array('Athapascan'), + self::AUSTRALIAN => array('Australian'), + self::AVARIC => array('Avaric'), + self::AVESTAN => array('Avestan'), + self::AWADHI => array('Awadhi'), + self::AYMARA => array('Aymara'), + self::AZERBAIJANI => array('Azerbaijani'), + self::BANDA => array('Banda'), + self::BAMILEKE => array('Bamileke'), + self::BASHKIR => array('Bashkir'), + self::BALUCHI => array('Baluchi'), + self::BAMBARA => array('Bambara'), + self::BALINESE => array('Balinese'), + self::BASQUE => array('Basque'), + self::BASA => array('Basa'), + self::BALTIC => array('Baltic'), + self::BEJA => array('Beja'), + self::BELARUSIAN => array('Belarusian'), + self::BEMBA => array('Bemba'), + self::BENGALI => array('Bengali'), + self::BERBER => array('Berber'), + self::BHOJPURI => array('Bhojpuri'), + self::BIHARI => array('Bihari'), + self::BIKOL => array('Bikol'), + self::BINI => array('Bini', 'Edo'), + self::BISLAMA => array('Bislama'), + self::SIKSIKA => array('Siksika'), + self::BANTU => array('Bantu'), + self::TIBETAN => array('Tibetan'), + self::BOSNIAN => array('Bosnian'), + self::BRAJ => array('Braj'), + self::BRETON => array('Breton'), + self::BATAK => array('Batak'), + self::BURIAT => array('Buriat'), + self::BUGINESE => array('Buginese'), + self::BULGARIAN => array('Bulgarian'), + self::BURMESE => array('Burmese'), + self::BLIN => array('Blin', 'Bilin'), + self::CADDO => array('Caddo'), + self::AMERICAN_INDIAN_CENTRAL => array('Central American Indian'), + self::GALIBI_CARIB => array('Galibi Carib'), + self::CATALAN => array('Catalan', 'Valencian'), + self::CAUCASIAN => array('Caucasian'), + self::CEBUANO => array('Cebuano'), + self::CELTIC => array('Celtic'), + self::CZECH => array('Czech'), + self::CHAMORRO => array('Chamorro'), + self::CHIBCHA => array('Chibcha'), + self::CHECHEN => array('Chechen'), + self::CHAGATAI => array('Chagatai'), + self::CHINESE => array('Chinese'), + self::CHUUKESE => array('Chuukese'), + self::MARI => array('Mari'), + self::CHINOOK_JARGON => array('Chinook jargon'), + self::CHOCTAW => array('Choctaw'), + self::CHIPEWYAN => array('Chipewyan', 'Dene Suline'), + self::CHEROKEE => array('Cherokee'), + self::CHURCH_SLAVIC => array('Church Slavic', 'Old Slavonic', 'Church Slavonic', 'Old Bulgarian', 'Old Church Slavonic'), + self::CHUVASH => array('Chuvash'), + self::CHEYENNE => array('Cheyenne'), + self::CHAMIC => array('Chamic'), + self::COPTIC => array('Coptic'), + self::CORNISH => array('Cornish'), + self::CORSICAN => array('Corsican'), + self::CREOLES_ENGLISH => array('Creoles and Pidgins, English Based'), + self::CREOLES_FRENCH => array('Creoles and Pidgins, French Based'), + self::CREOLES_PORTUGESE => array('Creoles and Pidgins, Portugese Based'), + self::CREE => array('Cree'), + self::CRIMEAN_TATAR => array('Crimean Tatar', 'Crimean Turkish'), + self::CREOLES => array('Creoles and Pidgins'), + self::KASHUBIAN => array('Kashubian'), + self::CUSHITIC => array('Cushitic'), + self::WELSH => array('Welsh'), + self::DAKOTA => array('Dakota'), + self::DANISH => array('Danish'), + self::DARGWA => array('Dargwa'), + self::LAND_DAYAK => array('Land Dayak'), + self::DELAWARE => array('Delaware'), + self::SLAVE => array('Athapascan Slave'), + self::GERMAN => array('German'), + self::DOGRIB => array('Dogrib'), + self::DINKA => array('Dinka'), + self::DIVEHI => array('Divehi', 'Dhivehi', 'Maldivian'), + self::DOGRI => array('Dogri'), + self::DRAVIDIAN => array('Dravidian'), + self::LOWER_SORBIAN => array('Lower Sorbian'), + self::DUALA => array('Duala'), + self::DUTCH_MIDDLE => array('Middle Dutch'), + self::DUTCH_FLEMISH => array('Dutch', 'Flemish'), + self::DYULA => array('Dyula'), + self::DZONGKHA => array('Dzongkha'), + self::EFIK => array('Efik'), + self::EGYPTIAN => array('Ancient Egyptian'), + self::EKAJUK => array('Ekajuk'), + self::GREEK_MODERN => array('Modern Greek'), + self::ELAMITE => array('Elamite'), + self::ENGLISH_MIDDLE => array('Middle English'), + self::ESPERANTO => array('Esperanto'), + self::ESTONIAN => array('Estonian'), + self::EWE => array('Ewe'), + self::EWONDO => array('Ewondo'), + self::FANG => array('Fang'), + self::FAROESE => array('Faroese'), + self::PERSIAN => array('Persian'), + self::FANTI => array('Fanti'), + self::FIJIAN => array('Fijian'), + self::FILIPINO => array('Filipino', 'Pilipino'), + self::FINNISH => array('Finnish'), + self::FINNO_UGRIAN => array('Finno-Ugrian '), + self::FON => array('Fon'), + self::FRENCH => array('French'), + self::FRENCH_MIDDLE => array('Middle French'), + self::FRENCH_OLD => array('Old French'), + self::FRISIAN_NORTHERN => array('Northern Frisian'), + self::FRISIAN_EASTERN => array('Eastern Frisian'), + self::FRISIAN_WESTERN => array('Southern Frisian'), + self::FULAH => array('Fulah'), + self::FRIULIAN => array('Friulian'), + self::GA => array('Ga'), + self::GAYO => array('Gayo'), + self::GBAYA => array('Gbaya'), + self::GERMANIC => array('Germanic'), + self::GEORGIAN => array('Georgian'), + self::GEEZ => array('Geez'), + self::GILBERTESE => array('Gilbertese'), + self::GAELIC => array('Gaelic', 'Scottish Gaelic'), + self::IRISH => array('Irish'), + self::GALICIAN => array('Galician'), + self::MANX => array('Manx'), + self::GERMAN_MIDDLE_HIGH => array('Middle High German'), + self::GERMAN_OLD_HIGH => array('Old High German'), + self::GONDI => array('Gondi'), + self::GORONTALO => array('Gorontalo'), + self::GOTHIC => array('Gothic'), + self::GREBO => array('Grebo'), + self::GREEK_ANCIENT => array('Ancient Greek'), + self::GUARANI => array('Guarani'), + self::GERMAN_SWISS => array('Swiss German', 'Alemannic', 'Alsatian'), + self::GUJARATI => array('Gujarati'), + self::GWICHIN => array('Gwich\'in'), + self::HAIDA => array('Haida'), + self::HAITIAN => array('Haitian', 'Haitian Creole'), + self::HAUSA => array('Hausa'), + self::HAWAIIAN => array('Hawaiian'), + self::HEBREW => array('Hebrew'), + self::HERERO => array('Herero'), + self::HILIGAYNON => array('Hiligaynon'), + self::HIMACHALI => array('Himachali', 'Western Pahari'), + self::HINDI => array('Hindi'), + self::HITTITE => array('Hittite'), + self::HMONG => array('Hmong', 'Mong'), + self::HIRI_MOTU => array('Hiri Motu'), + self::CROATIAN => array('Croatian'), + self::SORBIAN_UPPER => array('Upper Sorbian'), + self::HUNGARIAN => array('Hungarian'), + self::HUPA => array('Hupa'), + self::IBAN => array('Iban'), + self::IGBO => array('Igbo'), + self::ICELANDIC => array('Icelandic'), + self::IDO => array('Ido'), + self::SICHUAN_YI => array('Sichuan Yi', 'Nuosu'), + self::IJO => array('Ijo'), + self::INUKTITUT => array('Inuktitut'), + self::INTERLINGUE => array('Interlingue'), + self::ILOKO => array('Iloko'), + self::INTERLINGUA => array('Interlingua'), + self::INDIC => array('Indic'), + self::INDONESIAN => array('Indonesian'), + self::INDO_EUROPEAN => array('Indo-European'), + self::INGUSH => array('Ingush'), + self::INUPIAQ => array('Inupiaq'), + self::IRANIAN => array('Iranian'), + self::IROQUOIAN => array('Iroquoian'), + self::ITALIAN => array('Italian'), + self::JAVANESE => array('Javanese'), + self::LOJBAN => array('Lojban'), + self::JAPANESE => array('Japanese'), + self::JUDEO_PERSIAN => array('Judeo-Persian'), + self::JUDEO_ARABIC => array('Judeo-Arabic'), + self::KARA_KALPAK => array('Kara-Kalpak'), + self::KABYLE => array('Kabyle'), + self::KACHIN => array('Kachin', 'Jingpho'), + self::KALAALLISUT => array('Kalaallisut', 'Greenlandic'), + self::KAMBA => array('Kamba'), + self::KANNADA => array('Kannada'), + self::KAREN => array('Karen'), + self::KASHMIRI => array('Kashmiri'), + self::KANURI => array('Kanuri'), + self::KAWI => array('Kawi'), + self::KAZAKH => array('Kazakh'), + self::KABARDIAN => array('Kabardian'), + self::KHASI => array('Khasi'), + self::KHOISAN => array('Khoisan'), + self::CENTRAL_KHMER => array('Central Khmer'), + self::KHOTANESE => array('Khotanese', 'Sakan'), + self::KIKUYU => array('Kikuyu', 'Gikuyu'), + self::KINYARWANDA => array('Kinyarwanda'), + self::KIRGHIZ => array('Kirghiz', 'Kyrgyz'), + self::KIMBUNDU => array('Kimbundu'), + self::KONKANI => array('Konkani'), + self::KOMI => array('Komi'), + self::KONGO => array('Kongo'), + self::KOREAN => array('Korean'), + self::KOSRAEAN => array('Kosraean'), + self::KPELLE => array('Kpelle'), + self::KARACHAY_BALKAR => array('Karachay-Balkar'), + self::KARELIAN => array('Karelian'), + self::KRU => array('Kru'), + self::KURUKH => array('Kurukh'), + self::KUANYAMA => array('Kuanyama', 'Kwanyama'), + self::KUMYK => array('Kumyk'), + self::KURDISH => array('Kurdish'), + self::KUTENAI => array('Kutenai'), + self::LADINO => array('Ladino'), + self::LAHNDA => array('Lahnda'), + self::LAMBA => array('Lamba'), + self::LAO => array('Lao'), + self::LATIN => array('Latin'), + self::LATVIAN => array('Latvian'), + self::LEZGHIAN => array('Lezghian'), + self::LIMBURGAN => array('Limburgan', 'Limburger', 'Limburgish'), + self::LINGALA => array('Lingala'), + self::LITHUANIAN => array('Lithuanian'), + self::MONGO => array('Mongo'), + self::LOZI => array('Lozi'), + self::LUXEMBOURGISH => array('Luxembourgish', 'Letzeburgesch'), + self::LUBA_LULUA => array('Luba-Lulua'), + self::LUBA_KATANGA => array('Luba-Katanga'), + self::GANDA => array('Ganda'), + self::LUISENO => array('Luiseno'), + self::LUNDA => array('Lunda'), + self::LUO => array('Luo'), + self::LUSHAI => array('Lushai'), + self::MACEDONIAN => array('Macedonian'), + self::MADURESE => array('Madurese'), + self::MAGAHI => array('Magahi'), + self::MARSHALLESE => array('Marshallese'), + self::MAITHILI => array('Maithili'), + self::MAKASAR => array('Makasar'), + self::MALAYALAM => array('Malayalam'), + self::MANDINGO => array('Mandingo'), + self::MAORI => array('Maori'), + self::AUSTRONESIAN => array('Austronesian'), + self::MARATHI => array('Marathi'), + self::MASAI => array('Masai'), + self::MALAY => array('Malay'), + self::MOKSHA => array('Moksha'), + self::MANDAR => array('Mandar'), + self::MENDE => array('Mende'), + self::IRISH_MIDDLE => array('Middle Irish'), + self::MIKMAQ => array('Mi\'kmaq', 'Micmac'), + self::MINANGKABAU => array('Minangkabau'), + self::UNCODED => array('Uncoded'), + self::MON_KHMER => array('Mon-Khmer'), + self::MALAGASY => array('Malagasy'), + self::MALTESE => array('Maltese'), + self::MANCHU => array('Manchu'), + self::MANIPURI => array('Manipuri'), + self::MANOBO => array('Manobo'), + self::MOHAWK => array('Mohawk'), + self::MONGOLIAN => array('Mongolian'), + self::MOSSI => array('Mossi'), + self::MULTIPLE => array('Multiple'), + self::MUNDA => array('Munda'), + self::CREEK => array('Creek'), + self::MIRANDESE => array('Mirandese'), + self::MARWARI => array('Marwari'), + self::MAYAN => array('Mayan'), + self::ERZYA => array('Erzya'), + self::NAHUATL => array('Nahuatl'), + self::AMERICAN_INDIAN_NORTH => array('North American Indian'), + self::NEAPOLITAN => array('Neapolitan'), + self::NAURU => array('Nauru'), + self::NAVAJO => array('Navajo', 'Navaho'), + self::NDEBELE_SOUTH => array('South Ndebele'), + self::NDEBELE_NORTH => array('North Ndebele'), + self::NDONGA => array('Ndonga'), + self::LOW_GERMAN => array('Low German', 'Low Saxon'), + self::NEPALI => array('Nepali'), + self::NEPAL_BHASA => array('Nepal Bhasa', 'Newari'), + self::NIAS => array('Nias'), + self::NIGER_KORDOFANIAN => array('Niger-Kordofanian'), + self::NIUEAN => array('Niuean'), + self::NORWEGIAN_NYNORSK => array('Norwegian Nynorsk'), + self::BOKMAL => array('Bokmål', 'Norwegian Bokmål'), + self::NOGAI => array('Nogai'), + self::NORSE_OLD => array('Old Norse'), + self::NORWEGIAN => array('Norwegian'), + self::NKO => array('N\'Ko'), + self::PEDI => array('Pedi', 'Sepedi', 'Northern Sotho'), + self::NUBIAN => array('Nubian'), + self::CLASSICAL_NEWARI => array('Classical Newari', 'Old Newari', 'Classical Nepal Bhasa'), + self::CHICHEWA => array('Chichewa', 'Chewa', 'Nyanja'), + self::NYAMWEZI => array('Nyamwezi'), + self::NYANKOLE => array('Nyankole'), + self::NYORO => array('Nyoro'), + self::NZIMA => array('Nzima'), + self::OCCITAN => array('Occitan'), + self::OJIBWA => array('Ojibwa'), + self::ORIYA => array('Oriya'), + self::OROMO => array('Oromo'), + self::OSAGE => array('Osage'), + self::OSSETIAN => array('Ossetian', 'Ossetic'), + self::OTTOMAN => array('Ottoman Turkish'), + self::OTOMIAN => array('Otomian'), + self::PAPUAN => array('Papuan'), + self::PANGASINAN => array('Pangasinan'), + self::PAHLAVI => array('Pahlavi'), + self::PAMPANGA => array('Pampanga', 'Kapampangan'), + self::PANJABI => array('Panjabi', 'Punjabi'), + self::PAPIAMENTO => array('Papiamento'), + self::PALAUAN => array('Palauan'), + self::PERSIAN_OLD => array('Old Persian'), + self::PHILIPPINE => array('Philippine'), + self::PHOENICIAN => array('Phoenician'), + self::PALI => array('Pali'), + self::POLISH => array('Polish'), + self::POHNPEIAN => array('Pohnpeian'), + self::PORTUGUESE => array('Portuguese'), + self::PRAKRIT => array('Prakrit'), + self::PROVENCAL => array('Old Provençal', 'Old Occitan'), + self::PUSHTO => array('Pushto', 'Pashto'), + self::QUECHUA => array('Quechua'), + self::RAJASTHANI => array('Rajasthani'), + self::RAPANUI => array('Rapanui'), + self::RAROTONGAN => array('Rarotongan', 'Cook Islands Maori'), + self::ROMANCE => array('Romance'), + self::ROMANSH => array('Romansh'), + self::ROMANY => array('Romany'), + self::ROMANIAN => array('Romanian', 'Moldavian', 'Moldovan'), + self::RUNDI => array('Rundi'), + self::AROMANIAN => array('Aromanian', 'Arumanian', 'Macedo-Romanian'), + self::RUSSIAN => array('Russian'), + self::SANDAWE => array('Sandawe'), + self::SANGO => array('Sango'), + self::YAKUT => array('Yakut'), + self::AMERICAN_INDIAN_SOUTH => array('South American Indian'), + self::SALISHAN => array('Salishan'), + self::SAMARITAN => array('Samaritan'), + self::SANSKRIT => array('Sanskrit'), + self::SASAK => array('Sasak'), + self::SANTALI => array('Santali'), + self::SICILIAN => array('Sicilian'), + self::SCOTS => array('Scots'), + self::SELKUP => array('Selkup'), + self::SEMITIC => array('Semitic'), + self::IRISH_OLD => array('Old Irish'), + self::SIGN => array('Sign Language'), + self::SHAN => array('Shan'), + self::SIDAMO => array('Sidamo'), + self::SINHALA => array('Sinhala', 'Sinhalese'), + self::SIOUAN => array('Siouan'), + self::SINO_TIBETAN => array('Sino-Tibetan'), + self::SLAVIC => array('Slavic'), + self::SLOVAK => array('Slovak'), + self::SLOVENIAN => array('Slovenian'), + self::SAMI_SOUTHERN => array('Southern Sami'), + self::SAMI_NORTHERN => array('Northern Sami'), + self::SAMI => array('Sami'), + self::SAMI_LULE => array('Lule Sami'), + self::SAMI_IRARI => array('Inari Sami'), + self::SAMOAN => array('Samoan'), + self::SAMI_SKOLT => array('Skolt Sami'), + self::SHONA => array('Shona'), + self::SINDHI => array('Sindhi'), + self::SONINKE => array('Soninke'), + self::SOGDIAN => array('Sogdian'), + self::SOMALI => array('Somali'), + self::SONGHAI => array('Songhai'), + self::SOTHO_SOUTHERN => array('Southern Sotho'), + self::SPANISH => array('Spanish', 'Castilian'), + self::SARDINIAN => array('Sardinian'), + self::SRANAN_TONGO => array('Sranan Tongo'), + self::SERBIAN => array('Serbian'), + self::SERER => array('Serer'), + self::NILO_SAHARAN => array('Nilo-Saharan'), + self::SWATI => array('Swati'), + self::SUKUMA => array('Sukuma'), + self::SUNDANESE => array('Sundanese'), + self::SUSU => array('Susu'), + self::SUMERIAN => array('Sumerian'), + self::SWAHILI => array('Swahili'), + self::SWEDISH => array('Swedish'), + self::SYRIAC_CLASSICAL => array('Classical Syriac'), + self::SYRIAC => array('Syriac'), + self::TAHITIAN => array('Tahitian'), + self::TAI => array('Tai'), + self::TAMIL => array('Tamil'), + self::TATAR => array('Tatar'), + self::TELUGU => array('Telugu'), + self::TIMNE => array('Timne'), + self::TERENO => array('Tereno'), + self::TETUM => array('Tetum'), + self::TAJIK => array('Tajik'), + self::TAGALOG => array('Tagalog'), + self::THAI => array('Thai'), + self::TIGRE => array('Tigre'), + self::TIGRINYA => array('Tigrinya'), + self::TIV => array('Tiv'), + self::TOKELAU => array('Tokelau'), + self::KLINGON => array('Klingon', 'tlhIngan-Hol'), + self::TLINGIT => array('Tlingit'), + self::TAMASHEK => array('Tamashek'), + self::TONGA_NYASA => array('Nyasa Tonga'), + self::TONGA_ISLANDS => array('Tonga Islands Tonga', 'to'), + self::TOK_PISIN => array('Tok Pisin'), + self::TSIMSHIAN => array('Tsimshian'), + self::TSWANA => array('Tswana'), + self::TSONGA => array('Tsonga'), + self::TURKMEN => array('Turkmen'), + self::TUMBUKA => array('Tumbuka'), + self::TUPI => array('Tupi'), + self::TURKISH => array('Turkish'), + self::ALTAIC => array('Altaic'), + self::TUVALU => array('Tuvalu'), + self::TWI => array('Twi'), + self::TUVINIAN => array('Tuvinian'), + self::UDMURT => array('Udmurt'), + self::UGARITIC => array('Ugaritic'), + self::UIGHUR => array('Uighur', 'Uyghur'), + self::UKRAINIAN => array('Ukrainian'), + self::UMBUNDU => array('Umbundu'), + self::UNDETERMINED => array('Undetermined'), + self::URDU => array('Urdu'), + self::UZBEK => array('Uzbek'), + self::VAI => array('Vai'), + self::VENDA => array('Venda'), + self::VIETNAMESE => array('Vietnamese'), + self::VOLAPUK => array('Volapük'), + self::VOTIC => array('Votic'), + self::WAKASHAN => array('Wakashan'), + self::WOLAITTA => array('Wolaitta', 'Wolaytta'), + self::WARAY => array('Waray'), + self::WASHO => array('Washo'), + self::SORBIAN => array('Sorbian'), + self::WALLOON => array('Walloon'), + self::WOLOF => array('Wolof'), + self::KALMYK => array('Kalmyk', 'Oirat'), + self::XHOSA => array('Xhosa'), + self::YAO => array('Yao'), + self::YAPESE => array('Yapese'), + self::YIDDISH => array('Yiddish'), + self::YORUBA => array('Yoruba'), + self::YUPIK => array('Yupik'), + self::ZAPOTEC => array('Zapotec'), + self::BLISSYMBOLS => array('Blissymbols', 'Blissymbolics', 'Bliss'), + self::ZENAGA => array('Zenaga'), + self::MOROCCAN_TAMAZIGHT => array('Standard Moroccan Tamazight'), + self::ZHUANG => array('Zhuang', 'Chuang'), + self::ZANDE => array('Zande'), + self::ZULU => array('Zulu'), + self::ZUNI => array('Zuni'), + self::NOT_APPLICABLE => array('No Linguistic Content', 'Not Applicable'), + self::ZAZA => array('Zaza', 'Dimili', 'Dimli', 'Kirdki', 'Kirmanjki', 'Zazaki'), + ); + + private static $s_ids = array( + 'en-us' => self::ENGLISH_US, + 'en' => self::ENGLISH, + 'eng' => self::ENGLISH, + 'en-ca' => self::ENGLISH_CA, + 'en-gb' => self::ENGLISH_GB, + 'aar' => self::AFAR, + 'aa' => self::AFAR, + 'abk' => self::ABKHAZIAN, + 'ab' => self::ABKHAZIAN, + 'ace' => self::ACHINESE, + 'ach' => self::ACOLI, + 'ada' => self::ADANGME, + 'ady' => self::ADYGHE, + 'afa' => self::AFRO_ASIATIC, + 'afh' => self::AFRIHILI, + 'afr' => self::AFRIKAANS, + 'af' => self::AFRIKAANS, + 'ain' => self::AINU, + 'aka' => self::AKAN, + 'ak' => self::AKAN, + 'akk' => self::AKKADIAN, + 'alb' => self::ALBANIAN, + 'sqi' => self::ALBANIAN, + 'sq' => self::ALBANIAN, + 'ale' => self::ALEUT, + 'alg' => self::ALGONQUIAN, + 'alt' => self::SOUTHERN_ALTAI, + 'amh' => self::AMHARIC, + 'am' => self::AMHARIC, + 'ang' => self::ENGLISH_OLD, + 'anp' => self::ANGIKA, + 'apa' => self::APACHE, + 'ara' => self::ARABIC, + 'arc' => self::ARAMAIC, + 'arg' => self::ARAGONESE, + 'arm' => self::ARMENIAN, + 'hye' => self::ARMENIAN, + 'hy' => self::ARMENIAN, + 'am' => self::MAPUDUNGUN, + 'arp' => self::ARAPAHO, + 'art' => self::ARTIFICIAL, + 'arw' => self::ARAWAK, + 'asm' => self::ASSAMESE, + 'as' => self::ASSAMESE, + 'ast' => self::ASTURIAN, + 'ath' => self::ATHAPASCAN, + 'aus' => self::AUSTRALIAN, + 'ava' => self::AVARIC, + 'av' => self::AVARIC, + 'ave' => self::AVESTAN, + 'ae' => self::AVESTAN, + 'awa' => self::AWADHI, + 'aym' => self::AYMARA, + 'ay' => self::AYMARA, + 'aze' => self::AZERBAIJANI, + 'az' => self::AZERBAIJANI, + 'bad' => self::BANDA, + 'bai' => self::BAMILEKE, + 'bak' => self::BASHKIR, + 'ba' => self::BASHKIR, + 'bal' => self::BALUCHI, + 'bam' => self::BAMBARA, + 'ba' => self::BAMBARA, + 'ban' => self::BALINESE, + 'baq' => self::BASQUE, + 'eus' => self::BASQUE, + 'eu' => self::BASQUE, + 'bas' => self::BASA, + 'bat' => self::BALTIC, + 'bej' => self::BEJA, + 'bel' => self::BELARUSIAN, + 'be' => self::BELARUSIAN, + 'bem' => self::BEMBA, + 'ben' => self::BENGALI, + 'bn' => self::BENGALI, + 'ber' => self::BERBER, + 'bho' => self::BHOJPURI, + 'bih' => self::BIHARI, + 'bh' => self::BIHARI, + 'bik' => self::BIKOL, + 'bin' => self::BINI, + 'bis' => self::BISLAMA, + 'bi' => self::BISLAMA, + 'bla' => self::SIKSIKA, + 'bnt' => self::BANTU, + 'tib' => self::TIBETAN, + 'bod' => self::TIBETAN, + 'bo' => self::TIBETAN, + 'bos' => self::BOSNIAN, + 'bs' => self::BOSNIAN, + 'bra' => self::BRAJ, + 'bre' => self::BRETON, + 'btk' => self::BATAK, + 'bua' => self::BURIAT, + 'bug' => self::BUGINESE, + 'bul' => self::BULGARIAN, + 'bur' => self::BURMESE, + 'mya' => self::BURMESE, + 'my' => self::BURMESE, + 'byn' => self::BLIN, + 'cad' => self::CADDO, + 'cai' => self::AMERICAN_INDIAN_CENTRAL, + 'car' => self::GALIBI_CARIB, + 'cat' => self::CATALAN, + 'ca' => self::CATALAN, + 'cau' => self::CAUCASIAN, + 'ceb' => self::CEBUANO, + 'cel' => self::CELTIC, + 'cze' => self::CZECH, + 'ces' => self::CZECH, + 'cs' => self::CZECH, + 'cha' => self::CHAMORRO, + 'ch' => self::CHAMORRO, + 'chb' => self::CHIBCHA, + 'che' => self::CHECHEN, + 'ce' => self::CHECHEN, + 'chg' => self::CHAGATAI, + 'chi' => self::CHINESE, + 'zho' => self::CHINESE, + 'zh' => self::CHINESE, + 'chk' => self::CHUUKESE, + 'chm' => self::MARI, + 'chn' => self::CHINOOK_JARGON, + 'cho' => self::CHOCTAW, + 'chp' => self::CHIPEWYAN, + 'chr' => self::CHEROKEE, + 'chu' => self::CHURCH_SLAVIC, + 'cu' => self::SLAVIC, + 'chv' => self::CHUVASH, + 'cv' => self::CHUVASH, + 'chy' => self::CHEYENNE, + 'cmc' => self::CHAMIC, + 'cop' => self::COPTIC, + 'cor' => self::CORNISH, + 'cos' => self::CORSICAN, + 'co' => self::CORSICAN, + 'cpe' => self::CREOLES_ENGLISH, + 'cpf' => self::CREOLES_FRENCH, + 'cpp' => self::CREOLES_PORTUGESE, + 'cre' => self::CREE, + 'cr' => self::CREE, + 'crh' => self::CRIMEAN_TATAR, + 'crp' => self::CREOLES, + 'csb' => self::KASHUBIAN, + 'cus' => self::CUSHITIC, + 'wel' => self::WELSH, + 'cym' => self::WELSH, + 'cy' => self::WELSH, + 'dak' => self::DAKOTA, + 'dan' => self::DANISH, + 'da' => self::DANISH, + 'dar' => self::DARGWA, + 'day' => self::LAND_DAYAK, + 'del' => self::DELAWARE, + 'den' => self::SLAVE, + 'ger' => self::GERMAN, + 'deu' => self::GERMAN, + 'de' => self::GERMAN, + 'dgr' => self::DOGRIB, + 'din' => self::DINKA, + 'div' => self::DIVEHI, + 'dv' => self::DIVEHI, + 'doi' => self::DOGRI, + 'dra' => self::DRAVIDIAN, + 'dsb' => self::LOWER_SORBIAN, + 'dua' => self::DUALA, + 'dum' => self::DUTCH_MIDDLE, + 'dut' => self::DUTCH_FLEMISH, + 'nld' => self::DUTCH_FLEMISH, + 'nl' => self::DUTCH_FLEMISH, + 'dyu' => self::DYULA, + 'dzo' => self::DZONGKHA, + 'dz' => self::DZONGKHA, + 'efi' => self::EFIK, + 'egy' => self::EGYPTIAN, + 'eka' => self::EKAJUK, + 'gre' => self::GREEK_MODERN, + 'ell' => self::GREEK_MODERN, + 'el' => self::GREEK_MODERN, + 'elx' => self::ELAMITE, + 'enm' => self::ENGLISH_MIDDLE, + 'epo' => self::ESPERANTO, + 'eo' => self::ESPERANTO, + 'est' => self::ESTONIAN, + 'et' => self::ESTONIAN, + 'ewe' => self::EWE, + 'ew' => self::EWE, + 'ewo' => self::EWONDO, + 'fan' => self::FANG, + 'fao' => self::FAROESE, + 'fo' => self::FAROESE, + 'per' => self::PERSIAN, + 'fas' => self::PERSIAN, + 'fa' => self::PERSIAN, + 'fat' => self::FANTI, + 'fij' => self::FIJIAN, + 'fj' => self::FIJIAN, + 'fil' => self::FILIPINO, + 'fin' => self::FINNISH, + 'fi' => self::FINNISH, + 'fiu' => self::FINNO_UGRIAN, + 'fon' => self::FON, + 'fre' => self::FRENCH, + 'fra' => self::FRENCH, + 'fr' => self::FRENCH, + 'frm' => self::FRENCH_MIDDLE, + 'fro' => self::FRENCH_OLD, + 'frr' => self::FRISIAN_NORTHERN, + 'frs' => self::FRISIAN_EASTERN, + 'fry' => self::FRISIAN_WESTERN, + 'fy' => self::FRISIAN_WESTERN, + 'ful' => self::FULAH, + 'ff' => self::FULAH, + 'fur' => self::FRIULIAN, + 'gaa' => self::GA, + 'gay' => self::GAYO, + 'gba' => self::GBAYA, + 'gem' => self::GERMANIC, + 'geo' => self::GEORGIAN, + 'kat' => self::GEORGIAN, + 'ka' => self::GEORGIAN, + 'gez' => self::GEEZ, + 'gil' => self::GILBERTESE, + 'gla' => self::GAELIC, + 'ga' => self::GAELIC, + 'gle' => self::IRISH, + 'ga' => self::IRISH, + 'glg' => self::GALICIAN, + 'gl' => self::GALICIAN, + 'glv' => self::MANX, + 'gv' => self::MANX, + 'gmh' => self::GERMAN_MIDDLE_HIGH, + 'goh' => self::GERMAN_OLD_HIGH, + 'gon' => self::GONDI, + 'gor' => self::GORONTALO, + 'got' => self::GOTHIC, + 'grb' => self::GREBO, + 'grc' => self::GREEK_ANCIENT, + 'grm' => self::GUARANI, + 'gn' => self::GUARANI, + 'gsw' => self::GERMAN_SWISS, + 'guj' => self::GUJARATI, + 'gu' => self::GUJARATI, + 'gwi' => self::GWICHIN, + 'hai' => self::HAIDA, + 'hat' => self::HAITIAN, + 'ht' => self::HAITIAN, + 'hau' => self::HAUSA, + 'ha' => self::HAUSA, + 'haw' => self::HAWAIIAN, + 'heb' => self::HEBREW, + 'he' => self::HEBREW, + 'her' => self::HERERO, + 'hz' => self::HERERO, + 'hil' => self::HILIGAYNON, + 'him' => self::HIMACHALI, + 'hin' => self::HINDI, + 'hi' => self::HINDI, + 'hit' => self::HITTITE, + 'hmn' => self::HMONG, + 'hmo' => self::HIRI_MOTU, + 'ho' => self::HIRI_MOTU, + 'hrv' => self::CROATIAN, + 'hr' => self::CROATIAN, + 'hsb' => self::SORBIAN_UPPER, + 'hun' => self::HUNGARIAN, + 'hu' => self::HUNGARIAN, + 'hup' => self::HUPA, + 'iba' => self::IBAN, + 'ibo' => self::IGBO, + 'ig' => self::IGBO, + 'ice' => self::ICELANDIC, + 'isl' => self::ICELANDIC, + 'is' => self::ICELANDIC, + 'ido' => self::IDO, + 'io' => self::IDO, + 'iii' => self::SICHUAN_YI, + 'ii' => self::SICHUAN_YI, + 'ijo' => self::IJO, + 'iku' => self::INUKTITUT, + 'iu' => self::INUKTITUT, + 'ile' => self::INTERLINGUE, + 'ie' => self::INTERLINGUE, + 'ilo' => self::ILOKO, + 'ina' => self::INTERLINGUA, + 'ia' => self::INTERLINGUA, + 'inc' => self::INDIC, + 'ind' => self::INDONESIAN, + 'id' => self::INDONESIAN, + 'ine' => self::INDO_EUROPEAN, + 'inh' => self::INGUSH, + 'ipk' => self::INUPIAQ, + 'ik' => self::INUPIAQ, + 'ira' => self::IRANIAN, + 'iro' => self::IROQUOIAN, + 'ita' => self::ITALIAN, + 'it' => self::ITALIAN, + 'jav' => self::JAVANESE, + 'jv' => self::JAVANESE, + 'jbo' => self::LOJBAN, + 'jpn' => self::JAPANESE, + 'ja' => self::JAPANESE, + 'jpr' => self::JUDEO_PERSIAN, + 'jrb' => self::JUDEO_ARABIC, + 'kaa' => self::KARA_KALPAK, + 'kab' => self::KABYLE, + 'kac' => self::KACHIN, + 'kal' => self::KALAALLISUT, + 'kl' => self::KALAALLISUT, + 'kam' => self::KAMBA, + 'kan' => self::KANNADA, + 'kn' => self::KANNADA, + 'kar' => self::KAREN, + 'kas' => self::KASHMIRI, + 'ks' => self::KASHMIRI, + 'kau' => self::KANURI, + 'kk' => self::KANURI, + 'kaw' => self::KAWI, + 'kaz' => self::KAZAKH, + 'kz' => self::KAZAKH, + 'kbd' => self::KABARDIAN, + 'kha' => self::KHASI, + 'khi' => self::KHOISAN, + 'khm' => self::CENTRAL_KHMER, + 'km' => self::CENTRAL_KHMER, + 'kho' => self::KHOTANESE, + 'kik' => self::KIKUYU, + 'ki' => self::KIKUYU, + 'kin' => self::KINYARWANDA, + 'rw' => self::KINYARWANDA, + 'kir' => self::KIRGHIZ, + 'ky' => self::KIRGHIZ, + 'kmb' => self::KIMBUNDU, + 'kok' => self::KONKANI, + 'kom' => self::KOMI, + 'kv' => self::KOMI, + 'kon' => self::KONGO, + 'kg' => self::KONGO, + 'kor' => self::KOREAN, + 'ko' => self::KOREAN, + 'kos' => self::KOSRAEAN, + 'kpe' => self::KPELLE, + 'krc' => self::KARACHAY_BALKAR, + 'krl' => self::KARELIAN, + 'kro' => self::KRU, + 'kru' => self::KURUKH, + 'kua' => self::KUANYAMA, + 'kj' => self::KUANYAMA, + 'kum' => self::KUMYK, + 'kur' => self::KURDISH, + 'ku' => self::KURDISH, + 'kut' => self::KUTENAI, + 'lad' => self::LADINO, + 'lah' => self::LAHNDA, + 'lam' => self::LAMBA, + 'lao' => self::LAO, + 'lo' => self::LAO, + 'lat' => self::LATIN, + 'la' => self::LATIN, + 'lav' => self::LATVIAN, + 'la' => self::LATVIAN, + 'lez' => self::LEZGHIAN, + 'lv' => self::LEZGHIAN, + 'lim' => self::LIMBURGAN, + 'li' => self::LIMBURGAN, + 'lin' => self::LINGALA, + 'li' => self::LINGALA, + 'lit' => self::LITHUANIAN, + 'lt' => self::LITHUANIAN, + 'lol' => self::MONGO, + 'loz' => self::LOZI, + 'ltz' => self::LUXEMBOURGISH, + 'lb' => self::LUXEMBOURGISH, + 'lua' => self::LUBA_LULUA, + 'lub' => self::LUBA_KATANGA, + 'lu' => self::LUBA_KATANGA, + 'lug' => self::GANDA, + 'lg' => self::GANDA, + 'lui' => self::LUISENO, + 'lun' => self::LUNDA, + 'luo' => self::LUO, + 'lus' => self::LUSHAI, + 'mac' => self::MACEDONIAN, + 'mkd' => self::MACEDONIAN, + 'mk' => self::MACEDONIAN, + 'mad' => self::MADURESE, + 'mag' => self::MAGAHI, + 'mah' => self::MARSHALLESE, + 'mh' => self::MARSHALLESE, + 'mai' => self::MAITHILI, + 'mak' => self::MAKASAR, + 'mal' => self::MALAYALAM, + 'ml' => self::MALAYALAM, + 'man' => self::MANDINGO, + 'mao' => self::MAORI, + 'mri' => self::MAORI, + 'mi' => self::MAORI, + 'map' => self::AUSTRONESIAN, + 'mar' => self::MARATHI, + 'mr' => self::MARATHI, + 'mas' => self::MASAI, + 'may' => self::MALAY, + 'msa' => self::MALAY, + 'ms' => self::MALAY, + 'mdf' => self::MOKSHA, + 'mdr' => self::MANDAR, + 'men' => self::MENDE, + 'mga' => self::IRISH_MIDDLE, + 'mic' => self::MIKMAQ, + 'min' => self::MINANGKABAU, + 'mis' => self::UNCODED, + 'mkh' => self::MON_KHMER, + 'mlg' => self::MALAGASY, + 'mg' => self::MALAGASY, + 'mlt' => self::MALTESE, + 'mt' => self::MALTESE, + 'mnc' => self::MANCHU, + 'mni' => self::MANIPURI, + 'mno' => self::MANOBO, + 'moh' => self::MOHAWK, + 'mon' => self::MONGOLIAN, + 'mn' => self::MONGOLIAN, + 'mos' => self::MOSSI, + 'mul' => self::MULTIPLE, + 'mun' => self::MUNDA, + 'mus' => self::CREEK, + 'mwl' => self::MIRANDESE, + 'mwr' => self::MARWARI, + 'myn' => self::MAYAN, + 'myv' => self::ERZYA, + 'nah' => self::NAHUATL, + 'nai' => self::AMERICAN_INDIAN_NORTH, + 'nap' => self::NEAPOLITAN, + 'nau' => self::NAURU, + 'na' => self::NAURU, + 'nav' => self::NAVAJO, + 'nv' => self::NAVAJO, + 'nbl' => self::NDEBELE_SOUTH, + 'nr' => self::NDEBELE_SOUTH, + 'nde' => self::NDEBELE_NORTH, + 'nd' => self::NDEBELE_NORTH, + 'ndo' => self::NDONGA, + 'ng' => self::NDONGA, + 'nds' => self::LOW_GERMAN, + 'nep' => self::NEPALI, + 'ne' => self::NEPALI, + 'new' => self::NEPAL_BHASA, + 'nia' => self::NIAS, + 'nic' => self::NIGER_KORDOFANIAN, + 'niu' => self::NIUEAN, + 'nno' => self::NORWEGIAN_NYNORSK, + 'nn' => self::NORWEGIAN_NYNORSK, + 'nob' => self::BOKMAL, + 'nb' => self::BOKMAL, + 'nog' => self::NOGAI, + 'non' => self::NORSE_OLD, + 'nor' => self::NORWEGIAN, + 'no' => self::NORWEGIAN, + 'nqo' => self::NKO, + 'nso' => self::PEDI, + 'nub' => self::NUBIAN, + 'nwc' => self::CLASSICAL_NEWARI, + 'nya' => self::CHICHEWA, + 'nym' => self::NYAMWEZI, + 'nyn' => self::NYANKOLE, + 'nyo' => self::NYORO, + 'nzi' => self::NZIMA, + 'oci' => self::OCCITAN, + 'oc' => self::OCCITAN, + 'oji' => self::OJIBWA, + 'oj' => self::OJIBWA, + 'ori' => self::ORIYA, + 'or' => self::ORIYA, + 'orm' => self::OROMO, + 'om' => self::OROMO, + 'osa' => self::OSAGE, + 'oss' => self::OSSETIAN, + 'os' => self::OSSETIAN, + 'ota' => self::OTTOMAN, + 'oto' => self::OTOMIAN, + 'paa' => self::PAPUAN, + 'pag' => self::PANGASINAN, + 'pal' => self::PAHLAVI, + 'pam' => self::PAMPANGA, + 'pan' => self::PANJABI, + 'pa' => self::PANJABI, + 'pap' => self::PAPIAMENTO, + 'pau' => self::PALAUAN, + 'peo' => self::PERSIAN_OLD, + 'phi' => self::PHILIPPINE, + 'phn' => self::PHOENICIAN, + 'pli' => self::PALI, + 'pi' => self::PALI, + 'pol' => self::POLISH, + 'pl' => self::POLISH, + 'pon' => self::POHNPEIAN, + 'por' => self::PORTUGUESE, + 'pt' => self::PORTUGUESE, + 'pra' => self::PRAKRIT, + 'pro' => self::PROVENCAL, + 'pus' => self::PUSHTO, + 'ps' => self::PUSHTO, + 'que' => self::QUECHUA, + 'qu' => self::QUECHUA, + 'raj' => self::RAJASTHANI, + 'rap' => self::RAPANUI, + 'rar' => self::RAROTONGAN, + 'roa' => self::ROMANCE, + 'roh' => self::ROMANSH, + 'rm' => self::ROMANSH, + 'rom' => self::ROMANY, + 'rum' => self::ROMANIAN, + 'ron' => self::ROMANIAN, + 'ro' => self::ROMANIAN, + 'run' => self::RUNDI, + 'rn' => self::RUNDI, + 'rup' => self::AROMANIAN, + 'rus' => self::RUSSIAN, + 'ru' => self::RUSSIAN, + 'sad' => self::SANDAWE, + 'sag' => self::SANGO, + 'sg' => self::SANGO, + 'sah' => self::YAKUT, + 'sai' => self::AMERICAN_INDIAN_SOUTH, + 'sal' => self::SALISHAN, + 'sam' => self::SAMARITAN, + 'san' => self::SANSKRIT, + 'sa' => self::SANSKRIT, + 'sas' => self::SASAK, + 'sat' => self::SANTALI, + 'scn' => self::SICILIAN, + 'sco' => self::SCOTS, + 'sel' => self::SELKUP, + 'sem' => self::SEMITIC, + 'sga' => self::IRISH_OLD, + 'sgn' => self::SIGN, + 'shn' => self::SHAN, + 'sid' => self::SIDAMO, + 'sin' => self::SINHALA, + 'si' => self::SINHALA, + 'sio' => self::SIOUAN, + 'sit' => self::SINO_TIBETAN, + 'sla' => self::SLAVIC, + 'slo' => self::SLOVAK, + 'slk' => self::SLOVAK, + 'sk' => self::SLOVAK, + 'slv' => self::SLOVENIAN, + 'sl' => self::SLOVENIAN, + 'sma' => self::SAMI_SOUTHERN, + 'sme' => self::SAMI_NORTHERN, + 'se' => self::SAMI_NORTHERN, + 'smi' => self::SAMI, + 'smj' => self::SAMI_LULE, + 'smn' => self::SAMI_IRARI, + 'smo' => self::SAMOAN, + 'sm' => self::SAMOAN, + 'sms' => self::SAMI_SKOLT, + 'sna' => self::SHONA, + 'sn' => self::SHONA, + 'snd' => self::SINDHI, + 'sd' => self::SINDHI, + 'snk' => self::SONINKE, + 'sog' => self::SOGDIAN, + 'som' => self::SOMALI, + 'so' => self::SOMALI, + 'son' => self::SONGHAI, + 'sot' => self::SOTHO_SOUTHERN, + 'st' => self::SOTHO_SOUTHERN, + 'spa' => self::SPANISH, + 'es' => self::SPANISH, + 'srd' => self::SARDINIAN, + 'sc' => self::SARDINIAN, + 'sm' => self::SRANAN_TONGO, + 'srp' => self::SERBIAN, + 'sr' => self::SERBIAN, + 'srr' => self::SERER, + 'ssa' => self::NILO_SAHARAN, + 'ssw' => self::SWATI, + 'ss' => self::SWATI, + 'suk' => self::SUKUMA, + 'sun' => self::SUNDANESE, + 'su' => self::SUNDANESE, + 'sus' => self::SUSU, + 'sux' => self::SUMERIAN, + 'swa' => self::SWAHILI, + 'sw' => self::SWAHILI, + 'swe' => self::SWEDISH, + 'sv' => self::SWEDISH, + 'syc' => self::SYRIAC_CLASSICAL, + 'syr' => self::SYRIAC, + 'tah' => self::TAHITIAN, + 'ty' => self::TAHITIAN, + 'tai' => self::TAI, + 'tam' => self::TAMIL, + 'ta' => self::TAMIL, + 'tat' => self::TATAR, + 'tt' => self::TATAR, + 'tel' => self::TELUGU, + 'te' => self::TELUGU, + 'tem' => self::TIMNE, + 'ter' => self::TERENO, + 'tet' => self::TETUM, + 'tgk' => self::TAJIK, + 'tg' => self::TAJIK, + 'tgl' => self::TAGALOG, + 'tl' => self::TAGALOG, + 'tha' => self::THAI, + 'th' => self::THAI, + 'tig' => self::TIGRE, + 'tir' => self::TIGRINYA, + 'ti' => self::TIGRINYA, + 'tiv' => self::TIV, + 'tkl' => self::TOKELAU, + 'tlh' => self::KLINGON, + 'tli' => self::TLINGIT, + 'tmh' => self::TAMASHEK, + 'tog' => self::TONGA_NYASA, + 'ton' => self::TONGA_ISLANDS, + 'to' => self::TONGA_ISLANDS, + 'tpi' => self::TOK_PISIN, + 'tsi' => self::TSIMSHIAN, + 'tsn' => self::TSWANA, + 'tn' => self::TSWANA, + 'tso' => self::TSONGA, + 'ts' => self::TSONGA, + 'tuk' => self::TURKMEN, + 'tk' => self::TURKMEN, + 'tum' => self::TUMBUKA, + 'tup' => self::TUPI, + 'tur' => self::TURKISH, + 'tr' => self::TURKISH, + 'tut' => self::ALTAIC, + 'tvl' => self::TUVALU, + 'twi' => self::TWI, + 'tw' => self::TWI, + 'tyv' => self::TUVINIAN, + 'udm' => self::UDMURT, + 'uga' => self::UGARITIC, + 'uig' => self::UIGHUR, + 'ug' => self::UIGHUR, + 'ukr' => self::UKRAINIAN, + 'uk' => self::UKRAINIAN, + 'umb' => self::UMBUNDU, + 'und' => self::UNDETERMINED, + 'urd' => self::URDU, + 'ur' => self::URDU, + 'uzb' => self::UZBEK, + 'uz' => self::UZBEK, + 'vai' => self::VAI, + 'ven' => self::VENDA, + 've' => self::VENDA, + 'vie' => self::VIETNAMESE, + 'vi' => self::VIETNAMESE, + 'vol' => self::VOLAPUK, + 'vo' => self::VOLAPUK, + 'vot' => self::VOTIC, + 'wak' => self::WAKASHAN, + 'wal' => self::WOLAITTA, + 'war' => self::WARAY, + 'was' => self::WASHO, + 'wen' => self::SORBIAN, + 'wln' => self::WALLOON, + 'wa' => self::WALLOON, + 'wol' => self::WOLOF, + 'wo' => self::WOLOF, + 'xal' => self::KALMYK, + 'xho' => self::XHOSA, + 'xh' => self::XHOSA, + 'yao' => self::YAO, + 'yap' => self::YAPESE, + 'yid' => self::YIDDISH, + 'yi' => self::YIDDISH, + 'yor' => self::YORUBA, + 'yo' => self::YORUBA, + 'ypk' => self::YUPIK, + 'zap' => self::ZAPOTEC, + 'zbl' => self::BLISSYMBOLS, + 'zen' => self::ZENAGA, + 'zgh' => self::MOROCCAN_TAMAZIGHT, + 'zha' => self::ZHUANG, + 'za' => self::ZHUANG, + 'znd' => self::ZANDE, + 'zul' => self::ZULU, + 'zu' => self::ZULU, + 'zun' => self::ZUNI, + 'zxx' => self::NOT_APPLICABLE, + 'zza' => self::ZAZA, + ); + + + /** + * Get the language names associated with the id. + * + * @param int $id + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given id. + */ + static function s_get_names_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_names)) { + return c_base_return_array::s_new(self::$s_names[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language names associated with the alias. + * + * @param string $alias + * The id of the names to return. + * + * @return c_base_return_status|c_base_return_array + * An array of names or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given alias. + */ + static function s_get_names_by_alias($alias) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id associated with the language name. + * + * @param string $name + * The string associated with the id + * + * @return c_base_return_status|c_base_return_int + * The numeric id or FALSE on error. + * FALSE without the error flag means that there are no ids associated with the given name. + */ + static function s_get_id_by_alias($alias) { + if (!is_string($alias)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($alias, self::$s_ids)) { + return c_base_return_int::s_new(self::$s_ids[$alias]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the id. + * + * @param int $id + * The id of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given id. + */ + static function s_get_aliases_by_id($id) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($id, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$id]); + } + + return new c_base_return_false(); + } + + /** + * Get the language aliases associated with the name. + * + * @param string $name + * The language name of the aliases to return. + * + * @return c_base_return_status|c_base_return_array + * An array of aliases or FALSE on error. + * FALSE without the error flag means that there are no aliases associated with the given name. + */ + static function s_get_aliases_by_name($name) { + if (!is_string($name)) { + return c_base_return_error::s_false(); + } + + if (array_key_exists($name, self::$s_aliases)) { + return c_base_return_array::s_new(self::$s_aliases[$name]); + } + + return new c_base_return_false(); + } + + /** + * Get the id of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_id() { + return c_base_return_int::s_new(self::ENGLISH_US); + } + + /** + * Get the name of the language considered to be default by the implementing class. + * + * @return c_base_return_status|c_base_return_string + * A string representing the default language. + * FALSE without the error flag means that there are no languages assigned as default. + */ + static function s_get_default_name() { + return c_base_return_string::s_new($this->s_aliases[self::ENGLISH_US]); + } +} diff --git a/common/base/classes/base_ldap.php b/common/base/classes/base_ldap.php new file mode 100644 index 0000000..3a16fb2 --- /dev/null +++ b/common/base/classes/base_ldap.php @@ -0,0 +1,945 @@ +ldap = NULL; + $this->name = NULL; + + $this->bind_name = NULL; + $this->bind_password = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->ldap); + unset($this->name); + + unset($this->bind_name); + unset($this->bind_password); + } + + /** + * Assigns the host name url of the server to connect to. + * + * @param string $name + * The host name url, such as: ldaps://ldap.example.com/ . + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_name($name) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + // sanitize the name string. + $parsed = parse_url($name, PHP_URL_HOST); + if ($parsed === FALSE) { + unset($parsed); + return c_base_return_error::s_false(); + } + + $this->name = preg_replace('/(^\s+)|(\s+$)/us', '', $parsed); + unset($parsed); + + return new c_base_return_true(); + } + + /** + * Returns the stored ldap host name url. + * + * @return c_base_return_string + * The user name string. + */ + public function get_name() { + return c_base_return_string::s_new($this->name); + } + + /** + * Assigns the bind name used to connect to the ldap server. + * + * @param string $name + * The bind name. Often referred to as the bind_rdn. + * Set to NULL to disble bind username. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_bind_name($name) { + if (!is_null($name) && (!is_string($name) || empty($name))) { + return c_base_return_error::s_false(); + } + + $this->bind_name = $name; + return new c_base_return_true(); + } + + /** + * Returns the bind user name. + * + * @return c_base_return_string + * The bind name. Often referred to as the bind_rdn. + */ + public function get_bind_name() { + return c_base_return_string::s_new($this->bind_name); + } + + /** + * Assigns the bind password used to connect to the ldap server. + * + * @param string|null $password + * The bind password. + * Set to NULL to disble bind password. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_bind_password($password) { + if (!is_null($password) && (!is_string($password) || empty($password))) { + return c_base_return_error::s_false(); + } + + $this->bind_password = $password; + return new c_base_return_true(); + } + + /** + * Returns the bind password. + * + * @return c_base_return_string + * The password string. + */ + public function get_bind_password() { + return c_base_return_string::s_new($this->bind_password); + } + + /** + * Binds/Connects to the ldap server. + * + * This performs both ldap_connect() and ldap_bind(). + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: ldap_connect() + * @see: ldap_bind() + * @see: self::do_prepare() + */ + public function do_connect() { + if (is_null($this->name)) { + return c_base_return_error::s_false(); + } + + // already prepared, just return true. + if (is_resource($this->ldap)) { + return new c_base_return_true(); + } + + $this->ldap = ldap_connect($this->name); + if (!is_resource($this->ldap)) { + $this->ldap = NULL; + return c_base_return_error::s_false(); + } + + $bound = ldap_bind($this->ldap, $this->bind_name, $this->bind_password); + if ($bound) { + unset($bound); + return new c_base_return_true(); + } + unset($bound); + + return c_base_return_error::s_false(); + } + + /** + * Unbinds/Disconnects from the ldap server. + * + * The ldap connection must be prepared before this function can be used. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: ldap_unbind() + * @see: self::do_connect() + */ + public function do_disconnect() { + if (!is_resource($this->ldap)) { + return new c_base_return_false(); + } + + $unbound = ldap_unbind($this->ldap); + if ($unbound) { + unset($unbound); + return new c_base_return_true(); + } + unset($unbound); + + return c_base_return_error::s_false(); + } + + /** + * Search the ldap database. + * + * @param string $directory_name + * The is the base directory name (DN). + * @param string $filter + * A simple or advanced filter string. + * @param array $attributes + * An array of required attributes. + * The PHP documentation recommends supplying this for efficiency reasons. + * @param bool $attributes_only + * (optional) If TRUE, then only the attribute types. + * Otherwise, both attribute types and values are loaded. + * @param int $entry_limit + * (optional) Limit the number of entries fetched by this limit. + * PHP calls this the size limit. 0 means no limit. + * @param int $time_limit + * (optional) Limit the number of seconds the query is allowed to operate. + * 0 means no limit. + * @param int $dereference + * Specify how aliases should be handled during the search. + * One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS. + * + * @return c_base_return_status|c_base_ldap_entries + * The search identifier is returned + * + * @see: ldap_search() + */ + public function do_search($directory_name, $filter, $attributes, $attributes_only = FALSE, $entry_limit = 0, $time_limit = 0, $dereference = LDAP_DEREF_NEVER) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_string($directory_name) || !is_string($filter)) { + return c_base_return_error::s_false(); + } + + if (!is_array($attributes)) { + return c_base_return_error::s_false(); + } + + if (!is_int($entry_limit) || $entry_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($time_limit) || $time_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($dereference)) { + return c_base_return_error::s_false(); + } + + // To prevent flooding the logs, prepend @ to prevent errors from being printed as described by the PHP documentation. + // Any errors can still be obtained via the self::get_error_message() and self::get_error_number() functions. + $found = ldap_search($this->ldap, $directory_name, $filter, $attributes, $attributes_only, $entry_limit, $time_limit, $dereference); + if (is_resource($found)) { + $result = c_base_ldap_result::s_new($found); + $result->set_ldap($this->ldap); + + unset($found); + return $result; + } + unset($found); + + return c_base_return_error::s_false(); + } + + /** + * Search the ldap database, but only as far as the base directory name (single-depth). + * + * @param string $directory_name + * The is the base directory name (DN). + * @param string $filter + * A simple or advanced filter string. + * @param array $attributes + * An array of required attributes. + * The PHP documentation recommends supplying this for efficiency reasons. + * @param bool $attributes_only + * (optional) If TRUE, then only the attribute types. + * Otherwise, both attribute types and values are loaded. + * @param int $entry_limit + * (optional) Limit the number of entries fetched by this limit. + * PHP calls this the size limit. 0 means no limit. + * @param int $time_limit + * (optional) Limit the number of seconds the query is allowed to operate. + * 0 means no limit. + * @param int $dereference + * Specify how aliases should be handled during the search. + * One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS. + * + * @return c_base_return_status|c_base_ldap_entries + * The search identifier is returned + * + * @see: ldap_list() + */ + public function do_list($directory_name, $filter, $attributes, $attributes_only = FALSE, $entry_limit = 0, $time_limit = 0, $dereference = LDAP_DEREF_NEVER) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_string($directory_name) || !is_string($filter)) { + return c_base_return_error::s_false(); + } + + if (!is_array($attributes)) { + return c_base_return_error::s_false(); + } + + if (!is_int($entry_limit) || $entry_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($time_limit) || $time_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($dereference)) { + return c_base_return_error::s_false(); + } + + // To prevent flooding the logs, prepend @ to prevent errors from being printed as described by the PHP documentation. + // Any errors can still be obtained via the self::get_error_message() and self::get_error_number() functions. + $found = ldap_list($this->ldap, $directory_name, $filter, $attributes, $attributes_only, $entry_limit, $time_limit, $dereference); + if (is_resource($found)) { + $result = c_base_ldap_result::s_new($found); + $result->set_ldap($this->ldap); + + unset($found); + return $result; + } + unset($found); + + return c_base_return_error::s_false(); + } + + /** + * Read the ldap database, but only for a single entry. + * + * @param string $directory_name + * The is the base directory name (DN). + * @param string $filter + * A simple or advanced filter string. + * @param array $attributes + * An array of required attributes. + * The PHP documentation recommends supplying this for efficiency reasons. + * @param bool $attributes_only + * (optional) If TRUE, then only the attribute types. + * Otherwise, both attribute types and values are loaded. + * @param int $entry_limit + * (optional) Limit the number of entries fetched by this limit. + * PHP calls this the size limit. 0 means no limit. + * @param int $time_limit + * (optional) Limit the number of seconds the query is allowed to operate. + * 0 means no limit. + * @param int $dereference + * Specify how aliases should be handled during the search. + * One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS. + * + * @return c_base_return_status|c_base_ldap_entries + * The search identifier is returned + * + * @see: ldap_read() + */ + public function do_read($directory_name, $filter, $attributes, $attributes_only = FALSE, $entry_limit = 0, $time_limit = 0, $dereference = LDAP_DEREF_NEVER) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_string($directory_name) || !is_string($filter)) { + return c_base_return_error::s_false(); + } + + if (!is_array($attributes)) { + return c_base_return_error::s_false(); + } + + if (!is_int($entry_limit) || $entry_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($time_limit) || $time_limit < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($dereference)) { + return c_base_return_error::s_false(); + } + + // To prevent flooding the logs, prepend @ to prevent errors from being printed as described by the PHP documentation. + // Any errors can still be obtained via the self::get_error_message() and self::get_error_number() functions. + $found = ldap_read($this->ldap, $directory_name, $filter, $attributes, $attributes_only, $entry_limit, $time_limit, $dereference); + if (is_resource($found)) { + $result = c_base_ldap_result::s_new($found); + $result->set_ldap($this->ldap); + + unset($found); + return $result; + } + unset($found); + + return c_base_return_error::s_false(); + } + + /** + * Compare value of an attribute specified within a directory. + * + * This does not recurse down to sub-entries. + * + * @param string $directory_name + * The diretory or dn. + * @param string $attribute + * The attribute to compare. + * @param string $value + * The value to compare against. + * + * @return c_return_status + * TRUE on match, FALSE on no-match (no error bit set), and FALSE with error bit set for error. + * + * @see: ldap_compare() + */ + public function do_compare($directory_name, $attribute, $value) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_string($directory_name) || !is_string($attribute) || !is_string($value)) { + return c_base_return_error::s_false(); + } + + $result = ldap_compare($this->ldap, $domain, $attribute, $value); + if ($result === -1) { + unset($result); + return c_base_return_error::s_false(); + } + + if ($result === TRUE) { + unset($result); + return new c_base_return_true(); + } + unset($result); + + return new c_base_return_false(); + } + + /** + * Returns the ldap error message, if there is an error. + * + * @return c_base_return_status|c_base_return_string + * The error message. + * + * @see: ldap_error() + */ + public function get_error_message() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new(ldap_error($this->ldap)); + } + + /** + * Returns the ldap error number, if there is an error. + * + * Call ldap_err2str() with this number at any point to determine the message associated with it. + * + * @return c_base_return_status|c_base_return_int + * The error number. + * + * @see: ldap_errno() + * @see: ldap_err2str() + */ + public function get_error_number() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new(ldap_errno($this->ldap)); + } + + /** + * Get the current value for a given option in the ldap connection. + * + * @param int $option + * A number representing the option. + * + * @return c_base_return_status|c_base_return_value + * FALSE with the error bit set is returned on failure. + * Anything else is returned on success. + * The return type is depending on the value for option, see: + * - LDAP_OPT_DEREF: c_base_return_int + * - LDAP_OPT_SIZELIMIT: c_base_return_int + * - LDAP_OPT_TIMELIMIT: c_base_return_int + * - LDAP_OPT_NETWORK_TIMEOUT: c_base_return_int + * - LDAP_OPT_PROTOCOL_VERSION: c_base_return_int + * - LDAP_OPT_ERROR_NUMBER: c_base_return_int + * - LDAP_OPT_REFERRALS: c_base_return_status + * - LDAP_OPT_RESTART: c_base_return_status + * - LDAP_OPT_HOST_NAME: c_base_return_string + * - LDAP_OPT_ERROR_STRING: c_base_return_string + * - LDAP_OPT_MATCHED_DN: c_base_return_string + * - LDAP_OPT_SERVER_CONTROLS: c_base_return_array + * - LDAP_OPT_CLIENT_CONTROLS: c_base_return_array + * - Anything else: c_base_return_value. + * + * @see: ldap_get_option() + */ + public function get_option($option) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_int($option)) { + return c_base_return_error::s_false(); + } + + $value = NULL; + if (ldap_get_option($this->ldap, $option, $value) === FALSE) { + unset($value); + return c_base_return_error::s_false(); + } + + if ($option == LDAP_OPT_DEREF || $option == LDAP_OPT_SIZELIMIT || $option == LDAP_OPT_TIMELIMIT || $option == LDAP_OPT_NETWORK_TIMEOUT || $option == LDAP_OPT_PROTOCOL_VERSION || $option == LDAP_OPT_ERROR_NUMBER) { + return c_base_return_int::s_new($value); + } + + if ($option == LDAP_OPT_REFERRALS || $option == LDAP_OPT_RESTART) { + if ($value === TRUE) { + unset($value); + return new c_base_return_true(); + } + unset($value); + + return new c_base_return_false(); + } + + if ($option == LDAP_OPT_HOST_NAME || $option == LDAP_OPT_ERROR_STRING || $option == LDAP_OPT_MATCHED_DN) { + return c_base_return_string::s_new($value); + } + + if ($option == LDAP_OPT_SERVER_CONTROLS || $option == LDAP_OPT_CLIENT_CONTROLS) { + return c_base_return_array::s_new($value); + } + + return c_base_return_value::s_new($value); + } +} + +/** + * A generic class for processing ldap search results. + * + * This should be returned by c_base_ldap::search(). + */ +class c_base_ldap_result extends c_base_return_resource { + private $ldap; + private $entry; + + + /** + * Class constructor. + */ + public function __construct() { + $this->ldap = NULL; + $this->entry = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + if (is_resource($this->value)) { + ldap_free_result($this->value); + } + + unset($this->ldap); + unset($this->entry); + + parent::__destruct(); + } + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * Assigns the ldap resource to this class. + * + * @param resource $ldap + * The ldap resource to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_ldap($ldap) { + if (!is_resource($ldap)) { + return c_base_return_error::s_false(); + } + + $this->ldap = $ldap; + return new c_base_return_true(); + } + + /** + * Returns the total number of entries. + * + * This appears to be capped by the directory limit. + * Therefore, this may not actually be the max number of entries. + * Instead, consider this the total number of entries retrieved in a single request. + * + * @return c_base_return_status|c_base_return_int + * + * @see: ldap_count_entries() + */ + public function get_count() { + if (!is_resource($ldap)) { + return c_base_return_error::s_false(); + } + + $total = ldap_count_entries($this->ldap, $this->value); + if ($total === FALSE) { + unset($total); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($total); + } + + /** + * Loads the first entry. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: ldap_first_entry() + */ + public function load_entry_first() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + $first = ldap_first_entry($this->ldap, $this->value); + if ($first === FALSE) { + unset($first); + return c_base_return_error::s_false(); + } + + $this->entry = $first; + unset($first); + + return new c_base_return_true(); + } + + /** + * Returns the next ldap result entry. + * + * The first entry must be loaded before calling this function. + * + * @return c_base_return_status + * TRUE on success, FALSE on error. + * FALSE (without error bit set) is returned when there are no remaining entries. + * + * @see: ldap_first_entry() + * @see: self::load_entry_first() + */ + public function load_entry_next() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + // the entry is false when there are no entries remaining. + if ($this->entry === FALSE) { + return new c_base_return_false(); + } + + // the entry must first be loaded by self::load_entry_first(). + if (!is_null($this->entry)) { + return c_base_return_error::s_false(); + } + + $this->entry = ldap_next_entry($this->ldap, $this->value); + if ($this->entry === FALSE) { + return new c_base_return_false(); + } + + return $entry; + } + + /** + * Returns all entries. + * + * This requires that the entries be loaded first. + * Call self::load_entry_first() or the subsequent self::load_entry_next() before calling this. + * + * @return c_base_return_status|c_base_return_array + * The an array of attribute strings on success, FALSE otherwise. + * + * @see: ldap_get_entries() + * @see: self::load_entry_first() + * @see: self::load_entry_next() + */ + public function get_entry_all() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + $entries = ldap_get_entries($this->ldap, $this->value); + if ($entries === FALSE) { + unset($entries); + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($entries); + } + + /** + * Returns the first attribute for a given entry. + * + * This requires that the entries be loaded first. + * Call self::load_entry_first() or the subsequent self::load_entry_next() before calling this. + * + * @return c_base_return_status|c_base_return_string + * The attribute string on success, FALSE otherwise. + * + * @see: ldap_first_attribute() + * @see: self::load_entry_first() + * @see: self::load_entry_next() + */ + public function get_attribute_first() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->entry)) { + return c_base_return_error::s_false(); + } + + $attribute = ldap_first_attribute($this->ldap, $this->entry); + if ($attribute === FALSE) { + unset($attribute); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($attribute); + } + + /** + * Returns the next attribute for a given entry. + * + * This requires that the entries be loaded first. + * Call self::load_entry_first() or the subsequent self::load_entry_next() before calling this. + * + * @return c_base_return_status|c_base_return_string + * The attribute string on success, FALSE otherwise. + * + * @see: ldap_next_attribute() + * @see: self::load_attribute_first() + * @see: self::load_entry_first() + * @see: self::load_entry_next() + */ + public function get_attribute_next() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->entry)) { + return c_base_return_error::s_false(); + } + + $attribute = ldap_next_attribute($this->ldap, $this->entry); + if ($attribute === FALSE) { + unset($attribute); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($attribute); + } + + /** + * Loads the all attributes for a given entry. + * + * This requires that the entries be loaded first. + * Call self::load_entry_first() or the subsequent self::load_entry_next() before calling this. + * + * @return c_base_return_status|c_base_return_array + * The an array of attribute strings on success, FALSE otherwise. + * + * @see: ldap_next_attribute() + * @see: self::load_attribute_first() + * @see: self::load_entry_first() + * @see: self::load_entry_next() + */ + public function get_attribute_all() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->entry)) { + return c_base_return_error::s_false(); + } + + $attributes = ldap_get_attributes($this->ldap, $this->entry); + if ($attributes === FALSE) { + unset($attributes); + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($attributes); + } + + /** + * Returns the directory name for a given entry. + * + * This requires that the entries be loaded first. + * Call self::load_entry_first() or the subsequent self::load_entry_next() before calling this. + * + * @return c_base_return_status|c_base_return_string + * The attribute string on success, FALSE otherwise. + * + * @see: ldap_get_dn() + * @see: self::load_attribute_first() + * @see: self::load_entry_first() + * @see: self::load_entry_next() + */ + public function get_directory_name() { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->entry)) { + return c_base_return_error::s_false(); + } + + $directory_name = ldap_get_dn($this->ldap, $this->entry); + if ($directory_name === FALSE) { + unset($directory_name); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($directory_name); + } + + /** + * Returns the values for a given attribute. + * + * @param string $attribute + * The attribute to get the values for. + * @param bool $binary + * (optional) When TRUE, returns binary data. When FALSE, returns string data. + * + * @return c_base_return_status|c_base_return_array + * An array on success, FALSE otherwise. + * + * @see: ldap_get_values() + * @see: ldap_get_values_len() + */ + public function get_values($attribute, $binary = FALSE) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->entry)) { + return c_base_return_error::s_false(); + } + + if (!is_string($attribute)) { + return c_base_return_error::s_false(); + } + + if ($binary) { + $values = ldap_get_values_len($this->ldap, $this->entry, $attribute); + } + else { + $values = ldap_get_values($this->ldap, $this->entry, $attribute); + } + + if (!is_array($values)) { + unset($values); + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($values); + } + + /** + * Sorts all loaded entries by the given filter. + * + * @param string $attribute + * The attribute to perform the sort against. + * + * @see: ldap_sort() + */ + public function do_sort($attribute) { + if (!is_resource($this->ldap)) { + return c_base_return_error::s_false(); + } + + if (!is_string($attribute)) { + return c_base_return_error::s_false(); + } + + $status = ldap_sort($this->ldap, $this->value, $attribute); + if ($status === FALSE) { + unset($status); + return c_base_return_error::s_false(); + } + unset($status); + + return new c_base_return_true(); + } + + /** + * Free's memory allocated to the class. + * + * @return c_base_return_status + * TRUE is returned on success, FALSE is returned if nothing to free. + * FALSE with the error flag set is returned on error. + * + * @see: ldap_free_result() + */ + public function free_result() { + if (!is_resource($this->value)) { + return new c_base_return_false(); + } + + if (ldap_free_result($this->value)) { + return new c_base_return_true(); + } + + return c_base_return_error::s_false(); + } +} diff --git a/common/base/classes/base_log.php b/common/base/classes/base_log.php new file mode 100644 index 0000000..a1140a7 --- /dev/null +++ b/common/base/classes/base_log.php @@ -0,0 +1,220 @@ +type = self::TYPE_NONE; + $this->data = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->type); + unset($this->data); + } + + /** + * Assigns the type code for this log entry. + * + * @param int $tyoe + * The type code. + * + * @return c_base_return_status + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_type($type) { + if (!is_int($type) || $type < 0) { + return c_base_return_error::s_false(); + } + + $this->type = $type; + return new c_base_return_true(); + } + + /** + * Returns the type code for this log entry. + * + * @return c_base_return_status|c_base_return_int + * The type integer on success. + * FALSE with error bit set on error. + */ + public function get_type() { + return c_base_return_int::s_new($this->type); + } + + /** + * Returns the data as a serialized array string. + * + * @return c_base_return_status|c_base_return_array + * An array representing the data array on success. + * FALSE with error bit set is returned on error. + */ + public function get_data_serialized() { + return c_base_return_array::s_new($this->data); + } + + /** + * Returns the data as a serialized array string. + * + * @return c_base_return_status|c_base_return_string + * A serialized string representing the data array on success. + * FALSE with error bit set is returned on error. + */ + public function get_data_serialized() { + return c_base_return_string::s_new(serialize($this->data)); + } + + /** + * Returns the data as a json-serialized array string. + * + * @return c_base_return_status|c_base_return_string + * A json-serialized string representing the data array on success. + * FALSE with error bit set is returned on error. + */ + public function get_data_jsonized($options = 0, $depth = 512) { + if (!is_int($options)) { + return c_base_return_error::s_false(); + } + + if (!is_int($depth) || $depth < 1) { + return c_base_return_error::s_false(); + } + + $encoded = json_encode($this->data, $options, $depth) + if ($encoded === FALSE) { + unset($encoded); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new($encoded); + } + + /** + * Assigns data to a specific key in the data array. + * + * @param string|int $key + * The key name string or integer. + * @param $value + * The value to assign. + * There is no enforcement on the data type. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + protected function pr_set_data($key, $value) { + if (is_int($key)) { + if ($key < 0) { + return c_base_return_error::s_false(); + } + } + elseif (!is_string($key)) { + return c_base_return_error::s_false(); + } + + $this->data[$key] = $value; + return new c_base_return_true(); + } + + /** + * Returns the data assigned at the specified key. + * + * @param string|int $key + * The key name string or integer. + * + * @return c_base_return_status|c_base_return_value + * The array value is returned on success. + * FALSE with error bit set is returned on invalid key. + * FALSE with error bit set is returned on error. + */ + protected function pr_get_data($key) { + if (is_int($key)) { + if ($key < 0) { + return c_base_return_error::s_false(); + } + } + elseif (!is_string($key)) { + return c_base_return_error::s_false(); + } + + if (!array_key_exists($key, $this->data)) { + return c_base_return_error::s_false(); + } + + return c_base_return_value::s_new($this->data[$key]); + } +} diff --git a/common/base/classes/base_markup.php b/common/base/classes/base_markup.php new file mode 100644 index 0000000..0ffa6b5 --- /dev/null +++ b/common/base/classes/base_markup.php @@ -0,0 +1,157 @@ +id = NULL; + $this->attributes = array(); + $this->tags = NULL; + $this->tags_total = 0; + $this->parent = NULL; + $this->children = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->id); + unset($this->attributes); + unset($this->tags); + unset($this->tags_total); + unset($this->parent); + unset($this->children); + } + + /** + * Assign the internal unique numeric tag id. + * + * @param int $id + * The internal numeric id to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_id($id) { + if (!is_int($id)) { + return c_base_return_error::s_false(); + } + + $this->id = $id; + return new c_base_return_true(); + } + + /** + * Get the unique numeric tag id assigned to this object. + * + * @return c_base_return_int|c_base_return_status + * The tag type assigned to this class. + * FALSE is returned if the unique numeric tag id is not set. + * FALSE with error bit set is returned on error. + */ + public function get_id() { + if (!isset($this->id)) { + return new c_base_return_false(); + } + + return new c_base_return_int($this->id); + } + + /** + * Add or append a given tag to the object. + * + * If the tag is not assigned a unique internal id, then one is generated. + * + * @param c_base_markup_tag $tag + * The tag to assign. + * @param int|null $index + * (optional) A position within the children array to assign the tag. + * If NULL, then the tag is appended to the end of the children array. + * + * @return c_base_return_int|$c_base_return_status + * The position in which the tag was added is returned on success. + * FALSE is reeturned if an tag at the specified index already exists. + * FALSE with error bit set is returned on error. + */ + public function set_tag($tag, $index = NULL) { + if (!($tag instanceof c_base_markup_tag)) { + return c_base_return_error::s_false(); + } + + if (!is_null($index) && (!is_int($index) && $index < 0)) { + return c_base_return_error::s_false(); + } + + $tag_id = $tag->get_id(); + if (!($tag_id instanceof c_base_return_int)) { + // PHP fails to provide an end() equivalent to get the end key. + // This performs a less efficient process of generating an array of keys and then calling end() on that array. + // This is then used to get the last key so that the end key can be used as the tag id. + $keys = array_keys($this->tags); + $tag_id = end($keys); + unset($keys); + + $tag->set_id($tag_id); + } + + if (!array_key_exists($tag_id, $this->tags)) { + $this->tags[$tag_id] = $tag; + } + + if (is_null($index)) { + $this->children[] = $tag; + + // PHP fails to provide an end() equivalent to get the end key. + // This performs a less efficient process of generating an array of keys and then calling end() on that array. + // This is then used to get the last key so that the end key can be used as the tag children position. + $keys = array_keys($this->tags); + $index = end($keys); + unset($keys); + } + else { + if (array_key_exists($index, $this->children)) { + return new c_base_return_false(); + } + + $this->children[$index] = $tag; + ksort($this->children); + } + unset($tag_id); + + $this->tags_total++; + return new c_base_return_int::s_new($index); + } +} diff --git a/common/base/classes/base_mime.php b/common/base/classes/base_mime.php new file mode 100644 index 0000000..ea63e21 --- /dev/null +++ b/common/base/classes/base_mime.php @@ -0,0 +1,534 @@ + array('*/*', 'text/*', 'image/*', 'audio/*', 'video/*', 'application/*'), + self::TYPE_STREAM => array('application/octet-stream'), + ); + + private static $s_names_text = array( + self::TYPE_TEXT_PLAIN => array('text/plain'), + self::TYPE_TEXT_HTML => array('text/html'), + self::TYPE_TEXT_RSS => array('application/rss', 'application/rss+xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml'), + self::TYPE_TEXT_ICAL => array('text/calendar'), + self::TYPE_TEXT_CSV => array('text/csv'), + self::TYPE_TEXT_XML => array('application/xml'), + self::TYPE_TEXT_CSS => array('text/css'), + self::TYPE_TEXT_JS => array('text/javascript', 'application/javascript'), + self::TYPE_TEXT_JSON => array('text/json', 'application/json'), + self::TYPE_TEXT_RICH => array('text/rtf'), + self::TYPE_TEXT_XHTML => array('application/xhtml', 'application/xhtml+xml'), + self::TYPE_TEXT_PS => array('text/ps'), + ); + + private static $s_names_image = array( + self::TYPE_IMAGE_PNG => array('image/png'), + self::TYPE_IMAGE_GIF => array('image/gif'), + self::TYPE_IMAGE_JPEG => array('image/jpeg', 'image/jpg', 'image/jpx'), + self::TYPE_IMAGE_BMP => array('image/bmp'), + self::TYPE_IMAGE_SVG => array('image/svg'), + self::TYPE_IMAGE_TIFF => array('image/tiff', 'image/tiff-fx'), + ); + + private static $s_names_audio = array( + self::TYPE_AUDIO_WAV => array('audio/wav'), + self::TYPE_AUDIO_OGG => array('audio/ogg'), + self::TYPE_AUDIO_MP3 => array('audio/mpeg'), + self::TYPE_AUDIO_MP4 => array('audio/mp4'), + self::TYPE_AUDIO_MIDI => array('audio/midi'), + ); + + private static $s_names_video = array( + self::TYPE_VIDEO_MPEG => array('video/mp4', 'video/mpeg'), + self::TYPE_VIDEO_OGG => array('video/ogg'), + self::TYPE_VIDEO_H264 => array('video/h264'), + self::TYPE_VIDEO_QUICKTIME => array('video/qt'), + self::TYPE_VIDEO_DV => array('video/dv'), + self::TYPE_VIDEO_JPEG => array('video/jpeg', 'video/jpeg2000'), + self::TYPE_VIDEO_WEBM => array('video/webm'), + ); + + private static $s_names_document = array( + self::TYPE_DOCUMENT_PDF => array('application/pdf'), + self::TYPE_DOCUMENT_LIBRECHART => array('application/vnd.oasis.opendocument.chart'), + self::TYPE_DOCUMENT_LIBREFORMULA => array('application/vnd.oasis.opendocument.formula'), + self::TYPE_DOCUMENT_LIBREGRAPHIC => array('application/vnd.oasis.opendocument.graphics'), + self::TYPE_DOCUMENT_LIBREPRESENTATION => array('application/vnd.oasis.opendocument.presentation'), + self::TYPE_DOCUMENT_LIBRESPREADSHEET => array('application/vnd.oasis.opendocument.spreadsheet'), + self::TYPE_DOCUMENT_LIBRETEXT => array('application/vnd.oasis.opendocument.text'), + self::TYPE_DOCUMENT_LIBREHTML => array('application/vnd.oasis.opendocument.text-web'), + self::TYPE_DOCUMENT_ABIWORD => array('application/abiword', 'application/abiword-compressed'), + self::TYPE_DOCUMENT_MSWORD => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword'), + self::TYPE_DOCUMENT_MSEXCEL => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/ms-excel'), + self::TYPE_DOCUMENT_MSPOWERPOINT => array('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/ms-powerpoint'), + ); + + private static $s_names_container = array( + self::TYPE_CONTAINER_TAR => array('application/tar'), + self::TYPE_CONTAINER_CPIO => array('application/cpio'), + self::TYPE_CONTAINER_JAVA => array('application/java'), + ); + + private static $s_names_application = array( + ); + + + /** + * Get the mime-types associated with the id. + * + * @param int $id + * The id of the mime type to return. + * @param int|null $category + * (optional) search a limited sub-set of ids based on the category. + * + * @return c_base_return_status|c_base_return_array + * An array of mime-types or FALSE on error. + * FALSE without the error flag means that there are no names associated with the given id. + */ + static function s_get_names_by_id($id, $category = NULL) { + if (!is_int($id) && !is_numeric($id)) { + return c_base_return_error::s_false(); + } + + if (is_null($category)) { + $result = NULL; + + if (array_key_exists($id, self::$s_names_basic)) { + return c_base_return_array::s_new(self::$s_names_basic[$id]); + } + + if (array_key_exists($id, self::$s_names_text)) { + return c_base_return_array::s_new(self::$s_names_text[$id]); + } + + if (array_key_exists($id, self::$s_names_audio)) { + return c_base_return_array::s_new(self::$s_names_audio[$id]); + } + + if (array_key_exists($id, self::$s_names_video)) { + return c_base_return_array::s_new(self::$s_names_video[$id]); + } + + if (array_key_exists($id, self::$s_names_document)) { + return c_base_return_array::s_new(self::$s_names_document[$id]); + } + + if (array_key_exists($id, self::$s_names_container)) { + return c_base_return_array::s_new(self::$s_names_container[$id]); + } + + if (array_key_exists($id, self::$s_names_application)) { + return c_base_return_array::s_new(self::$s_names_application[$id]); + } + } + else { + if (!is_int($category)) { + return c_base_return_error::s_false(); + } + + if ($category == self::CATEGORY_PROVIDED) { + if (array_key_exists($id, self::$s_names_basic)) { + return c_base_return_array::s_new(self::$s_names_basic[$id]); + } + } + elseif ($category == self::CATEGORY_TEXT) { + if (array_key_exists($id, self::$s_names_text)) { + return c_base_return_array::s_new(self::$s_names_text[$id]); + } + } + elseif ($category == self::CATEGORY_IMAGE) { + if (array_key_exists($id, self::$s_names_text)) { + return c_base_return_array::s_new(self::$s_names_text[$id]); + } + } + elseif ($category == self::CATEGORY_AUDIO) { + if (array_key_exists($id, self::$s_names_audio)) { + return c_base_return_array::s_new(self::$s_names_audio[$id]); + } + } + elseif ($category == self::CATEGORY_VIDEO) { + if (array_key_exists($id, self::$s_names_video)) { + return c_base_return_array::s_new(self::$s_names_video[$id]); + } + } + elseif ($category == self::CATEGORY_DOCUMENT) { + if (array_key_exists($id, self::$s_names_document)) { + return c_base_return_array::s_new(self::$s_names_document[$id]); + } + } + elseif ($category == self::CATEGORY_CONTAINER) { + if (array_key_exists($id, self::$s_names_container)) { + return c_base_return_array::s_new(self::$s_names_container[$id]); + } + } + elseif ($category == self::CATEGORY_APPLICATION) { + if (array_key_exists($id, self::$s_names_application)) { + return c_base_return_array::s_new(self::$s_names_application[$id]); + } + } + } + + return new c_base_return_false(); + } + + /** + * Identifies the mime-type and returns relevant information. + * + * @param string $mime + * The mime-type string to identify. + * @param bool $lowercase + * (optional) When TRUE, the passed string will be auto-forced to be lower-case. + * + * @return c_base_return_status|c_base_return_array + * An array of containing the following: + * - 'id_category': An integer representing the mime-type category. + * - 'id_type': An integer representing the mime-type. + * - 'name_category': A (lowercase) string representing the category part of the mime-type. + * - 'name_type': A (lowercase) string representing the type part of the mime-type. + * Both category and type ids may have appropriate UNKNOWN values to for valid mime-type formats of unknown mime-type strings. + * FALSE with the error bit set is returned on error. + * FALSE without the error flag means for an invalid mime-type string. + * + * @see: mb_strtolower() + */ + static function s_identify($mime, $lowercase = FALSE) { + if (!is_string($mime)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($lowercase)) { + return c_base_return_error::s_false(); + } + + if ($lowercase) { + $lower_mime = mb_strtolower($mime); + } + else { + $lower_mime = $mime; + } + + $information = array( + 'id_category' => self::CATEGORY_PROVIDED, + 'id_type' => self::TYPE_PROVIDED, + 'name_category' => '*', + 'name_type' => '*', + ); + + if ($mime == '*/*') { + return c_base_return_array::s_new($information); + } + + $parts = mb_split('/', $lower_mime); + + if (count($parts) != 2) { + // there is no valid value to process. + unset($parts); + unset($lower_mime); + unset($information); + return new c_base_return_false(); + } + + $information['name_category'] = $parts[0]; + $information['name_type'] = $parts[1]; + + if ($parts[0] == 'text') { + $information['id_category'] = self::CATEGORY_TEXT; + + if ($parts[1] == '*') { + // nothing to change. + } + elseif ($parts[1] == 'html') { + $information['id_type'] = self::TYPE_TEXT_HTML; + } + elseif ($parts[1] == 'plain') { + $information['id_type'] = self::TYPE_TEXT_PLAIN; + } + elseif ($parts[1] == 'calendar') { + $information['id_type'] = self::TYPE_TEXT_ICAL; + } + elseif ($parts[1] == 'csv') { + $information['id_type'] = self::TYPE_TEXT_CSV; + } + elseif ($parts[1] == 'xml') { + $information['id_type'] = self::TYPE_TEXT_XML; + } + elseif ($parts[1] == 'css') { + $information['id_type'] = self::TYPE_TEXT_CSS; + } + elseif ($parts[1] == 'rtf') { + $information['id_type'] = self::TYPE_TEXT_RICH; + } + elseif ($parts[1] == 'javascript') { + $information['id_type'] = self::TYPE_TEXT_JS; + } + else { + $information['id_type'] = self::TYPE_UNKNOWN; + } + } + elseif ($parts[0] == 'application') { + $information['id_category'] = self::CATEGORY_APPLICATION; + + if ($parts[1] == '*') { + // nothing to change. + } + elseif ($parts[1] == 'octet-stream') { + $information['id_category'] = self::CATEGORY_STREAM; + $information['id_type'] = self::TYPE_STREAM; + } + elseif ($parts[1] == 'pdf') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_APPLICATION_PDF; + } + elseif ($parts[1] == 'rss' || $parts[1] == 'rss+xml' || $parts[1] == 'rdf+xml' || $parts[1] == 'atom+xml') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_RSS; + } + elseif ($parts[1] == 'xml') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_XML; + } + elseif ($parts[1] == 'javascript') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_JS; + } + elseif ($parts[1] == 'json') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_JSON; + } + elseif ($parts[1] == 'xhtml' || $parts[1] == 'xhtml+xml') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_XHTML; + } + elseif ($parts[1] == 'ps') { + $information['id_category'] = self::CATEGORY_TEXT; + $information['id_type'] = self::TYPE_TEXT_PS; + } + elseif ($parts[1] == 'tar') { + $information['id_category'] = self::CATEGORY_CONTAINER; + $information['id_type'] = self::TYPE_CONTAINER_TAR; + } + elseif ($parts[1] == 'cpio') { + $information['id_category'] = self::CATEGORY_CONTAINER; + $information['id_type'] = self::TYPE_CONTAINER_CPIO; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.chart') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBRECHART; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.formula') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBREFORMULA; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.graphics') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBREGRAPHIC; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.presentation') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBREPRESENTATION; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.spreadsheet') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBRESPREADSHEET; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.text') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBRETEXT; + } + elseif ($parts[1] == 'vnd.oasis.opendocument.text-web') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_LIBREHTML; + } + elseif ($parts[1] == 'abiword' || $parts[1] == 'abiword-compressed') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_ABIWORD; + } + elseif ($parts[1] == 'msword' || $parts[1] == 'vnd.openxmlformats-officedocument.wordprocessingml.document') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_MSWORD; + } + elseif ($parts[1] == 'ms-excel' || $parts[1] == 'vnd.openxmlformats-officedocument.spreadsheetml.sheet') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_MSEXCEL; + } + elseif ($parts[1] == 'ms-powerpoint' || $parts[1] == 'vnd.openxmlformats-officedocument.presentationml.presentation') { + $information['id_category'] = self::CATEGORY_DOCUMENT; + $information['id_type'] = self::TYPE_DOCUMENT_MSPOWERPOINT; + } + elseif ($parts[1] == 'java') { + $information['id_category'] = self::CATEGORY_CONTAINER; + $information['id_type'] = self::TYPE_CONTAINER_JAVA; + } + else { + $information['id_type'] = self::TYPE_UNKNOWN; + } + } + elseif ($parts[0] == 'image') { + $information['id_category'] = self::CATEGORY_IMAGE; + + if ($parts[1] == '*') { + // nothing to change. + } + elseif ($parts[1] == 'png') { + $information['id_type'] = self::TYPE_IMAGE_PNG; + } + elseif ($parts[1] == 'jpeg' || $parts[1] == 'jpg' || $parts[1] == 'jpx') { + $information['id_type'] = self::TYPE_IMAGE_JPEG; + } + elseif ($parts[1] == 'gif') { + $information['id_type'] = self::TYPE_IMAGE_GIF; + } + elseif ($parts[1] == 'bmp') { + $information['id_type'] = self::TYPE_IMAGE_BMP; + } + elseif ($parts[1] == 'svg') { + $information['id_type'] = self::TYPE_IMAGE_SVG; + } + elseif ($parts[1] == 'tiff' || $parts[1] == 'tiff-fx') { + $information['id_type'] = self::TYPE_IMAGE_TIFF; + } + else { + $information['id_type'] = self::TYPE_UNKNOWN; + } + } + elseif ($parts[0] == 'audio') { + $information['id_category'] = self::CATEGORY_AUDIO; + + if ($parts[1] == '*') { + // nothing to change. + } + elseif ($parts[1] == 'ogg') { + $information['id_type'] = self::TYPE_AUDIO_OGG; + } + elseif ($parts[1] == 'mpeg') { + $information['id_type'] = self::TYPE_AUDIO_MP3; + } + elseif ($parts[1] == 'mp4') { + $information['id_type'] = self::TYPE_AUDIO_MP4; + } + elseif ($parts[1] == 'wav') { + $information['id_type'] = self::TYPE_AUDIO_WAV; + } + elseif ($parts[1] == 'midi') { + $information['id_type'] = self::TYPE_AUDIO_MIDI; + } + else { + $information['id_type'] = self::TYPE_UNKNOWN; + } + } + elseif ($parts[0] == 'video') { + $information['id_category'] = self::CATEGORY_VIDEO; + + if ($parts[1] == '*') { + // nothing to change. + } + elseif ($parts[1] == 'mp4' || $parts[1] == 'mpeg') { + $information['id_type'] = self::TYPE_VIDEO_MPEG; + } + elseif ($parts[1] == 'ogg') { + $information['id_type'] = self::TYPE_VIDEO_OGG; + } + elseif ($parts[1] == 'h264') { + $information['id_type'] = self::TYPE_VIDEO_H264; + } + elseif ($parts[1] == 'quicktime') { + $information['id_type'] = self::TYPE_VIDEO_QUICKTIME; + } + elseif ($parts[1] == 'dv') { + $information['id_type'] = self::TYPE_VIDEO_DV; + } + elseif ($parts[1] == 'jpeg' || $parts[1] == 'jpeg2000') { + $information['id_type'] = self::TYPE_VIDEO_JPEG; + } + elseif ($parts[1] == 'webm') { + $information['id_type'] = self::TYPE_VIDEO_WEBM; + } + else { + $information['id_type'] = self::TYPE_UNKNOWN; + } + } + else { + $information['id_category'] = self::CATEGORY_UNKNOWN; + $information['id_type'] = self::TYPE_UNKNOWN; + } + unset($parts); + + unset($lower_mime); + return c_base_return_array::s_new($information); + } +} diff --git a/common/base/classes/base_return.php b/common/base/classes/base_return.php new file mode 100644 index 0000000..010ed71 --- /dev/null +++ b/common/base/classes/base_return.php @@ -0,0 +1,1311 @@ +value); + } + + /** + * Assign the value. + * + * @param $value + * This can be anything that is to be considered a return value. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return $value + * This can be anything that is to be considered a return value. + */ + public function get_value() { + return $this->value; + } + + /** + * Determine if this class has a value assigned to it. + * + * @return bool + * TRUE if a value is assigned, FALSE otherwise. + */ + public function has_value() { + return !is_null($this->value); + } + + /** + * Creates a new return __class__ type. + * + * This is used to simplify the returning of a new class value. + * + * Errata: My theory was that by using __CLASS__ child classes could call this static function and __CLASS__ would be replaced with the child class type. + * This is not the case, so I am left with using an abstract static class, which is also not supported by PHP. + * Therefore, API-wise, this is an abstract static class and child classes are expected to implement this function, even if PHP does not enforce this. + * + * @param $value + * The value to assign. + * Validation is performed by the current class. + * No error will be set if an invalid parameter is provided. + * Child classes are expected to assign a value of NULL for invalid parameters. + * + * @return __class__ + * A newly created __class__ type, without an error set. + */ + //public abstract static function s_new($value); + + /** + * Private implemntation of s_new(). + * + * @param $value + * The value to assign. + * Validation is performed by the current class. + * No error will be set if an invalid parameter is provided. + * Child classes are expected to assign a value of NULL for invalid parameters. + * @param string $class + * This is the class name of the type to create. + * Essentially just pass __CLASS__ to this argument. + * + * @see: t_base_return_value::s_new() + */ + final protected static function p_s_new($value, $class) { + $object_return = new $class(); + unset($class); + + // allow for NULL to be passed without generating any errors. + // the default value, when undefined is always null. + if (!is_null($value)) { + $object_return->set_value($value); + } + + return $object_return; + } + + /** + * Perform a very basic, safe, value retrieval. + * + * PHP allows for things like $account->get_password()->get_value(). + * If get_password() returns an non-object or an object without the get_value() function, a PHP error happens. + * This provides a simple way to obtain the value of a specific class that supports this trait without generating errors. + * + * Errata: My theory was that by using __CLASS__ child classes could call this static function and __CLASS__ would be replaced with the child class type. + * This is not the case, so I am left with using an abstract static class, which is also not supported by PHP. + * Therefore, API-wise, this is an abstract static class and child classes are expected to implement this function, even if PHP does not enforce this. + * + * @return + * The value is returned or NULL is returned if value retrieval is not possible. + */ + //public abstract static function s_value($return); + + /** + * Private implementation of s_value(). + * + * @param object $return + * The appropriate c_base_return class that supports the t_base_return_value_exact trait. + * @param string $class + * The class name to expect. + * + * @return + * The value is returned or a generated expected type is returned if value retrieval is not possible. + * + * @see: t_base_return_value::p_s_value() + */ + final protected static function p_s_value($return, $class) { + if (!is_object($return) || !($return instanceof $class)) { + return NULL; + } + + return $return->get_value(); + } +} + +/** + * A generic trait for a getting a return value that is an exact type. + * + * No NULL values are allowed. + */ +trait t_base_return_value_exact { + // PHP does not support this. This project's API does require this trait to be used even if its not enforced by PHP. + //use t_base_return_value; + + /** + * Perform a very basic, safe, value retrieval of the expected type. + * + * This guarantees that a specific type is returned. + * + * PHP allows for things like $account->get_password()->get_value(). + * If get_password() returns an non-object or an object without the get_value() function, a PHP error happens. + * This provides a simple way to obtain the value of a specific class that supports this trait without generating errors. + * + * Errata: API-wise, this is an abstract static class and child classes are expected to implement this function, even if PHP does not enforce this. + * + * @param object $return + * The appropriate c_base_return class that supports the t_base_return_value_exact trait. + * + * @return + * The value is returned or a generated expected type is returned if value retrieval is not possible. + */ + //abstract public static function s_value_exact($return); + + /** + * Private implementation of s_value_exact(). + * + * @param object $return + * The appropriate c_base_return class that supports the t_base_return_value_exact trait. + * @param string $class + * The class name to expect. + * @param $failsafe + * The variable to return in case $return is invalid in some way. + * This is used to guarantee that the return value is of the exact expected type. + * + * @return + * The value is returned or a generated expected type is returned if value retrieval is not possible. + * + * @see: t_base_return_value_exact::p_s_value_exact() + */ + final protected static function p_s_value_exact($return, $class, $failsafe) { + if (!is_object($return) || !($return instanceof $class)) { + return $failsafe; + } + + return $return->get_value_exact(); + } + + /** + * Return the value of the expected type. + * + * This guarantees that a specific type is returned. + * + * @return $value + * The value of a specific type is returned. + */ + abstract public function get_value_exact(); +} + +/** + * A generic trait for a message associated with a return value. + * + * This is added as a consideration and may be removed it ends up being unused. + */ +trait t_base_return_message { + protected $message = NULL; + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->message); + } + + /** + * Assign the message. + */ + public function set_message($message) { + $this->message = $message; + } + + /** + * Return the value. + * + * @return $value + * This can be anything that is to be considered a return value. + */ + public function get_message() { + return $this->value; + } +} + +/** + * A generic class for managing return values. + * + * This is the base template class used for specific return classes. + * + * All base returns will have an error variable. + * + * @require class c_base_error + */ +class c_base_return { + private $error; + + /** + * Class constructor. + */ + public function __construct() { + $this->error = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->error); + } + + /** + * Provide a simple way to check for error in a single step. + * + * This is intended to help clean up code and make code more readable. + * + * @return bool + * return TRUE if the passed argument is an object class of type __CLASS__ and has an error flag set. + */ + public static function s_has_error($return) { + return is_object($return) && $return instanceof c_base_return && $return->has_error(); + } + + /** + * Assign the error code. + * + * @param null|c_base_error $error + * The error code class defining what the error is. + * Setting this to NULL will clear the error flag. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_error($error) { + if (!is_null($error) && !($error instanceof c_base_error)) { + return FALSE; + } + + $this->error = $error; + return TRUE; + } + + /** + * Return the error code. + * + * @return null|c_base_error + * @todo: finish this once c_base_error is implemented. + */ + public function get_error() { + if (!($this->error instanceof c_base_error)) { + $this->error = NULL; + } + + return $this->error; + } + + /** + * Return the error state in a simple TRUE/FALSE manner. + * + * This is similar to get_error(), but should instead be used to to check to see if there is an error and not check what the error is set to. + * + * @return bool + * TRUE if an error is assigned and FALSE if no error is assigned. + * + * @see: get_error() + */ + public function has_error() { + // when there is no error flag assigned, its value should be NULL so a simple existence check should be all that is needed. + return $this->error instanceof c_base_error; + } +} + +/** + * A boolean return class representing a return value of either TRUE or FALSE. + * + * This is used as a return status for a function that does not return any expected valid values. + * This class does not have any values of its own. + */ +class c_base_return_status extends c_base_return { +} + +/** + * A boolean return class representing a return value of TRUE. + * + * This class will not have any values. + */ +class c_base_return_true extends c_base_return_status { +} + +/** + * A boolean return class representing a return value of FALSE. + * + * This class will not have any values. + */ +class c_base_return_false extends c_base_return_status { +} + +/** + * A return class representing a return value of NULL. + * + * This class will not have any values. + */ +class c_base_return_null extends c_base_return_status { +} + +/** + * A return class representing a return value with a specific value. + */ +class c_base_return_value extends c_base_return { + use t_base_return_value; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } +} + +/** + * A return class whose value is represented as a bool. + * + * Do not use this class for returning status or errors for functions. + * Instead use anything of type c_base_return_status(). + */ +class c_base_return_bool extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, FALSE); + } + + /** + * Assign the value. + * + * @param bool $value + * Any value so long as it is a bool. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_bool($value)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return bool|null $value + * The value bool stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !is_bool($this->value)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return bool $value + * The value bool stored within this class. + */ + public function get_value_exact() { + if (!is_bool($this->value)) { + $this->value = FALSE; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a string. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_base_return_string extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param string $value + * Any value so long as it is a string. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_string($value)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !is_string($this->value)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return string $value + * The value string stored within this class. + */ + public function get_value_exact() { + if (!is_string($this->value)) { + $this->value = ''; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a int. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + * Numeric types are converted to integers and stored as an integer. + */ +class c_base_return_int extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, 0); + } + + /** + * Assign the value. + * + * @param int|numeric $value + * Any value so long as it is an integer or a numeric. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_int($value) && !is_numeric($value)) { + return FALSE; + } + + if (is_numeric($value)) { + $this->value = (int) $value; + } + else { + $this->value = $value; + } + + return TRUE; + } + + /** + * Return the value. + * + * @return int|null $value + * The value integer stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !is_int($this->value)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return int $value + * The value int stored within this class. + */ + public function get_value_exact() { + if (!is_int($this->value)) { + $this->value = 0; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a float. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + * Numeric types are converted to float and stored as a float. + */ +class c_base_return_float extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, 0.0); + } + + /** + * Assign the value. + * + * @param float|numeric $value + * Any value so long as it is an integer or a numeric. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_float($value) && !is_numeric($value)) { + return FALSE; + } + + if (is_numeric($value)) { + $this->value = (float) $value; + } + else { + $this->value = $value; + } + + return TRUE; + } + + /** + * Return the value. + * + * @return float|null $value + * The value float stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !is_int($this->value)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return float $value + * The value float stored within this class. + */ + public function get_value_exact() { + if (!is_float($this->value)) { + $this->value = 0.0; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as an array. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + * This provides basic functionality for managing keys and values in the class. + */ +class c_base_return_array extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, array()); + } + + /** + * Assign the value. + * + * @param array $value + * Any value so long as it is an array. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_array($value)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Assign the value at a specific index in the array. + * + * @param array $value + * Any value so long as it is an array. + * NULL is not allowed. + * @param string $key + * A key to assign a specific value to. + * @param string $type + * (optional) When key is not NULL, a specific known type to assign. + * This does nothing if $key is not provided. + * This is used for validation purposes. + * + * Supported known types: + * 'bool': a boolean value. + * 'int': an integer value. + * 'string': a string value. + * 'array': an array value. + * 'object': an object value. + * 'resource': a generic resource value. + * 'stream': a stream resource value. + * 'socket': a socket resource value. + * 'numeric': a string value that represents either an int or float. + * 'null': A null value. + * NULL: no specific type requirements. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value_at($value, $key, $type = NULL) { + if (!is_string($key) || empty($key)) { + return FALSE; + } + + // when type is not supplied, return a generic type. + if (is_null($type)) { + $this->value[$key] = $value; + return TRUE; + } + + // if type is supplied, it must be string. + if (!is_string($type) || empty($type)) { + return FALSE; + } + + if ($type == 'bool' && !is_bool($value)) { + return FALSE; + } + elseif ($type == 'int' && !is_int($value)) { + return FALSE; + } + elseif ($type == 'float' && !is_float($value)) { + return FALSE; + } + elseif ($type == 'numeric' && !is_numeric($value)) { + return FALSE; + } + elseif ($type == 'string' && !is_string($value)) { + return FALSE; + } + elseif ($type == 'array' && !is_array($value)) { + return FALSE; + } + elseif ($type == 'object' && !is_object($value)) { + return FALSE; + } + elseif ($type == 'resource' && !is_resource($value)) { + return FALSE; + } + elseif ($type == 'stream') { + if (!is_resource($value) || get_resource_type($value) != 'stream') { + return FALSE; + } + } + elseif ($type == 'socket') { + if (!is_resource($value) || get_resource_type($value) != 'socket') { + return FALSE; + } + } + elseif ($type == 'null' && !is_null($value)) { + return FALSE; + } + + $this->value[$key] = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return array|null $value + * The value array stored within this class. + * NULL may be returned if there is no defined valid array. + */ + public function get_value() { + if (!is_null($this->value) && !is_array($this->value)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return array $value + * The value array stored within this class. + */ + public function get_value_exact() { + if (!is_array($this->value)) { + $this->value = array(); + } + + return $this->value; + } + + /** + * Return the value at a specific index in the array. + * + * Note: ideally, this should return specific c_base_return_* types. + * The problem is that this would then make this class dependent on those type, which I am trying to avoid. + * No c_base_return_* type should use another c_base_return_* as their return values for their non-static functions. + * @todo: This design might be reviewed and changed before this project is finalized. + * + * @param string $key + * A key to assign a specific value to. + * @param string $type + * (optional) When key is not NULL, a specific known type to assign. + * This does nothing if $key is not provided. + * This is used for validation purposes. + * + * Supported known types: + * 'bool': a boolean value. + * 'int': an integer value. + * 'string': a string value. + * 'array': an array value. + * 'object': an object value. + * 'resource': a generic resource value. + * 'stream': a stream resource value. + * 'socket': a socket resource value. + * 'numeric': a string value that represents either an int or float. + * 'null': A null value. + * NULL: no specific type requirements. + * + * @return + * Value on success, FALSE otherwise. + * Warning: There is no way to distinguish a return value of FALSE for an error to a valid FALSE when $type is set to 'bool'. + */ + public function get_value_at($key, $type = NULL) { + if (!is_string($key) || empty($key)) { + return FALSE; + } + + // if type is supplied, it must be string. + if (!is_null($type) && (!is_string($type) || empty($type))) { + return FALSE; + } + + if (!is_array($this->value)) { + $this->value = array(); + } + + if (!array_key_exists($key, $this->value)) { + return FALSE; + } + + if (!is_null($type)) { + if ($type == 'bool') { + if (!is_bool($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'int') { + if (!is_int($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'float') { + if (!is_float($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'numeric') { + if (!is_numeric($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'string') { + if (!is_string($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'array') { + if (!is_array($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'object') { + if (!is_object($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'resource') { + if (!is_resource($this->value[$key])) { + return FALSE; + } + } + elseif ($type == 'stream') { + if (!is_resource($this->value[$key]) || get_resource_type($this->value[$key]) != 'stream') { + return FALSE; + } + } + elseif ($type == 'socket') { + if (!is_resource($this->value[$key]) || get_resource_type($this->value[$key]) != 'socket') { + return FALSE; + } + } + elseif ($type == 'null') { + if (!is_null($this->value[$key])) { + return FALSE; + } + } + else { + return FALSE; + } + } + + return $this->value[$key]; + } +} + +/** + * A return class whose value is represented as a generic object. + * + * This should be used to return either a generic object or used as a base class for extending to a specific object type. + * All specific object types should extend this class so that instanceof tests against c_base_return_object are always accurate. + */ +class c_base_return_object extends c_base_return_value { + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * Assign the value. + * + * @param object $value + * Any value so long as it is an object. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_object($value)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return object|null $value + * The value object stored within this class. + * NULL may be returned if there is no defined valid resource. + */ + public function get_value() { + if (!is_null($this->value) && !is_object($this->value)) { + $this->value = NULL; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a generic resource. + * + * This should be used to return either a generic resource or used as a base class for extending to a specific resource type. + * All specific resource types should extend this class so that instanceof tests against c_base_return_resource are always accurate. + */ +class c_base_return_resource extends c_base_return_value { + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * Assign the value. + * + * @param resource $value + * Any value so long as it is an resource. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_resource($value)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return resource|null $value + * The value resource stored within this class. + * NULL may be returned if there is no defined valid resource. + */ + public function get_value() { + if (!is_null($this->value) && !is_resource($this->value)) { + $this->value = NULL; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a stream resource. + */ +class c_base_return_resource_stream extends c_base_return_resource { + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * Assign the value. + * + * @param stream $value + * Any value so long as it is an resource. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_resource($value) || get_resource_type($value) != 'stream') { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return resource|null $value + * The value resource stored within this class. + * NULL may be returned if there is no defined valid resource. + */ + public function get_value() { + if (!is_resource($this->value) || get_resource_type($this->value) != 'stream') { + $this->value = NULL; + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a socket resource. + */ +class c_base_return_resource_socket extends c_base_return_resource { + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * Assign the value. + * + * @param stream $value + * Any value so long as it is an resource. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!is_resource($value) || get_resource_type($value) != 'socket') { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return resource|null $value + * The value resource stored within this class. + * NULL may be returned if there is no defined valid resource. + */ + public function get_value() { + if (!is_resource($this->value) || get_resource_type($this->value) != 'socket') { + $this->value = NULL; + } + + return $this->value; + } +} + +/** + * A generic class for pre-populating specific error return codes. + * + * This is used to simplify the code by pre-setting the error code. + * + * @require class c_base_error + */ +class c_base_return_error { + + /** + * Creates a return boolean TRUE with the error value populated. + * + * @todo: this is incomplete because the base_error class is not yet written. + * + * @param todo|null $error + * (optional) a custom error setting. + * + * @return c_base_return_true + * A c_base_return_true object with the error value populated. + */ + public static function s_true($error = NULL) { + $object_error = new c_base_error(); + + $object_return = new c_base_return_true(); + $object_return->set_error($object_error); + + if (!is_null($error)) { + // @todo: do something with the code. + } + + return $object_return; + } + + /** + * Creates a return boolean TRUE with the error value populated. + * + * @todo: this is incomplete because the base_error class is not yet written. + * + * @param todo|null $error + * (optional) a custom error setting. + * + * @return c_base_return_false + * A c_base_return_true object with the error value populated. + */ + public static function s_false($error = NULL) { + $object_error = new c_base_error(); + + $object_return = new c_base_return_false(); + $object_return->set_error($object_error); + + if (!is_null($error)) { + // @todo: do something with the code. + } + + return $object_return; + } + + /** + * Creates a return boolean TRUE with the error value populated. + * + * @todo: this is incomplete because the base_error class is not yet written. + * + * @param $value + * A value to provide + * @param $class + * A custom class name. + * @param todo|null $error + * (optional) a custom error setting. + * + * @return c_base_return_false|c_base_return_value + * A c_base_return_value object is returned with the error value populated + * If the passed class is invalid or not of type c_base_return_value, then a c_base_return_true object with the error value populated. + */ + public static function s_value($value, $class, $error = NULL) { + if (!class_exists($class) || !($class instanceof c_base_return_value)) { + return self::s_false($error); + } + + $object_error = new c_base_error(); + + $object_return = new $class(); + $object_return->set_error($object_error); + $object_return->set_value($value); + + if (!is_null($error)) { + // @todo: do something with the code. + } + + return $object_return; + } +} diff --git a/common/base/classes/base_rfc_char.php b/common/base/classes/base_rfc_char.php new file mode 100644 index 0000000..99e682b --- /dev/null +++ b/common/base/classes/base_rfc_char.php @@ -0,0 +1,940 @@ +91 and 93-126. + * dtext = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_text($ordinal) { + if (($ordinal > c_base_ascii::SPACE && $ordinal < c_base_ascii::BRACKET_OPEN) || ($ordinal > c_base_ascii::BRACKET_CLOSE && $ordinal < c_base_ascii::DELETE)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: ctext. + * + * ctext = ASCII character codes 33->39, 42->91, and 93-126. + * ctext = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_ctext($ordinal) { + if (($ordinal > c_base_ascii::SPACE && $ordinal < c_base_ascii::PARENTHESIS_OPEN) || ($ordinal > c_base_ascii::PARENTHESIS_CLOSE && $ordinal < c_base_ascii::SLASH_BACKWARD) || ($ordinal > c_base_ascii::SLASH_BACKWARD && $ordinal < c_base_ascii::DELETE)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: dtext. + * + * dtext = ASCII character codes 33->90 and 94-126. + * dtext = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_dtext($ordinal) { + if (($ordinal > c_base_ascii::SPACE && $ordinal < c_base_ascii::BRACKET_OPEN) || ($ordinal > c_base_ascii::BRACKET_CLOSE && $ordinal < c_base_ascii::DELETE)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: qtext. + * + * qtext = ASCII character codes 33, 35->91 and 93-126. + * qtext = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_qtext($ordinal) { + if ($ordinal == c_base_ascii::SPACE) { + return TRUE; + } + + if (($ordinal > c_base_ascii::QUOTE_DOUBLE && $ordinal < c_base_ascii::SLASH_BACKWARD) || ($ordinal > c_base_ascii::SLASH_BACKWARD && $ordinal < c_base_ascii::DELETE)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: vchar. + * + * vchar = Visible ASCII character codes 33->126. + * vchar = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_vchar($ordinal) { + if ($ordinal > c_base_ascii::SPACE && $ordinal < c_base_ascii::DELETE) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: tchar. + * + * tchar = Visible ASCII character codes 33->126, excluding: DQUOTE and "(),/:;<=>?@[\]{}". + * tchar = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ + protected function pr_rfc_char_is_tchar($ordinal) { + if ($ordinal > c_base_ascii::SPACE && $ordinal < c_base_ascii::DELETE) { + if ($ordinal == c_base_ascii::QUOTE_DOUBLE || $ordinal == c_base_ascii::PARENTHESIS_OPEN || $ordinal == c_base_ascii::PARENTHESIS_CLOSE ) { + return FALSE; + } + + if ($ordinal == c_base_ascii::COMMA || $ordinal == c_base_ascii::SLASH_FORWARD) { + return FALSE; + } + + if (($ordinal > c_base_ascii::NINE && $ordinal < c_base_ascii::UPPER_A) || ($ordinal > c_base_ascii::UPPER_Z && $ordinal < c_base_ascii::CARET)) { + return FALSE; + } + + if ($ordinal == c_base_ascii::BRACE_OPEN || $ordinal == c_base_ascii::BRACE_CLOSE) { + return FALSE; + } + + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: tchar68. + * + * This doesn't directly exist, but is supplied for use with token68 in the same way tchar is used by token. + * + * tchar = Visible ASCII character codes: 43, 45->57, 65->90, 95, 97->122, 126 + * tchar = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc7235#appendix-C + */ + protected function pr_rfc_char_is_tchar68($ordinal) { + if ($ordinal > c_base_ascii::COMMA && $ordinal < c_base_ascii::COLON) { + return TRUE; + } + + if (($ordinal > c_base_ascii::AT && $ordinal < c_base_ascii::BRACKET_OPEN) || ($ordinal > c_base_ascii::GRAVE && $ordinal < c_base_ascii::BRACE_OPEN)) { + return TRUE; + } + + if ($ordinal == c_base_ascii::UNDERSCORE || $ordinal == c_base_ascii::TILDE) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + // UTF_8-1/ASCII characters have already been checked, so return FALSE without doing additional pointless processing. + return FALSE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: CRLF. + * + * CRLF = carraige return or line feed (new line), codes 10, 13. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234#appendix-B + */ + protected function pr_rfc_char_is_crlf($ordinal) { + return $ordinal == c_base_ascii::NEW_LINE || $ordinal == c_base_ascii::CARRIAGE_RETURN; + } + + /** + * Check to see if character is: WSP. + * + * WSP = space or horizontal tab, codes 9, 32. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + * @see: https://tools.ietf.org/html/rfc5234#appendix-B + */ + protected function pr_rfc_char_is_wsp($ordinal) { + return $ordinal == c_base_ascii::TAB_HORIZONTAL || $ordinal == c_base_ascii::SPACE; + } + + /** + * Check to see if character is: SP. + * + * SP = space, codes 32. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_sp($ordinal) { + return $ordinal == c_base_ascii::SPACE; + } + + /** + * Check to see if character is: special. + * + * special = special/reserved visible characters, codes 34, 40, 41, 44, 46, 58, 59, 60, 62, 64, 91, 92, 93 + * - which are the characters: '(', ')', '<', '>', '[', ']', ':', ';', '@', '\', ',', '.', '"'. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + */ + protected function pr_rfc_char_is_special($ordinal) { + switch ($ordinal) { + case c_base_ascii::PARENTHESIS_OPEN: + case c_base_ascii::PARENTHESIS_CLOSE: + case c_base_ascii::LESS_THAN: + case c_base_ascii::GREATER_THAN: + case c_base_ascii::BRACKET_OPEN: + case c_base_ascii::BRACKET_CLOSE: + case c_base_ascii::COLON: + case c_base_ascii::COLON_SEMI: + case c_base_ascii::AT: + case c_base_ascii::SLASH_BACKWARD: + case c_base_ascii::COMMA: + case c_base_ascii::PERIOD: + case c_base_ascii::QUOTE_DOUBLE: + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: atext. + * + * atext = Visible ASCII characters, discluding special. + * atext = UTF_8-2, UTF_8-3, UTF_8-4. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc5234 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_atext($ordinal) { + return !$this->pr_rfc_char_is_special($ordinal) && $this->pr_rfc_char_is_vchar($ordinal); + } + + /** + * Check to see if character is: FWS or LWSP. + * + * FWS = space, tab, or CRLF, codes 9, 10, 13, 32. + * LWSP = identical to FWS (defined in rfc 4234). + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + * @see: https://tools.ietf.org/html/rfc5234 + */ + protected function pr_rfc_char_is_fws($ordinal) { + return $this->pr_rfc_char_is_crlf($ordinal) || $this->pr_rfc_char_is_wsp($ordinal); + } + + /** + * Check to see if character is: ALPHA. + * + * ALPHA = Visible ASCII character codes 65->90, 97->122. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_alpha($ordinal) { + if (($ordinal > c_base_ascii::AT && $ordinal < c_base_ascii::BRACKET_OPEN) || ($ordinal > c_base_ascii::GRAVE && $ordinal < c_base_ascii::BRACE_OPEN)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: BIT. + * + * BIT = ASCII character codes 48, 49. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_bit($ordinal) { + if ($ordinal == c_base_ascii::ZERO || $ordinal == c_base_ascii::ONE) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: CHAR. + * + * CHAR = ASCII character codes 1->127. + * CHAR = UTF_8-2, UTF_8-3, or UTF_8-4. + * + * Another name for this is UTF_8-OCTETS, except that based on the standard UTF_8-OCTETS refers to 1 or more UTF_8-chars. + * Given that this is a test against a single character, UTF_8-char and UTF_8-octets are synonymous. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: $this->pr_rfc_char_is_octet() + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + * @see: https://tools.ietf.org/html/rfc3629 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_char($ordinal) { + // null is not allowed, otherwise this is identical to UTF_8-octet. + if ($ordinal == c_base_ascii::NULL) { + return FALSE; + } + + return $this->pr_rfc_char_is_octet($ordinal); + } + + /** + * Check to see if character is: UTF_8-CHAR or UTF_8-OCTET. + * + * UTF_8-CHAR = ASCII character codes 0->127. + * UTF_8-CHAR = UTF_8-1, UTF_8-2, UTF_8-3, or UTF_8-4. + * + * Another name for this is UTF_8-OCTETS, except that based on the standard UTF_8-OCTETS refers to 1 or more UTF_8-chars. + * Given that this is a test against a single character, UTF_8-char and UTF_8-octets are synonymous. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + * @see: https://tools.ietf.org/html/rfc3629 + * @see: https://tools.ietf.org/html/rfc6532 + */ + protected function pr_rfc_char_is_octet($ordinal) { + if ($this->pr_rfc_char_is_utf8_1($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_2($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_3($ordinal)) { + return TRUE; + } + + if ($this->pr_rfc_char_is_utf8_4($ordinal)) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: CR. + * + * CR = Carriage Return, codes 13. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_cr($ordinal) { + if ($ordinal == c_base_ascii::CARRIAGE_RETURN) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: LF. + * + * LF = line feed (new line), codes 10. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_lf($ordinal) { + return $ordinal == c_base_ascii::NEW_LINE; + } + + /** + * Check to see if character is: CTL. + * + * CTL = ASCII control character codes 1->31, 127. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_ctl($ordinal) { + if (($ordinal > c_base_ascii::NULL && $ordinal < c_base_ascii::SPACE) || $ordinal == c_base_ascii::DELETE) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: DIGIT. + * + * DIGIT = ASCII character codes 48->57. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_digit($ordinal) { + if ($ordinal > c_base_ascii::SLASH_FORWARD && $ordinal < c_base_ascii::COLON) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: HEXDIG. + * + * HEXDIG = ASCII character codes 48->57, 65-70, 97->103. + * + * @param int $ordinal + * A code representing a single character to test. + * @param bool $lower_case + * (optional) When TRUE, lower case a-f will also be supported (ascii codes 97-103). + * Using lower case might be a violation of the standard, but this is unclear to me. + * Most things are forced to lower case for simplicity during tests anyway so this might be a necessary functionality. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_hexdigit($ordinal, $lower_case = FALSE) { + if ($ordinal > c_base_ascii::SLASH_FORWARD && $ordinal < c_base_ascii::COLON) { + return TRUE; + } + + if ($ordinal > c_base_ascii::GRAVE && $ordinal < c_base_ascii::LOWER_G) { + return TRUE; + } + + if ($lower_case) { + return FALSE; + } + + if ($ordinal > c_base_ascii::AT && $ordinal < c_base_ascii::UPPER_G) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: DQUOTE. + * + * DQUOTE = ASCII character codes 34. + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc4234 + */ + protected function pr_rfc_char_is_dquote($ordinal) { + if ($ordinal == c_base_ascii::QUOTE_DOUBLE) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: unreserved. + * + * unreserved = ASCII character codes 45, 46, 48->57, 65->90, 95, 97->122, 126 + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * https://tools.ietf.org/html/rfc3986#appendix-A + */ + protected function pr_rfc_char_is_unreserved($ordinal) { + if (self::pr_rfc_char_is_alpha($ordinal) || self::pr_rfc_char_is_digit($ordinal) ) { + return TRUE; + } + + if ($ordinal == c_base_ascii::MINUS || $ordinal == c_base_ascii::PERIOD || $ordinal == c_base_ascii::UNDERSCORE || $ordinal == c_base_ascii::TILDE) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: unreserved. + * + * reserved = ASCII character codes 33, 35, 36, 38->44, 47, 58, 59, 61, 63, 64, 91, 93 + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc3986#appendix-A + */ + protected function pr_rfc_char_is_reserved($ordinal) { + if ($ordinal == c_base_ascii::EXCLAMATION || $ordinal == c_base_ascii::HASH || $ordinal == c_base_ascii::DOLLAR) { + return TRUE; + } + + if ($ordinal > c_base_ascii::PERCENT && $ordinal < c_base_ascii::MINUS) { + return TRUE; + } + + if ($ordinal == c_base_ascii::SLASH_FORWARD || $ordinal == c_base_ascii::COLON || $ordinal == c_base_ascii::COLON_SEMI || $ordinal == c_base_ascii::EQUAL) { + return TRUE; + } + + if ($ordinal == c_base_ascii::QUESTION_MARK || $ordinal == c_base_ascii::AT || $ordinal == c_base_ascii::BRACKET_OPEN || $ordinal == c_base_ascii::BRACKET_CLOSE) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: gen-delims. + * + * gen-delims = ASCII character codes 35, 47, 58, 63, 64, 91, 93 + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc3986#appendix-A + */ + protected function pr_rfc_char_is_gen_delims($ordinal) { + if ($ordinal == c_base_ascii::COLON || $ordinal == c_base_ascii::SLASH_FORWARD || $ordinal == c_base_ascii::QUESTION_MARK || $ordinal == c_base_ascii::HASH) { + return TRUE; + } + + if ($ordinal == c_base_ascii::BRACKET_OPEN || $ordinal == c_base_ascii::BRACKET_CLOSE || $ordinal == c_base_ascii::AT) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: sub-delims. + * + * sub-delims = ASCII character codes 33, 36, 38->44, 59, 61 + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE on match, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc3986#appendix-A + */ + protected function pr_rfc_char_is_sub_delims($ordinal) { + if ($ordinal == c_base_ascii::EXCLAMATION || $ordinal == c_base_ascii::DOLLAR) { + return TRUE; + } + + if ($ordinal > c_base_ascii::PERCENT && $ordinal < c_base_ascii::MINUS) { + return TRUE; + } + + if ($ordinal == c_base_ascii::COLON_SEMI || $ordinal == c_base_ascii::EQUAL) { + return TRUE; + } + + return FALSE; + } + + /** + * Check to see if character is: UTF_8-1 (ASCII). + * + * The standard claims the following ranges: + * - [0x00 -> 0x7f] + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE when UTF_8-1 or ASCII, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc3629 + */ + protected function pr_rfc_char_is_utf8_1($ordinal) { + return ($ordinal & c_base_utf8::MASK_1) == 0; + } + + /** + * Check to see if character is: UTF_8-2 (ASCII). + * + * The standard claims the following ranges: + * - [0xc2 -> 0xdf] [0x80 -> 0xbf] + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE when UTF_8-1 or ASCII, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc3629 + */ + protected function pr_rfc_char_is_utf8_2($ordinal) { + return ($ordinal & c_base_utf8::MASK_2) == 0 && ($ordinal & c_base_utf8::MARK_2) == c_base_utf8::MARK_2; + } + + /** + * Check to see if character is: UTF_8-3 (ASCII). + * + * The standard claims the following ranges: + * - [0xe0] [0xa0 -> 0xbf] [0x80 -> 0xbf] + * - [0xe1 -> 0xec] [0x80 -> 0xbf] [0x80 -> 0xbf] + * - [0xed] [0x80 -> 0x9f] [0x80 -> 0xbf] + * - [0xee -> 0xef] [0x80 -> 0xbf] [0x80 -> 0xbf] + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE when UTF_8-1 or ASCII, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc3629 + */ + protected function pr_rfc_char_is_utf8_3($ordinal) { + return ($ordinal & c_base_utf8::MASK_3) == 0 && ($ordinal & c_base_utf8::MARK_3) == c_base_utf8::MARK_3; + } + + /** + * Check to see if character is: UTF_8-4 (ASCII). + * + * The standard claims the following ranges: + * - [0xf0] [0x90 -> 0xbf] [0x80 -> 0xbf] [0x80 -> 0xbf] + * - [0xf1 -> 0xf3] [0x80 -> 0xbf] [0x80 -> 0xbf] [0x80 -> 0xbf] + * - [0xf4] [0x80 -> 0x8f] [0x80 -> 0xbf] [0x80 -> 0xbf] + * + * @param int $ordinal + * A code representing a single character to test. + * + * @return bool + * TRUE when UTF_8-1 or ASCII, FALSE otherwise. + * + * @see: https://tools.ietf.org/html/rfc822 + * @see: https://tools.ietf.org/html/rfc3629 + */ + protected function pr_rfc_char_is_utf8_4($ordinal) { + return ($ordinal & c_base_utf8::MARK_4) == c_base_utf8::MARK_4; + } +} diff --git a/common/base/classes/base_rfc_string.php b/common/base/classes/base_rfc_string.php new file mode 100644 index 0000000..61adedf --- /dev/null +++ b/common/base/classes/base_rfc_string.php @@ -0,0 +1,2444 @@ + array(), + 'characters' => array(), + 'invalid' => FALSE, + ); + + $ordinals = c_base_utf8::s_string_to_ordinals($text); + if ($ordinals instanceof c_base_return_false) { + unset($ordinals); + $result['invalid'] = TRUE; + return $result; + } + + $result['ordinals'] = $ordinals->get_value_exact(); + unset($ordinals); + + $characters = c_base_utf8::s_ordinals_to_string_array($result['ordinals']); + if ($characters instanceof c_base_return_error) { + unset($characters); + $result['invalid'] = TRUE; + return $result; + } + + $result['characters'] = $characters->get_value_exact(); + unset($characters); + + return $result; + } + + /** + * Converts a string into a ordinals array but not a characters array. + * + * This should be somewhat faster if characters are not intended to be used. + * + * @param string $text + * The string to convert into ordinals array and characters array. + * + * @return array + * An array containing: + * ordinals: an array of ordinal values representing the string. + * characters: an empty array. + * invalid: FALSE on success, TRUE otherwise. + * + * @see: c_base_utf8::s_string_to_ordinals() + * @see: c_base_utf8::s_ordinals_to_string_array() + */ + protected function pr_rfc_string_prepare_ordinals($text) { + $result = array( + 'ordinals' => array(), + 'characters' => array(), + 'invalid' => FALSE, + ); + + $ordinals = c_base_utf8::s_string_to_ordinals($text); + if ($ordinals instanceof c_base_return_error) { + unset($ordinals); + $result['invalid'] = TRUE; + return $result; + } + + $result['ordinals'] = $ordinals->get_value_exact(); + unset($ordinals); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: quoted-string. + * + * This assumes that the start position is immediately after the initial DQUOTE. + * This will stop at the closing DQUOTE or if there is trailing valid CFWS, then it will stop at the end of the CFWS. + * + * A quoted-string has the following syntax: + * - [CFWS] DQUOTE *([FWS] qtext / "\"DQUOTE) [FWS] DQUOTE [CFWS] + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * If self::STOP_AT_CLOSING_CHARACTER, then stop at the end of a double quote and do no further processing. + * + * @return array + * The processed information, with comments separated: + * - 'comments': an array containg the comment before and comment after, if found. + * - 'text': A string containing the processed quoted string. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_qtext() + */ + protected function pr_rfc_string_is_quoted_string($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'comments' => array( + 'before' => NULL, + 'after' => NULL, + ), + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + $stop_at_closing_quote = FALSE; + if ($stop < 0) { + if ($stop == self::STOP_AT_CLOSING_CHARACTER) { + $stop_at_closing_quote = TRUE; + } + + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $comment_first = FALSE; + $comment_last = FALSE; + $quote_closed = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::SLASH_BACKWARD) { + if ($quote_closed) { + // only comments and FWS are allowed after $closing_quote is reached. + $result['invalid'] = TRUE; + break; + } + + if (!$comment_first) { + // anything that is not a comment and not FWS means that comments are no longer allowed at this point until end of quoted string. + $comment_first = TRUE; + } + + // check for and handle delimiters. + $result['current']++; + + if ($ordinals[$result['current']] == c_base_ascii::QUOTE_DOUBLE) { + $result['text'] .= $characters[$result['current']]; + continue; + } + + if ($ordinals[$result['current']] == c_base_ascii::SLASH_BACKWARD) { + $result['text'] .= $characters[$result['current']]; + continue; + } + + $result['current']--; + } + elseif ($code == c_base_ascii::QUOTE_DOUBLE) { + if ($quote_closed) { + // double quote may be supplied only once. + $result['invalid'] = TRUE; + break; + } + + if ($stop_at_closing_quote) { + break; + } + + $quote_closed = TRUE; + continue; + } + elseif ($code == c_base_ascii::PARENTHESIS_OPEN) { + if ($comment_first || $comment_last) { + // there may be only 1 comment at the start and only 1 comment at the end. + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_comment($ordinals, $characters, $result['current'], $stop); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + unset($parsed); + break; + } + + if ($quote_closed) { + $comment_last = TRUE; + $results['comments']['after'] = $parsed['comment']; + } + else { + $comment_first = TRUE; + $results['comments']['before'] = $parsed['comment']; + } + unset($parsed); + } + elseif ($code == c_base_ascii::PARENTHESIS_CLOSE) { + // an isolated parenthesis is invald. + $result['invalid'] = TRUE; + break; + } + elseif ($quote_closed) { + if ($this->pr_rfc_char_is_fws($code)) { + continue; + } + + // only comments and FWS are allowed after $closing_quote is reached. + $result['invalid'] = TRUE; + break; + } + else { + if (!$this->pr_rfc_char_is_ctext($code) && !$this->pr_rfc_char_is_fws($code)) { + $result['invalid'] = TRUE; + break; + } + + if (!$comment_first) { + // anything that is not a comment and not FWS means that comments are no longer allowed at this point until end of quoted string. + $comment_first = TRUE; + } + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + unset($comment_first); + unset($comment_last); + unset($quote_closed); + unset($stop_at_closing_quote); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: entity-tag. + * + * This assumes that the start position is immediately after the initial DQUOTE. + * This will stop at the closing DQUOTE. + * + * Because the start position after the initial DQUOTE, the calling function must handle the "W/" characters. + * + * An entity-tag has the following syntax: + * - 1*(W/) DQUOTE (vchar, except double qoutes) DQUOTE + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_qtext() + */ + protected function pr_rfc_string_is_entity_tag($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::QUOTE_DOUBLE) { + break; + } + elseif (!$this->pr_rfc_char_is_vchar($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: comment. + * + * This assumes that the start position is immediately after the initial "(". + * + * Comments allow for delimited text as well as other comments to be embedded. + * - This function does not recurse embedded comments. + * - Embedded comments are still processed, so an open parenthesis must be followed by a closing parenthesis or the string is considered invalid. + * + * A comment has the following syntax: + * - "(" *([FWS] ctext / "\"DQUOTE / comment) [FWS] ")" + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param null|int $start + * (optional) Specify a starting point in which to begin processing. + * @param null|int $stop + * (optional) Specify a stopping point in which to end processing. + * + * @return array + * The processed information, with comments separated: + * - 'comment': a string containing the comment or NULL if no comments defined (such as an empty comment). + * - 'current': an integer representing the position the counter where processing stopped. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + */ + protected function pr_rfc_string_is_comment($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'comment' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $comment_depth = 0; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::SLASH_BACKWARD) { + // check for and handle delimiters. + $result['current']++; + + if ($ordinals[$result['current']] == c_base_ascii::QUOTE_DOUBLE) { + $result['comment'] .= $characters[$result['current']]; + + continue; + } + + if ($ordinals[$result['current']] == c_base_ascii::SLASH_BACKWARD) { + $result['comment'] .= $characters[$result['current']]; + + continue; + } + + $result['current']--; + } + elseif ($code == c_base_ascii::PARENTHESIS_OPEN) { + // look for open-parenthesis to handle comments within a comment. + $comment_depth++; + } + elseif ($code == c_base_ascii::PARENTHESIS_CLOSE) { + // handle end of comment. + if ($comment_depth == 0) { + // the current position will remain on the closing ')'. + // use -1 to designate that the comment has been properly closed. + $comment_depth = -1; + break; + } + else { + $comment_depth--; + } + } + elseif (!$this->pr_rfc_char_is_ctext($code) && !$this->pr_rfc_char_is_fws($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['comment'] .= $characters[$result['current']]; + } + unset($code); + + if ($comment_depth > -1) { + $result['invalid'] = TRUE; + } + unset($comment_depth); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: digit. + * + * A string that is a digit has the following syntax: + * - 1*(digit) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_digit() + */ + protected function pr_rfc_string_is_digit($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_digit($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: token. + * + * A string that is a digit has the following syntax: + * - 1*(tchar) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag (the entire string, not broken down into individual tokens). + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_tchar() + * @see: https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ + protected function pr_rfc_string_is_token($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: token68. + * + * A string that is a digit has the following syntax: + * - 1*(tchar68) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed token (the entire string, not broken down into individual tokens). + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_tchar() + * @see: https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ + protected function pr_rfc_string_is_token68($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_tchar68($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: token_quoted. + * + * This is not a literal standard, but is implied in things such as 'cache-control'. + * + * A string that is a digit has the following syntax: + * - 1*(1*(tchar) / quoted-string) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag (the entire string, not broken down into individual tokens). + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_tchar() + * @see: https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @see: https://tools.ietf.org/html/rfc7234#section-5.2.3 + */ + protected function pr_rfc_string_is_token_quoted($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $not_quoted = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::QUOTE_DOUBLE) { + if ($not_quoted) { + // if the first, non-whitespace, character is not a quote, then a quote anywhere else is invalid. + $result['invalid'] = TRUE; + break; + } + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_quoted_string($ordinals, $characters, $result['current'], self::STOP_AT_CLOSING_CHARACTER); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + break; + } + + // the closing quote must be the last value, so if the stop point was not reached at the closing quote position, then fail. + if ($stop != ($result['current'] + 1)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] = $parsed['text']; + unset($parsed); + + break; + } + elseif (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + $not_quoted = TRUE; + $result['text'] .= $characters[$result['current']]; + } + unset($code); + unset($not_quoted); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: credentials. + * + * A string that is a digit has the following syntax: + * - token [ 1*(ws) ( token68 / [ ( "," / token *(ws) "=" *(ws) ( token / quoted-string ) ) *( *(ws) "," [ *(ws) token *(ws) "=" *(ws) ( token / quoted-string ) ] ) ] ) ] + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: https://tools.ietf.org/html/rfc7235#appendix-C + */ + protected function pr_rfc_string_is_credentials($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + // @todo: this looks like a lot of work, so deal with this at some point in the future because this is a very low priority function. + $result['invalid'] = TRUE; +/* + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_digit($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); +*/ + return $result; + } + + /** + * Processes a string based on the rfc syntax: range. + * + * A string that is a digit has the following syntax: + * - ??? + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed entity tag. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: https://tools.ietf.org/html/rfc7233#appendix-D + */ + protected function pr_rfc_string_is_range($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + // @todo: this looks like a lot of work, so deal with this at some point in the future because this is a low to moderate priority function. + $result['invalid'] = TRUE; +/* + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_digit($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); +*/ + return $result; + } + + /** + * Processes a string based on the rfc syntax: vchar, wsp. + * + * A string that has the following syntax: + * - 1*(vchar / wsp) + * + * This is for generic text that is any basic vchar or whitespace. + * This will often be used as the default when no particular syntax is specified. + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed text. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_vchar() + * @see: base_rfc_char::pr_rfc_char_is_wsp() + */ + protected function pr_rfc_string_is_basic($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_vchar($code) && !$this->pr_rfc_char_is_wsp($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: text. + * + * A string that has the following syntax: + * - 1*(text) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed text. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_text() + */ + protected function pr_rfc_string_is_text($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_text($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: atext. + * + * A string that has the following syntax: + * - 1*(atext) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed text. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_atext() + */ + protected function pr_rfc_string_is_atext($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_atext($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: dtext. + * + * A string that has the following syntax: + * - 1*(dtext) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed text. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_dtext() + */ + protected function pr_rfc_string_is_dtext($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_dtext($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: qtext. + * + * A string that has the following syntax: + * - 1*(qtext) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'text': A string containing the processed text. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_is_qtext() + */ + protected function pr_rfc_string_is_qtext($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'text' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!$this->pr_rfc_char_is_qtext($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['text'] .= $characters[$result['current']]; + } + unset($code); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: negotiation. + * + * There is no literal "negotiation" rfc syntax, but there are a number of different syntax that utilize this design. + * This is being considered an implicit standard. + * Negotation can be referred to as priority, or ranking, where the string is a list of preferred option based on some rank (specified by q=). + * The structure contradicts normal syntax logic used by most system in that the different groups are broken up by commas and the settings broken up by semicolons. + * + * For example, normal syntax logic would have the string "text/html; q=1.0, text/*; q=0.8" be broken up into the following structure: + * - text/html + * - q=1.0, text/* + * - q=0.8 + * + * The syntax logic used by this standard should instead have the string "text/html; q=1.0, text/*; q=0.8" be broken up into the following structure: + * - text/html; q=1.0 + * - text/*; q=0.8 + * + * Which should be read as the following: + * - text/html, q=1.0 + * - text/*, q=0.8 + * + * Another bizarre behavior of this standard is the use of decimals (perhaps they are considering values a percentage?). + * This behavior is rather wasteful because decimals tend to get processed differently, but is logically irrelevant. + * + * For example, the string "text/html; q=1.0, text/*; q=0.8" could simply be rewritten: + * - "text/html; q=10, text/*; q=8" + * + * To avoid encoding issues of floating points, all decimal values will be treated as integers and stores as such by this function. + * - All decimal values will be multiplied by 1000 and then the remaining valid integers will be truncated. + * + * The default behavior would be to assume case-sensitive, but just in case, this will accept uppercase "Q" and save it as a lowercase "q". + * + * A string that has the following syntax: + * - 1*(atext) *(wsp) *(";" *(wsp) q=1*(digit / 1*(digit) "." 1*(digit))) *(*(wsp) "," *(wsp) 1*(atext) *(wsp) *(";" *(wsp) 1*(digit / 1*(digit) "." 1*(digit)))) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed. + * + * @return array + * The processed information: + * - 'choices': An array of negotiation choices. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + */ + protected function pr_rfc_string_is_negotiation($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'choices' => array(), + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $choice = array( + 'choice' => NULL, + 'weight' => NULL, + ); + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::COLON_SEMI) { + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + + // search for the "q" character. + for (; $result['current'] < $stop; $result['current']++) { + // allow uppercase "Q" but force it to become lowercase "q". + if ($ordinals[$result['current']] == c_base_ascii::UPPER_Q) { + $ordinals[$result['current']] = c_base_ascii::LOWER_Q; + $characters[$result['current']] = c_base_ascii::LOWER_Q; + } + + if ($ordinals[$result['current']] == c_base_ascii::LOWER_Q) { + break; + } + + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + $result['invalid'] = TRUE; + break; + } + } + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + break; + } + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + + // search for the "=" character. + for (; $result['current'] < $stop; $result['current']++) { + if ($ordinals[$result['current']] == c_base_ascii::EQUAL) { + break; + } + + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + $result['invalid'] = TRUE; + break; + } + } + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + break; + } + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + + // Skip past whitespace until the first digit is found. + for (; $result['current'] < $stop; $result['current']++) { + if ($this->pr_rfc_char_is_digit($ordinals[$result['current']])) { + $result['current']--; + break; + } + + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + $result['invalid'] = TRUE; + break; + } + } + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + } + + if ($result['invalid']) { + break; + } + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + + // Process the weight value, removing decimal numbers and multiplying by 1000. + $weight = 0; + $multiple = 1000; + $period = FALSE; + $base = 1; + for (; $result['current'] < $stop; $result['current']++) { + if ($ordinals[$result['current']] == c_base_ascii::PERIOD) { + if ($period) { + $result['invalid'] = TRUE; + break; + } + + $multiple /= 10; + $period = TRUE; + continue; + } + + if ($this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + + if (!$this->pr_rfc_char_is_digit($ordinals[$result['current']])) { + if ($ordinals[$result['current']] == c_base_ascii::COMMA) { + break; + } + + $result['invalid'] = TRUE; + break; + } + + if ($period) { + $weight += intval($characters[$result['current']]) * $multiple; + $multiple /= 10; + } + else { + $weight *= $base; + $weight += (intval($characters[$result['current']]) * $multiple); + $base *= 10; + } + } + unset($multiple); + unset($period); + unset($base); + + if ($result['invalid']) { + unset($weight); + break; + } + + + // the weight has been identified, so store its value and prepare for another run. + $choice['weight'] = $weight; + if (!isset($result['choices'][$weight])) { + $result['choices'][$weight] = array(); + } + + // strip out leading and trailing whitespace. + $choice['choice'] = preg_replace('/(^\s+)|(\s+$)/us', '', $choice['choice']); + + $result['choices'][$weight][$choice['choice']] = $choice; + unset($weight); + + $choice = array( + 'choice' => NULL, + 'weight' => NULL, + ); + + if ($result['current'] >= $stop) { + break; + } + + + // skip past trailing whitespace. + if ($this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + } + + // if stop is reached at this point, then a valid end of string has been reached. + if ($result['current'] >= $stop) { + break; + } + + + // look for comma, which will designate that another pass is allowed, otherwise the string is invalid. + if ($ordinals[$result['current']] == c_base_ascii::COMMA) { + continue; + } + + $result['invalid'] = TRUE; + break; + } + elseif ($code == c_base_ascii::COMMA) { + // this is an unweighted choice. + $choice['weight'] = NULL; + if (!isset($result['choices'][NULL])) { + $result['choices'][NULL] = array(); + } + + // strip out leading and trailing whitespace. + $choice['choice'] = preg_replace('/(^\s+)|(\s+$)/us', '', $choice['choice']); + + $result['choices'][NULL][$choice['choice']] = $choice; + + $choice = array( + 'choice' => NULL, + 'weight' => NULL, + ); + + continue; + } + elseif (!$this->pr_rfc_char_is_atext($code) && !$this->pr_rfc_char_is_wsp($code)) { + $result['invalid'] = TRUE; + break; + } + + $choice['choice'] .= $characters[$result['current']]; + } + unset($code); + + // If there were no commas or semi-colons, then this is a single, unweighted, choice (which is valid). + if ($choice['choice'] != NULL) { + $choice['weight'] = NULL; + if (!isset($result['choices'][NULL])) { + $result['choices'][NULL] = array(); + } + + // strip out leading and trailing whitespace. + $choice['choice'] = preg_replace('/(^\s+)|(\s+$)/us', '', $choice['choice']); + + $result['choices'][NULL][$choice['choice']] = $choice; + } + unset($choice); + + if ($result['invalid']) { + return $result; + } + + // sort the choices array. + krsort($result['choices']); + + // The NULL key should be the first key in the weight. + $this->p_prepend_array_value(NULL, $result['choices']); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: media-type. + * + * The standard does not explicitly state that there is whitespace, but it instead implies it. + * Therefore optional whitespace are added. + * + * An media-type has the following syntax: + * - 1*(tchar) "/" 1*(tchar) *(*(wsp) ";" *(wsp) 1*(1*(tchar) *(wsp) "=" *(wsp) 1*(tchar) / (quoted-string))) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'media': A string containing the processed media type (without parameters). + * - 'parameters': An array of strings representing the parameters. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + protected function pr_rfc_string_is_media_type($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'media' => NULL, + 'parameters' => array(), + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $found_slash = FALSE; + $process_parameters = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($code == c_base_ascii::SLASH_FORWARD) { + if (!$process_parameters) { + if ($found_slash) { + $result['invalid'] = TRUE; + break; + } + + $found_slash = TRUE; + $result['media'] .= $characters[$result['current']]; + continue; + } + } + elseif ($code == c_base_ascii::COLON_SEMI || $found_slash && $this->pr_rfc_char_is_wsp($code)) { + if ($found_slash && $this->pr_rfc_char_is_wsp($code)) { + // in this case, the semi-colon has yet to be found, so seek until a semi-colon is found. + // any and all non-semi-colon and non-whitespace means that the string is invalid. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + if ($ordinals[$result['current']] == c_base_ascii::COLON_SEMI) { + break; + } + + $result['invalid'] = TRUE; + break; + } + } + + if ($result['invalid']) { + break; + } + } + + // begin processing the set of media type parameters, first skipping past the semi-colon. + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + // Check for: *(token "=" (token / quoted-string)). + $processed_token = NULL; + $found_equal = FALSE; + $parameter_name = NULL; + $parameter_value = NULL; + for (; $result['current'] < $stop; $result['current']++) { + $subcode = $ordinals[$result['current']]; + + if ($this->pr_rfc_char_is_wsp($subcode)) { + if (is_null($processed_token)) { + // skip past leading whitespace. + continue; + } + + $processed_token = TRUE; + continue; + } + elseif ($subcode == c_base_ascii::EQUAL) { + if ($found_equal || $process_whitespace) { + // it cannot start with an equal sign, so if $process_whitespace is TRUE, then this is an invalid equal sign. + $result['invalid'] = TRUE; + break; + } + + // skip past all whitespace. + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + // check for quoted_string, which must begin with a double quote. + if ($subcode == c_base_ascii::QUOTE_DOUBLE) { + // skip past the initial double quote. + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_quoted_string($ordinals, $characters, $result['current'], self::STOP_AT_CLOSING_CHARACTER); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + break; + } + + $parameter_value = $parsed['text']; + unset($parsed); + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + // check for semi-colon, if one is found then continue, otherwise end if at stop point. + $result['current']++; + if ($result['current'] >= $stop) { + $result['parameters'][$parameter_name] = $parameter_value; + break; + } + + // skip past any whitespace to see if there is a semi-colon. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop || $ordinals[$result['current']] != c_base_ascii::COLON_SEMI) { + $result['invalid'] = TRUE; + break; + } + + $result['parameters'][$parameter_name] = $parameter_value; + } + else { + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_tchar($ordinals[$result['current']])) { + $result['invalid'] = TRUE; + break; + } + + $parameter_value .= $characters[$result['current']]; + } + + if ($result['invalid']) { + break; + } + + $result['parameters'][$parameter_name] = $parameter_value; + } + + $parameter_name = NULL; + $parameter_value = NULL; + $found_equal = FALSE; + $processed_token = NULL; + + continue; + } + elseif (!$this->pr_rfc_char_is_tchar($subcode)) { + if ($found_equal) { + if ($subcode == c_base_ascii::COLON_SEMI) { + // save parameter and value and continue. + $result['parameters'][$parameter_name] = $parameter_value; + + $parameter_name = NULL; + $parameter_value = NULL; + $found_equal = FALSE; + $processed_token = NULL; + + continue; + } + } + + $result['invalid'] = TRUE; + break; + } + elseif ($processed_token) { + // processed token is set to TRUE after the first space following the token is found. + // spaces are not allowed inside an unqouted token and is therefore invalid. + $result['invalid'] = TRUE; + break; + } + + if ($found_equal) { + $parameter_value .= $characters[$subcode]; + } + else { + $parameter_name .= $characters[$subcode]; + } + + $processed_token = FALSE; + } + unset($subcode); + unset($process_whitespace); + + if ($found_equal) { + if (!is_null($parameter_name)) { + $result['parameters'][$parameter_name] = $parameter_value; + } + } + else { + // a parameter name without an equal sign to designate a parameter value is invalid. + $result['invalid'] = TRUE; + } + unset($processed_name); + unset($processed_value); + unset($found_equal); + + // all parameters have been processed, should be on or after $stop point or there is an invalid character. + if (!$result['invalid'] && $result['current'] < $stop) { + $result['invalid'] = TRUE; + } + + break; + } + elseif (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + $result['media'] .= $characters[$result['current']]; + $process_parameters = TRUE; + } + unset($code); + unset($found_slash); + unset($process_parameters); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: valued_token. + * + * There is no explicit "valued_token" standard, but it is implied and used in many ways. + * One such example is with the HTTP header "content-disposition". + * + * The standard does not seem to specify *(wsp) in all cases. + * Given that this is not a literal standard, strictness will be loosened a little to allow optional whitespace between the ";" and the "=". + * + * The standard does not explicitly state that there is whitespace, but in certain cases it implies whitespace usage. + * Therefore optional whitespace are added. + * + * A valued_token has the following syntax: + * - *(wsp) 1*(tchar) *(*(wsp) ";" *(wsp) 1*(1*(tchar) *(wsp) "=" *(wsp) 1*(tchar) / (quoted-string))) *(wsp) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'tokens': An array of strings representing the parameters, with the keys being the valued token name and the values being the valued token value. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: self::pr_rfc_string_is_valued_token_comma() + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: base_rfc_char::pr_rfc_string_is_media_type() + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + protected function pr_rfc_string_is_valued_token($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'tokens' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($this->pr_rfc_char_is_wsp($code)) { + // @todo: handle whitespace between '='. + if (is_null($token_name)) { + continue; + } + + // if the whitespace is not leading whitespace, then the end of the token has been reached. + // A stop point, an equal sign, or a semi-colon must be reached for the token and value pair to be valid. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + if ($ordinals[$result['current']] == c_base_ascii::COLON_SEMI) { + $result['tokens'][$token_name] = $token_value; + + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + // skip past all whitespace following the semi-colon. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + elseif ($ordinals[$result['current']] == c_base_ascii::EQUAL && !$processed_name) { + $processed_name = TRUE; + + // skip past all whitespace following the equal. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + + $result['invalid'] = TRUE; + break; + } + elseif ($code == c_base_ascii::COLON_SEMI) { + $result['tokens'][$token_name] = $token_value; + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + // skip past all whitespace following the semi-colon. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + elseif ($code == c_base_ascii::QUOTE_DOUBLE) { + if (!$processed_name) { + // the token name is not allowed to be a quoted string. + $result['invalid'] = TRUE; + break; + } + + // skip past the initial double quote. + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_quoted_string($ordinals, $characters, $result['current'], self::STOP_AT_CLOSING_CHARACTER); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + break; + } + + $token_value = $parsed['text']; + unset($parsed); + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + $result['tokens'][$token_name] = $token_value; + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + continue; + } + elseif (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + if ($processed_name) { + $token_value .= $characters[$result['current']]; + } + else { + $token_name .= $characters[$result['current']]; + } + } + unset($code); + unset($processed_name); + + if (!is_null($token_name) && $result['current'] >= $stop) { + // the stop point was reached, make sure to the token_name and token_value that were last being processed. + $result['tokens'][$token_name] = $token_value; + } + unset($token_name); + unset($token_value); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: valued_token_comma. + * + * This is similar to valued_token, expect that instead of using ';', a ',' is used as a separator. + * + * There is no explicit "valued_token" standard, but it is implied and used in many ways. + * One such example is with the HTTP header "cache-control". + * + * The standard does not seem to specify *(wsp) in all cases. + * Given that this is not a literal standard, strictness will be loosened a little to allow optional whitespace between the ";" and the "=". + * + * The standard does not explicitly state that there is whitespace, but in certain cases it implies whitespace usage. + * Therefore optional whitespace are added. + * + * A valued_token has the following syntax: + * - *(wsp) 1*(tchar) *(*(wsp) "=" *(wsp) 1*(tchar) / (quoted-string)) *(*(wsp) "," *(wsp) 1*(1*(tchar) *(*(wsp) "=" *(wsp) 1*(tchar) / (quoted-string)))) *(wsp) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'tokens': An array of strings representing the parameters, with the keys being the valued token name and the values being the valued token value. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: self::pr_rfc_string_is_valued_token() + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: base_rfc_char::pr_rfc_string_is_media_type() + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + protected function pr_rfc_string_is_valued_token_comma($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'tokens' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($this->pr_rfc_char_is_wsp($code)) { + // @todo: handle whitespace between '='. + if (is_null($token_name)) { + continue; + } + + // if the whitespace is not leading whitespace, then the end of the token has been reached. + // A stop point, an equal sign, or a semi-colon must be reached for the token and value pair to be valid. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + if ($ordinals[$result['current']] == c_base_ascii::COLON_SEMI) { + $result['tokens'][$token_name] = $token_value; + + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + // skip past all whitespace following the semi-colon. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + elseif ($ordinals[$result['current']] == c_base_ascii::EQUAL && !$processed_name) { + $processed_name = TRUE; + + // skip past all whitespace following the equal. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + + $result['invalid'] = TRUE; + break; + } + elseif ($code == c_base_ascii::COMMA) { + $result['tokens'][$token_name] = $token_value; + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + // skip past all whitespace following the semi-colon. + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + continue; + } + elseif ($code == c_base_ascii::QUOTE_DOUBLE) { + if (!$processed_name) { + // the token name is not allowed to be a quoted string. + $result['invalid'] = TRUE; + break; + } + + // skip past the initial double quote. + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + break; + } + + $parsed = $this->pr_rfc_string_is_quoted_string($ordinals, $characters, $result['current'], self::STOP_AT_CLOSING_CHARACTER); + $result['current'] = $parsed['current']; + + if ($parsed['invalid']) { + $result['invalid'] = TRUE; + break; + } + + $token_value = $parsed['text']; + unset($parsed); + + if ($result['current'] >= $stop) { + // must break now so that the 'current' counter remains at the stop point. + break; + } + + $result['tokens'][$token_name] = $token_value; + $token_name = NULL; + $token_value = NULL; + $processed_name = FALSE; + + continue; + } + elseif (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + if ($processed_name) { + $token_value .= $characters[$result['current']]; + } + else { + $token_name .= $characters[$result['current']]; + } + } + unset($code); + unset($processed_name); + + if (!is_null($token_name) && $result['current'] >= $stop) { + // the stop point was reached, make sure to the token_name and token_value that were last being processed. + $result['tokens'][$token_name] = $token_value; + } + unset($token_name); + unset($token_value); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: commad_token. + * + * This is a bit bizarre in that the standard allows for leading commas with nothing defined. + * - This function will accept that syntax, but those commas will be ignored. + * - Even more oddly, leading white space is not supported, but this too is bizarre and inconsistent. + * - A simpler syntax will therefore be used. + * + * A valued_token has the following syntax: + * - 1*(*(ws) "," *(ws) token) + * + * Original valued_token standard syntax: + * - *("," *(ws)) token *(*(ws) "," *(ws) token) + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'tokens': An array of strings representing the tokens. + * The array keys will represent the order in which they were processed. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: https://tools.ietf.org/html/rfc7230#appendix-B + */ + protected function pr_rfc_string_is_commad_token($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'tokens' => NULL, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + $token_value = NULL; + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if ($this->pr_rfc_char_is_wsp($code)) { + if (is_null($token_value)) { + continue; + } + + // End of token reached, now seek whitespace to end of line or a comma. + $result['current']++; + if ($result['current'] >= $stop) { + break; + } + + for (; $result['current'] < $stop; $result['current']++) { + if (!$this->pr_rfc_char_is_wsp($ordinals[$result['current']])) { + break; + } + } + + if ($ordinals[$result['current']] == c_base_ascii::COMMA) { + if (is_null($token_value)) { + // empty values separated by commas are to be ignored. + continue; + } + + $result['tokens'][] = $token_value; + $tokan_value = NULL; + continue; + } + + $result['invalid'] = TRUE; + break; + } + elseif ($code == c_base_ascii::COMMA) { + if (is_null($token_value)) { + // empty values separated by commas are to be ignored. + continue; + } + + $result['tokens'][] = $token_value; + $tokan_value = NULL; + continue; + } + elseif (!$this->pr_rfc_char_is_tchar($code)) { + $result['invalid'] = TRUE; + break; + } + + $token_value .= $characters[$result['current']]; + } + unset($code); + + if (!is_null($token_value) && $result['current'] >= $stop) { + // the stop point was reached, make sure the token_value was last being processed. + $result['tokens'][] = $token_value; + } + unset($token_value); + + return $result; + } + + /** + * Processes a string based on the rfc syntax: query. + * + * This is also used by the rfc syntax: fragment. + * + * A query has the following syntax: + * - *(ALPHA / DIGIT / "-" / "." / "_" / "~" / "%" HEXDIG HEXDIG / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" / ":" / "@" / "/" / "?") + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'address': A string containing the processed ip address. + * When is_future is TRUE, this is an array containing: + * - 'version': The ipfuture version. + * - 'ip': The ip address. + * - 'is_future': A boolean that when TRUE represents an ipvfuture address and when FALSE represents an ipv6 address. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + protected function pr_rfc_string_is_query($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'address' => NULL, + 'is_future' => FALSE, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + } + + /** + * Processes a string based on the rfc syntax: ip-literal. + * + * This assumes that the start position is immediately after the initial "[". + * This will stop at the closing "]". + * + * An ip-literal has the following syntax: + * - "[" (IPv6address / "v" 1*HEXDIG "." 1*(unreserved / sub-delims / ":")) "]" + * + * @param array $ordinals + * An array of integers representing each character of the string. + * @param array $characters + * An array of characters representing the string. + * @param int $start + * (optional) The position in the arrays to start checking. + * @param int|null $stop + * (optional) The position in the arrays to stop checking. + * If NULL, then the entire string is processed or the closing character is found. + * + * @return array + * The processed information: + * - 'address': A string containing the processed ip address. + * When is_future is TRUE, this is an array containing: + * - 'version': The ipfuture version. + * - 'ip': The ip address. + * - 'is_future': A boolean that when TRUE represents an ipvfuture address and when FALSE represents an ipv6 address. + * - 'current': an integer representing the position the counter stopped at. + * - 'invalid': a boolean representing whether or not this string is valid or if an error occurred. + * + * @see: base_rfc_char::pr_rfc_char_tchar() + * @see: https://tools.ietf.org/html/rfc2616#section-3.7 + */ + protected function pr_rfc_string_is_ip_literal($ordinals, $characters, $start = 0, $stop = NULL) { + $result = array( + 'address' => NULL, + 'is_future' => FALSE, + 'current' => $start, + 'invalid' => FALSE, + ); + + if (is_null($stop)) { + $stop = count($ordinals); + } + + if ($start >= $stop) { + return $result; + } + + // this first character must be either a "v" or a hex digit. + if ($ordinals[$result['current']] == c_base_ascii::LOWER_V || $ordinals[$result['current']] == c_base_ascii::UPPER_V) { + $result['is_future'] = TRUE; + } + elseif (!self::pr_rfc_char_is_hexdigit($ordinals[$result['current']])) { + $result['invalid'] = TRUE; + return $result; + } + + if ($result['is_future']) { + $result['address'] = array( + 'version' => NULL, + 'ip' => NULL, + ); + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + return $result; + } + + // store all hexdigits until a non-hexdigit is found as the version number. + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (!self::pr_rfc_char_is_hexdigit($code)) { + break; + } + + $result['address']['version'] .= $characters[$result['current']]; + } + unset($code); + + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + return $result; + } + + if ($ordinals[$result['current']] != c_base_ascii::PERIOD) { + $result['invalid'] = TRUE; + return $result; + } + + $result['current']++; + if ($result['current'] >= $stop) { + $result['invalid'] = TRUE; + return $result; + } + + // record all valid values as the ip address until the stop point or ']' is reached. + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (self::pr_rfc_char_is_digit($code)) { + // do nothing, valid + } + elseif (self::pr_rfc_char_is_alpha($code)) { + // do nothing, valid + } + elseif (self::pr_rfc_char_is_unreserved($code)) { + // do nothing, valid + } + elseif (self::pr_rfc_char_is_sub_delims($code)) { + // do nothing, valid + } + elseif ($code == c_base_ascii::COLON) { + // do nothing, valid + } + elseif ($code == c_base_ascii::BRACKET_CLOSE) { + break; + } + else { + $result['invalid'] = TRUE; + break; + } + + $result['address']['ip'] .= $characters[$result['current']]; + } + unset($code); + } + else { + for (; $result['current'] < $stop; $result['current']++) { + $code = $ordinals[$result['current']]; + + if (self::pr_rfc_char_is_hexdigit($code)) { + $result['address'] .= $characters[$result['current']]; + } + elseif ($code == c_base_ascii::COLON) { + $result['address'] .= $characters[$result['current']]; + } + elseif ($code == c_base_ascii::BRACKET_CLOSE) { + break; + } + else { + $result['invalid'] = TRUE; + break; + } + } + unset($code); + + if (!$result['invalid'] && inet_pton($result['address']) === FALSE) { + $result['invalid'] = TRUE; + } + } + + return $result; + } + + /** + * Effectively unshift a value onto a given array with a specified index. + * + * The NULL key should be the first key in the weight. + * PHP does not provide a way to preserve keys when merging arrays nor does PHP provide a way to unshift a value onto an array with a specific key. + * + * @param $key + * The index name to unshift the value onto the array as. + * @param array $array + * The array to unshift onto. + */ + protected function p_prepend_array_value($key, &$array) { + if (!array_key_exists($key, $array)) { + return; + } + + $value = $array[$key]; + unset($array[$key]); + + $new_array = array( + $key => $value, + ); + unset($value); + + foreach ($array as $key => $value) { + $new_array[$key] = $value; + } + + $array = $new_array; + unset($new_array); + } +} diff --git a/common/base/classes/base_session.php b/common/base/classes/base_session.php new file mode 100644 index 0000000..b261a09 --- /dev/null +++ b/common/base/classes/base_session.php @@ -0,0 +1,957 @@ +socket = NULL; + $this->socket_path = NULL; + $this->socket_timeout = NULL; + + $this->system_name = NULL; + + $this->name = NULL; + $this->id_user = NULL; + $this->ip = NULL; + $this->password = NULL; + $this->session_id = NULL; + $this->settings = NULL; + + $this->error = NULL; + + $this->timeout_expire = NULL; + $this->timeout_max = NULL; + } + + /** + * Class destructor. + */ + public function __destruct() { + $this->clear_password(); + + if (is_resource($this->socket)) { + @socket_close($this->socket); + } + + unset($this->socket); + unset($this->socket_path); + unset($this->socket_timeout); + + unset($this->system_name); + + unset($this->name); + unset($this->id_user); + unset($this->ip); + unset($this->password); + unset($this->session_id); + unset($this->special); + unset($this->settings); + + unset($this->error); + + unset($this->timeout_expire); + unset($this->timeout_max); + } + + /** + * Assigns the system name, which is used to create the socket path. + * + * @param string $system_name + * A system name string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_system_name($system_name) { + if (!is_string($system_name) || empty($system_name)) { + return c_base_return_error::s_false(); + } + + $this->system_name = basename($system_name); + $this->socket_path = self::SOCKET_PATH_PREFIX . $this->system_name . self::SOCKET_PATH_SUFFIX; + + return new c_base_return_true(); + } + + /** + * Returns the stored system name. + * + * @return c_base_return_string + * The system name string or NULL if undefined. + */ + public function get_system_name() { + return c_base_return_string::s_new($this->system_name); + } + + /** + * Assigns the user name associated with the session. + * + * @param string $name + * The user name. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_name($name) { + if (!is_string($name) || empty($name)) { + return c_base_return_error::s_false(); + } + + if (mb_strlen($name) == 0 || preg_match('/^(\w|-)+$/i', $name) != 1) { + return c_base_return_error::s_false(); + } + + $this->name = $name; + return new c_base_return_true(); + } + + /** + * Returns the stored user name. + * + * @return c_base_return_string + * The user name string. + */ + public function get_name() { + return c_base_return_string::s_new($this->name); + } + + /** + * Assigns the user id associated with the session. + * + * @param int $id_user + * The user id. + * This must be greater than or equal to 0. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_id_user($id_user) { + if ((is_int($id_user) && $id_user < 0) || !is_int($id_user) && (!is_string($id_user) || !(is_numeric($id_user) && (int) $id_user >= 0))) { + return c_base_return_error::s_false(); + } + + $this->id_user = (int) $id_user; + return new c_base_return_true(); + } + + /** + * Returns the stored user id. + * + * @return c_base_return_int + * The user id_user integer. + */ + public function get_id_user() { + return c_base_return_int::s_new($this->id_user); + } + + /** + * Assigns the ip address associated with the session. + * + * @param string $ip + * The ip address. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_ip($ip) { + if (!is_string($ip) || empty($ip)) { + return c_base_return_error::s_false(); + } + + if (mb_strlen($ip) == 0 || ip2long($ip) === FALSE) { + return c_base_return_error::s_false(); + } + + $this->ip = $ip; + return new c_base_return_true(); + } + + /** + * Returns the stored ip address. + * + * @return c_base_return_string + * The ip address string. + */ + public function get_ip() { + return c_base_return_string::s_new($this->ip); + } + + /** + * Assigns the password associated with a user name. + * + * Manually assign this only for new sessions only. + * When existing sessions are loaded, this will be auto-populated. + * + * @param string|null $password + * The password. + * Assigning null disable the password. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: c_base_session::load() + */ + public function set_password($password) { + if (!is_null($password) && (!is_string($password) || empty($password))) { + return c_base_return_error::s_false(); + } + + if (is_null($password)) { + $this->password = NULL; + return new c_base_return_true(); + } + + // deny 0-length passwords. + if (mb_strlen($password) == 0) { + return c_base_return_error::s_false(); + } + + $this->password = $password; + return new c_base_return_true(); + } + + /** + * Returns the stored password. + * + * @return c_base_return_string + * The password string. + */ + public function get_password() { + return c_base_return_string::s_new($this->password); + } + + /** + * Assigns the settings associated with the session. + * + * The settings provides optional information that a service may want to store with a particular session. + * + * @param array $settings + * The settings array to assign. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_settings($settings) { + if (!is_array($settings)) { + return c_base_return_error::s_false(); + } + + $this->settings = $settings; + return new c_base_return_true(); + } + + /** + * Returns the stored settings. + * + * @return c_base_return_array + * The settings array. + */ + public function get_settings() { + return c_base_return_array::s_new($this->settings); + } + + /** + * Uses an unproven technique in an attempt to 'delete' a password from memory and then unallocating the resource. + * + * The password will be set to a hopefully large enough string of whitespaces. + * The password variable will then be unset. + * + * This does not perform the garbage collection, but it is suggested that the caller consider calling gc_collect_cycles(). + * + * @see: gc_collect_cycles() + */ + public function clear_password() { + $this->password = str_repeat(' ', self::PASSWORD_CLEAR_TEXT_LENGTH); + unset($this->password); + } + + /** + * Assigns the session id associated with a session. + * + * Manually assign this for existing sessions only. + * This should be auto-populated when a new session is saved. + * + * @param string $session_id + * The session id string. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: c_base_session::save() + */ + public function set_session_id($session_id) { + if (!is_string($session_id) || empty($session_id)) { + return c_base_return_error::s_false(); + } + + // deny 0-length session_id. + if (mb_strlen($session_id) == 0) { + return c_base_return_error::s_false(); + } + + $this->session_id = $session_id; + return new c_base_return_true(); + } + + /** + * Returns the stored session id. + * + * @return c_base_return_string + * The session id string. + */ + public function get_session_id() { + return c_base_return_string::s_new($this->session_id); + } + + /** + * Assigns the session expiration timeout. + * + * @param int $timeout_expire + * The unix timestamp for the expiration timeout. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: c_base_session::save() + */ + public function set_timeout_expire($timeout_expire) { + if (!is_int($timeout_expire)) { + return c_base_return_error::s_false(); + } + + $this->timeout_expire = $timeout_expire; + return new c_base_return_true(); + } + + /** + * Returns the unix timestamp for the session expiration timeout. + * + * @return c_base_return_int + * The unix timestamp for the session expiration timeout. + */ + public function get_timeout_expire() { + return c_base_return_int::s_new($this->timeout_expire); + } + + /** + * Assigns the max session timeout. + * + * @param int $timeout_max + * The unix timestamp for the max session timeout. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: c_base_session::save() + */ + public function set_timeout_max($timeout_max) { + if (!is_int($timeout_max)) { + return c_base_return_error::s_false(); + } + + $this->timeout_max = $timeout_max; + return new c_base_return_true(); + } + + /** + * Returns the unix timestamp for the max timeout. + * + * @return c_base_return_int + * The unix timestamp for the max timeout. + */ + public function get_timeout_max() { + return c_base_return_int::s_new($this->timeout_max); + } + + /** + * Assigns the max session timeout. + * + * @param int $seconds + * Number of seconds until timeout is reached. + * @param int $microseconds + * (optional) Number of microseconds until timeout is reached. + * @param bool $receive + * (optional) When TRUE, the receive timeout is assigned. + * When FALSE, the send timeout is assigned. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: socket_set_option() + */ + public function set_socket_timeout($seconds, $microseconds = 0, $receive = TRUE) { + if (!is_int($seconds) || $seconds < 0) { + return c_base_return_error::s_false(); + } + + if (!is_int($microseconds) || $microseconds < 0) { + return c_base_return_error::s_false(); + } + + if (!is_bool($receive)) { + return c_base_return_error::s_false(); + } + + if (!is_array($this->socket_timeout)) { + $this->socket_timeout = array( + 'send' => NULL, + 'receive' => NULL, + ); + } + + if ($receive) { + $this->socket_timeout['receive'] = array('seconds' => $seconds, 'microseconds' => $microseconds); + if (is_resource($this->socket)) { + socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $seconds, $microseconds); + } + } + else { + $this->socket_timeout['send'] = array('seconds' => $seconds, 'microseconds' => $microseconds); + if (is_resource($this->socket)) { + socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $seconds, $microseconds); + } + } + + return new c_base_return_true(); + } + + /** + * Returns the unix timestamp for the max timeout. + * + * @return c_base_return_int + * The unix timestamp for the max timeout. + * + * @see: socket_get_option() + */ + public function get_socket_timeout() { + return c_base_return_array::s_new($this->socket_timeout); + } + + /** + * Returns the stored error array. + * + * This should be called after a load() or a save() command to check to see if the socket returned any error. + * + * This does not return the socket error, for that use self::get_error_socket() + * + * @return c_base_return_array|c_base_return_status + * The error array or boolean returned by the socket when transferring data or NULL if there are no socket errors. + * A value of FALSE means that no error was returned by the socket. + * A value of an array() for both load() and save() would contain the socket error message. + * + * @see: self::get_error_socket() + */ + public function get_error() { + if (is_bool($this->error)) { + c_base_return_bool::s_new($this->error); + } + + return c_base_return_array::s_new($this->error); + } + + /** + * This returns the error code reported by the socket itself. + * + * Use self::get_error() to get the error reported in the packet and not the socket. + * + * @return c_base_return_int + * Number representing the socket error. + * + * @see: self::get_error() + * @see: socket_last_error() + */ + public function get_error_socket() { + if (!is_resource($this->socket)) { + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new(@socket_last_error($this->socket)); + } + + /** + * This clears the error on the socket if any exist. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * + * @see: self::get_error_socket() + * @see: socket_clear_error() + */ + public function clear_error_socket() { + if (!is_resource($this->socket)) { + return c_base_return_error::s_false(); + } + + @socket_clear_error($this->socket); + + return new c_base_return_true(); + } + + /** + * Opens a socket connection for later loading. + * + * The system name must be defined before this call to ensure a valid socket path exists. + * The socket should be closed with c_base_session::do_disconnect() when finished. + * + * @return c_base_return_status + * TRUE on success, FALSE on failure. + * + * @see: c_base_session::set_system_name() + * @see: c_base_session::do_disconnect() + */ + function do_connect() { + if (is_resource($this->socket) || is_null($this->system_name)) { + return c_base_return_error::s_false(); + } + + $this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0); + if ($this->socket === FALSE) { + $this->do_disconnect(); + return c_base_return_error::s_false(); + } + + $connected = socket_connect($this->socket, $this->socket_path, 0); + if ($connected === FALSE) { + unset($connected); + + $this->do_disconnect(); + return c_base_return_error::s_false(); + } + unset($connected); + + + // assign any pre-defined timeouts. + if (isset($this->socket_timeout['receive']['seconds'])) { + socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $this->socket_timeout['receive']['seconds'], $this->socket_timeout['receive']['microseconds']); + } + + if (isset($this->socket_timeout['send']['seconds'])) { + socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $this->socket_timeout['send']['seconds'], $this->socket_timeout['send']['microseconds']); + } + + return new c_base_return_true(); + } + + /** + * Close an opened socket. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function do_disconnect() { + if (!is_resource($this->socket)) { + return c_base_return_error::s_false(); + } + + @socket_close($this->socket); + + $this->socket = NULL; + return new c_base_return_true(); + } + + /** + * Returns the connected status. + * + * This represents whether or not the self::do_connect() function was successfully called. + * The state of the connection should still be checked. + * + * @return c_base_return_status + * TRUE when connected, FALSE otherwise. + */ + public function is_connected() { + if (is_resource($this->socket)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Loads the session information from an open socket. + * + * @return c_base_return_status + * TRUE on success, FALSE on failure. + * + * @see: c_base_session::do_connect() + * @see: c_base_session::p_transfer() + */ + public function do_pull() { + if (is_null($this->ip) || is_null($this->session_id)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->socket) || @socket_last_error($this->socket) != 0) { + return c_base_return_error::s_false(); + } + + $response = $this->p_transfer(array('ip' => $this->ip, 'session_id' => $this->session_id)); + if (c_base_return::s_has_error($response)) { + return $response->get_error(); + } + + $response = c_base_return_array::s_value_exact($response); + if (empty($response['result']) || !is_array($response['result'])) { + unset($response); + + return c_base_return_error::s_false(); + } + + $this->name = NULL; + if (isset($response['result']['name']) && is_string($response['result']['name'])) { + $this->name = $response['result']['name']; + } + + $this->id_user = NULL; + if (isset($response['result']['id_user']) && is_int($response['result']['id_user'])) { + $this->id_user = $response['result']['id_user']; + } + + if (!is_null($this->password)) { + $this->password = str_repeat(' ', self::PASSWORD_CLEAR_TEXT_LENGTH); + } + + $this->password = NULL; + if (isset($response['result']['password']) && is_string($response['result']['password'])) { + $this->password = $response['result']['password']; + } + + $this->timeout_expire = NULL; + if (isset($response['result']['expire']) && is_int($response['result']['expire'])) { + $this->timeout_expire = $response['result']['expire']; + } + + $this->timeout_max = NULL; + if (isset($response['result']['max']) && is_int($response['result']['max'])) { + $this->timeout_max = $response['result']['max']; + } + + $this->settings = NULL; + if (isset($response['result']['settings']) && is_array($response['result']['settings'])) { + $this->settings = $response['result']['settings']; + } + + unset($response); + return new c_base_return_true(); + } + + /** + * Saves the session information to an open socket. + * + * This function accepts interval expire and max, but these should be considered soft limits. + * The server is allowed to impose its own hard limits that prevent expire and max from being set longer than. + * + * @param int|null $interval_expire + * (optional) Number of seconds a session should wait while idling before expiring the session. + * @param int|null $interval_max + * (optional) The maximum time a session is allowed to exist. + * + * @return c_base_return_status + * TRUE on success, FALSE on failure. + * + * @see: c_base_session::set_name() + * @see: c_base_session::set_ip() + * @see: c_base_session::set_password() + * @see: c_base_session::do_connect() + * @see: c_base_session::p_transfer() + */ + public function do_push($interval_expire = NULL, $interval_max = NULL) { + if (is_null($this->name) || is_null($this->id_user) || is_null($this->ip) || is_null($this->password)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->socket) || @socket_last_error($this->socket) != 0) { + return c_base_return_error::s_false(); + } + + if (!is_null($interval_expire) && (!is_int($interval_expire) || $interval_expire < 1)) { + return c_base_return_error::s_false(); + } + + if (!is_null($interval_max) && (!is_int($interval_max) || $interval_max < 1)) { + return c_base_return_error::s_false(); + } + + // settings is allowed to be undefined, so send it as an empty array. + if (is_null($this->settings)) { + $this->settings = array(); + } + + $response = $this->p_transfer(array('name' => $this->name, 'id_user' => $this->id_user, 'ip' => $this->ip, 'password' => $this->password, 'expire' => $interval_expire, 'max' => $interval_max, 'settings' => $this->settings)); + if (c_base_return::s_has_error($response)) { + return c_base_return_error::s_false(); + } + + $response = c_base_return_array::s_value_exact($response); + if (empty($response['result']) || !is_array($response['result'])) { + unset($response); + + return c_base_return_error::s_false(); + } + + $this->session_id = NULL; + if (isset($response['result']['session_id'])) { + $this->session_id = $response['result']['session_id']; + } + + $this->timeout_expire = NULL; + if (isset($response['result']['expire'])) { + $this->timeout_expire = $response['result']['expire']; + } + + $this->timeout_max = NULL; + if (isset($response['result']['max'])) { + $this->timeout_max = $response['result']['max']; + } + + unset($response); + return new c_base_return_true(); + } + + /** + * Terminates a session from an open socket. + * + * Unlike self::do_disconnect(), this does not close the connection to the socket, it closes the session itself. + * + * This is used to terminate a session before the expiration date and time is reached. + * Use this on logout operations. + * + * @return c_base_return_status + * TRUE on success, FALSE on failure. + * + * @see: self::do_connect() + * @see: self::p_transfer() + */ + public function do_terminate() { + if (is_null($this->ip) || is_null($this->session_id)) { + return c_base_return_error::s_false(); + } + + if (!is_resource($this->socket) || @socket_last_error($this->socket) != 0) { + return c_base_return_error::s_false(); + } + + $response = $this->p_transfer(array('ip' => $this->ip, 'session_id' => $this->session_id, 'close' => TRUE)); + if (c_base_return::s_has_error($response)) { + return $response->get_error(); + } + + $response = c_base_return_array::s_value_exact($response); + if (empty($response['result']) || !is_array($response['result'])) { + unset($response); + + return c_base_return_error::s_false(); + } + + if (!is_null($this->password)) { + $this->password = str_repeat(' ', self::PASSWORD_CLEAR_TEXT_LENGTH); + } + + $this->name = NULL; + $this->password = NULL; + $this->timeout_expire = NULL; + $this->timeout_max = NULL; + + unset($response); + return new c_base_return_true(); + } + + /** + * Closes (terminates) a session from an open socket. + * + * Unlike self::do_disconnect(), this does not close the connection to the socket, it closes the session itself. + * + * This is used to terminate a session before the expiration date and time is reached. + * Use this on logout operations. + * + * @return c_base_return_status + * TRUE on success, FALSE on failure. + * + * @see: self::do_connect() + * @see: self::p_transfer() + */ + public function do_flush() { + if (!is_resource($this->socket) || @socket_last_error($this->socket) != 0) { + return c_base_return_error::s_false(); + } + + $response = $this->p_transfer(array('flush' => TRUE)); + if (c_base_return::s_has_error($response)) { + return $response->get_error(); + } + + $response = c_base_return_array::s_value_exact($response); + if (empty($response['result']) || !is_array($response['result'])) { + unset($response); + + return c_base_return_error::s_false(); + } + + unset($response); + return new c_base_return_true(); + } + + /** + * Transfer a request packet through the socket. + * + * @param array $request + * A request array defined as required by the socket. + * + * @return c_base_return_status|c_base_return_array + * An array is returned on success. + * FALSE is returned otherwise. + * + * @see: c_base_session::do_connect() + */ + private function p_transfer($request) { + unset($this->error); + $this->error = NULL; + + $json = json_encode($request); + + $written = socket_write($this->socket, $json); + unset($json); + + if ($written === FALSE || $written == 0) { + unset($written); + + return c_base_return_error::s_false(); + } + unset($written); + + $json = socket_read($this->socket, self::PACKET_MAX_LENGTH); + if (!is_string($json) || mb_strlen($json) == 0) { + unset($json); + + return c_base_return_error::s_false(); + } + + $response = json_decode($json, TRUE); + unset($json); + + if (isset($response['error'])) { + $this->error = $response['error']; + } + + if ($response === FALSE) { + return c_base_return_error::s_false(); + } + + return c_base_return_array::s_new($response); + } +} + +/** + * A return class whose value is represented as a c_base_session. + */ +class c_base_session_return extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, FALSE); + } + + /** + * Assign the value. + * + * @param c_base_session $value + * Any value so long as it is a c_base_session object. + * NULL is not allowed. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!($value instanceof c_base_session)) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return c_base_session $value + * The value array stored within this class. + */ + public function get_value() { + if (!($this->value instanceof c_base_session)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return c_base_session $value + * The value c_base_session stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof c_base_session)) { + $this->value = new c_base_session(); + } + + return $this->value; + } +} diff --git a/common/base/classes/base_utf8.php b/common/base/classes/base_utf8.php new file mode 100644 index 0000000..fff5ca0 --- /dev/null +++ b/common/base/classes/base_utf8.php @@ -0,0 +1,997 @@ + $ordinal) { + $array[$key] = self::p_s_ordinal_to_character($ordinal); + } + unset($key); + unset($ordinal); + + return c_base_return_string::s_new(implode($array)); + } + + /** + * Converts ordinal to a unicode codepoint. + * + * @param int $ordinal + * The ordinal to be converted to codepoint. + * + * @return c_base_return_int|c_base_return_status + * The codepoint. + * FALSE with error bit set is returned on error. + */ + public static function s_ordinal_to_codepoint($ordinal) { + if (!is_int($ordinal)) { + return c_base_return_error::s_false(); + } + + $codepoint = self::p_s_ordinal_to_codepoint($ordinal); + if ($codepoint === FALSE) { + unset($codepoint); + return c_base_return_error::s_false(); + } + + return c_base_return_int::s_new($codepoint); + } + + /** + * Count the number of sub string occurances. + * + * @param string $haystack + * The string to search in. + * @param string $needle + * The string to search for. + * @param int $offset + * The offset where to start counting. + * @param null|int $length + * The maximum length after the specified offset to search for the substring. + * + * @return c_base_return_int|c_base_return_status + * Number of occurances of $needle. + * FALSE with error bit set is returned on error. + */ + public static function s_count_substrings($haystack, $needle, $offset = 0, $length = NULL) { + if (!is_string($haystack)) { + return c_base_return_error::s_false(); + } + + if (!is_string($needle)) { + return c_base_return_error::s_false(); + } + + if (!is_int($offset)) { + return c_base_return_error::s_false(); + } + + if (!is_null($length) && !is_int($length)) { + return c_base_return_error::s_false(); + } + + if ($offset || $length) { + $haystack = s_substring($haystack, $offset, $length); + } + + if (is_null($length)) { + return c_base_return_int::s_new(substr_count($haystack, $needle, $offset)); + } + + return c_base_return_int::s_new(substr_count($haystack, $needle, $offset, $length)); + } + + /** + * Checks if a string is 7 bit ASCII. + * + * @param string + * $text The string to check. + * + * @return c_base_return_status + * TRUE if ASCII, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public static function s_is_ascii($text) { + if (!is_string($text)) { + return c_base_return_error::s_false(); + } + + if (preg_match('/[\x80-\xff]/', $text)) { + return new c_base_return_false(); + } + + return new c_base_return_true(); + } + + /** + * Strip HTML and PHP tags from a string. + * + * @param string $text + * UTF-8 string. + * @param string $allowable_tags + * The tags to allow in the string. + * + * @return c_base_return_string|c_base_return_status + * The stripped string. + * FALSE with error bit set is returned on error. + */ + public static function s_strip_tags($text, $allowable_tags = '') { + if (!is_string($text)) { + return c_base_return_error::s_false(); + } + + if (!is_string($allowable_tags)) { + return c_base_return_error::s_false(); + } + + // clean broken UTF_8. + $sanitized = self::p_s_clean($text); + if ($sanitized === FALSE) { + unset($sanitized); + return c_base_return_error::s_false(); + } + + return c_base_return_string::s_new(strip_tags($sanitized, $allowable_tags)); + } + + /** + * Private version of s_character_to_ordinal(). + * + * @see: self::s_character_to_ordinal() + */ + private static function p_s_character_to_ordinal($character) { + $split = self::p_s_split($character); + if ($split === FALSE) { + unset($split); + return FALSE; + } + + $first = $split[0]; + unset($split); + + switch(strlen($first)) { + case 1: + return ord($first); + + case 2: + return ((ord($first[0]) << 8) | ord($first[1])); + + case 3: + return ((ord($first[0]) << 16) | (ord($first[1]) << 8) | ord($first[2])); + + case 4: + return ((ord($first[0]) << 24) | (ord($first[1]) << 16) | (ord($first[2]) << 8) | ord($first[3])); + } + + unset($first); + return FALSE; + } + + /** + * Private version of s_ordinal_to_codepoint(). + * + * @see: self::s_ordinal_to_codepoint() + */ + private static function p_s_ordinal_to_codepoint($ordinal) { + if ($ordinal == 0) { + return 0; + } + + if (($ordinal & self::MASK_1) == 0) { + return $ordinal; + } + + if (($ordinal & self::MASK_2) == 0) { + $high = ($ordinal >> 8); + $low = $ordinal - ($high << 8); + + return ((($high & 0x1f) << 6) | ($low & 0x3f)); + } + + if (($ordinal & self::MASK_3) == 0) { + $high = ($ordinal >> 16); + $medium = ($ordinal - ($high << 16) >> 8); + $low = ($ordinal - ($high << 16) - ($medium << 8)); + + return ((($high & 0x0f) << 12) | (($medium & 0x3f) << 6) | ($low & 0x3f)); + } + + + $high = ($ordinal >> 24); + $medium_1 = ($ordinal - ($high << 24) >> 16); + $medium_2 = ($ordinal - ($high << 24) - ($medium_1 << 16) >> 8); + $low = ($ordinal - ($high << 24) - ($medium_1 << 16) - ($medium_2 << 8)); + + return ((($high & 0x07) << 18) | (($medium_1 & 0x3f) << 12) | (($medium_2 & 0x3f) << 6) | ($low & 0x3f)); + } + + /** + * Private version of s_ordinal_to_character(). + * + * @see: self::s_ordinal_to_character() + */ + private static function p_s_ordinal_to_character($ordinal) { + // mb_convert_encoding() accepts codepoints, so first convert the ordinal to a codepoint. + $codepoint = self::p_s_ordinal_to_codepoint($ordinal); + + return mb_convert_encoding('&#' . $codepoint . ';', self::UTF_8, 'HTML-ENTITIES'); + } + + /** + * Private version of: s_string_to_ordinals() + * + * @see: self::s_string_to_ordinals() + */ + private static function p_s_string_to_ordinals($text) { + if (is_string($text)) { + $text = self::p_s_split($text); + } + + $ordinals = array(); + foreach ($text as $character) { + $value = self::p_s_character_to_ordinal($character); + if ($value === FALSE) { + continue; + } + + $ordinals[] = $value; + } + unset($value); + + return $ordinals; + } + + /** + * Private version of s_length_string(). + * + * @see: self::s_length_string() + */ + private static function p_s_length_string($text) { + return mb_strlen($text, self::UTF_8); + } + + /** + * Private version of s_clean(). + * + * @see: self::s_clean() + */ + private static function p_s_clean($text, $remove_bom = FALSE) { + $sanitized = preg_replace('/([\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})|./s', '$1', $text); + + if (is_null($sanitized)) { + unset($sanitized); + return FALSE; + } + + if ($remove_bom) { + $sanitized = str_replace(self::BOM, '', $sanitized); + } + + return $sanitized; + } + + /** + * Private version of s_split(). + * + * @see: self::s_split() + */ + private static function p_s_split($text, $split_length = 1) { + $split = array(); + $text = self::p_s_clean($text); + + preg_match_all('/\X/u', $text, $split); + $split = $split[0]; + + if ($split_length > 1) { + $split = array_chunk($split, $split_length); + if (is_null($split)) { + return FALSE; + } + + $array = array(); + foreach ($split as $key => $value) { + $array[$key] = implode($value); + } + unset($key); + unset($value); + + $split = $array; + unset($array); + } + + if ($split[0] === '') { + return array(); + } + + return $split; + } + + /** + * Private version of s_character_size_list(). + * + * @see: self::s_character_size_list() + */ + private static function p_s_character_size_list($text) { + $split = self::p_s_split($text); + if ($split === FALSE) { + unset($split); + return FALSE; + } + + $array = array(); + foreach ($split as $key => $value) { + $array[$key] = strlen($value); + } + unset($key); + unset($value); + unset($split); + + return $array; + } + + /** + * Private version of s_substring(). + * + * @see: self::s_substring() + */ + private static function p_s_substring($text, $start = 0, $length = NULL) { + if (is_null($length)) { + $length = PHP_INT_MAX; + } + + $sanitized = self::p_s_clean($text); + + return mb_substr($sanitized, $start, $length, self::UTF_8); + } + + /** + * Private version of s_encode_html_character(). + * + * @see: self::s_encode_html_character() + */ + private static function p_s_encode_html_character($character) { + $ordinal = p_s_character_to_ordinal($character); + if ($ordinal === FALSE) { + unset($ordinal); + return FALSE; + } + + $codepoint = p_s_ordinal_to_codepoint($ordinal); + unset($ordinal); + if ($codepoint === FALSE) { + unset($codepoint); + return FALSE; + } + + return '&#' . $codepoint . ';'; + } + + /** + * Private version of s_is_bom(). + * + * @see: self::s_is_bom() + */ + private static function p_s_is_bom($text) { + if ($text === self::BOM) { + return TRUE; + } + + return FALSE; + } +} diff --git a/common/theme/classes/theme_ajax.php b/common/theme/classes/theme_ajax.php new file mode 100644 index 0000000..e69de29 diff --git a/common/theme/classes/theme_dom.php b/common/theme/classes/theme_dom.php new file mode 100644 index 0000000..14b2a4f --- /dev/null +++ b/common/theme/classes/theme_dom.php @@ -0,0 +1,790 @@ +preserveWhiteSpace = TRUE; + $this->formatOutput = FALSE; + @$this->loadHTML(''); + } + + /** + * Changes the element from one type to another. + * + * @param DOMNode $element + * The element whose type will be changed. + * @param string $type + * The new element type to use. + * + * @return DOMNode|bool + * The changed element on success. + * FALSE with error bit set is returned on error. + * + * @see: self::pr_change_element() + */ + public function change_element($element, $type) { + if (!($element instanceof DOMNode)) { + return c_base_return_error::s_false(); + } + + if (!is_string($type) || empty($type)) { + return c_base_return_error::s_false(); + } + + return c_them_return_dom_node::s_new($this->pr_change_element($element, $type)); + } + + + /** + * Change all elements of a given element type to another type. + * + * @param string $type_old + * The old element type to be replaced. + * @param string $type_new + * The new element type to use. + * + * @return bool + * TRUE on success. + * Otherwise FALSE with error bit set is returned. + * + * @see: self::pr_change_elements() + */ + public function change_elements($type_old, $type_new) { + if (!is_string($type_old) || strlen($type_old) == 0) { + return c_base_return_error::s_false(); + } + + if (!is_string($type_new) || strlen($type_empty) == 0) { + return c_base_return_error::s_false(); + } + + if (!($this->content instanceof DOMNode)) { + return c_base_return_error::s_false(); + } + + return $this->pr_change_elements($type_old, $type_new, $this->content); + } + + /** + * Removes the given element from its parent. + * + * This preserves child elements. + * To remove entirely, use removeChild() directly. + * + * @param DOMNode $element + * The object to convert to markup text. + * @param bool $preserve_children + * (optional) If TRUE, children are re-attached to the parent node to preserve their location in the markup. + * If FALSE, the children remain attached to the removed element. + * + * @return bool + * TRUE on success. + * FALSE without error bit set is returned when unable to remove element. + * FALSE with error bit set is returned on error. + * + * @see: self::pr_remove_element() + * @see: DOMDocument::removeChild() + */ + public function remove_element($element, $preserve_children = TRUE) { + if (!($element instanceof DOMNode)) { + return c_base_return_error::s_false(); + } + + if (!is_bool($preserve_children)) { + return c_base_return_error::s_false(); + } + + if ($this->pr_remove_element($element, $preserve_children)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Remove all elements of a given element type. + * + * This preserves child elements. + * To remove elements entirely, use removeChild() directly. + * + * @param string $type + * The new element type to use. + * @param bool $preserve_children + * (optional) If TRUE, children are re-attached to the parent node to preserve their location in the markup. + * If FALSE, the children remain attached to the removed element. + * + * @return bool + * TRUE on success. + * FALSE without error bit set is returned when unable to remove element. + * FALSE with error bit set is returned on error. + * + * @see: self::pr_remove_elements() + * @see: DOMDocument::removeChild() + */ + public function remove_elements($type, $preserve_children = TRUE) { + if (!is_string($type) || strlen($type) == 0) { + return c_base_return_error::s_false(); + } + + if (!is_bool($preserve_children)) { + return c_base_return_error::s_false(); + } + + if (!($this->content instanceof DOMNode)) { + return c_base_return_error::s_false(); + } + + if ($this->pr_remove_elements($type, $this->content, $preserve_children)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Converts the element into markup + * + * @param bool $include_tag + * When TRUE, the head tag itself will be included in the output. + * When FALSE, only the contents of the tag will be included in the output. + * @param DOMNode $parent + * The object to operate on. + * + * @return string|bool + * The markup text that the object was converted from. + * FALSE with error bit set is returned on error. + */ + protected function pr_get_markup($include_tag, $parent) { + if ($include_tag) { + return $this->saveHTML($parent); + } + + $markup = ''; + if ($parent->hasChildNodes() > 0) { + foreach ($parent->childNodes as $child) { + $markup .= $this->saveHTML($child); + } + } + unset($child); + + return $markup; + } + + /** + * Changes the element from one type to another. + * + * @param DOMNode $element + * The element whose type will be changed. + * @param string $type + * The new element type to use. + * + * @return bool + * The changed element on success. + * FALSE with error bit set is returned on error. + * + * @see: self::change_element() + */ + protected function pr_change_element($element, $type) { + $parent = $element->parentNode; + $new = $this->createElement($type); + + if (!($new instanceof DOMNode)) { + return FALSE; + } + + if ($element->hasAttributes()) { + foreach ($element->attributes as $attribute) { + $new->setAttribute($attribute->name, $attribute->value); + } + } + + if ($element->hasChildNodes()) { + foreach ($element->childNodes as $child) { + $new->appendChild($child->cloneNode(TRUE)); + } + unset($child); + } + + if ($parent instanceOf DOMNode) { + $child = $parent->replaceChild($new, $element); + } + else { + $this->appendChild($element); + $child = $this->replaceChild($new, $element); + + if ($child instanceof DOMNode) { + $parent = $child->parentNode; + + if ($parent instanceOf DOMNode) { + $this->removeChild($child); + } + } + else { + $this->removeChild($element); + } + } + + unset($new); + unset($parent); + + if ($child instanceOf DOMNode) { + return $child; + } + + return FALSE; + } + + /** + * Change all elements of a given element type to another type. + * + * @param string $type + * The new element type to operate on. + * @param DOMNode $parent + * The object to operate on. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + * + * @see: self::change_elements() + */ + protected function pr_change_elements($type, $parent) { + $result = TRUE; + + $elements = $parent->getElementsByTagName($type); + foreach ($elements as $element) { + if ($element instanceof DOMNode) { + $result = $this->pr_change_element($element, $type); + } + else { + $result = FALSE; + } + + if (!$result) break; + } + unset($element); + unset($elements); + + return $result; + } + + /** + * Removes the given element from its parent. + * + * This preserves child elements. + * To remove entirely, use removeChild() directly. + * + * @param DOMNode $element + * The object to convert to markup text. + * @param bool $preserve_children + * (optional) If TRUE, children are re-attached to the parent node to preserve their location in the markup. + * If FALSE, the children remain attached to the removed element. + * + * @return bool + * The removed element on success. + * FALSE with error bit set is returned on error. + * + * @see: self::remove_element() + * @see: DOMDocument::removeChild() + */ + protected function pr_remove_element($element, $preserve_children = TRUE) { + $parent = $element->parentNode; + + if (!($parent instanceof DOMNode)) { + unset($parent); + return FALSE; + } + + if ($preserve_children && $element->hasChildNodes()) { + $children = array(); + + foreach ($element->childNodes as $child) { + $children[] = $child; + } + unset($child); + + foreach ($children as $child) { + $removed_child = $element->removeChild($child); + + if (is_object($removed_child)) { + $parent->insertBefore($removed_child, $element); + } + } + unset($child); + unset($removed_child); + unset($children); + } + + $child = $parent->removeChild($element); + unset($parent); + + if ($child instanceof DOMNode) { + return $child; + } + unset($child); + + return FALSE; + } + + /** + * Remove all elements of a given element type. + * + * This preserves child elements. + * To remove elements entirely, use removeChild() directly. + * + * @param string $type + * The new element type to operate on. + * @param DOMNode $parent + * The object to operate on. + * @param bool $preserve_children + * (optional) If TRUE, children are re-attached to the parent node to preserve their location in the markup. + * If FALSE, the children remain attached to the removed element. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + * + * @see: self::remove_elements() + * @see: DOMDocument::removeChild() + */ + protected function pr_remove_elements($type, $parent, $preserve_children = TRUE) { + $result = TRUE; + + $elements = $parent->getElementsByTagName($type); + foreach ($elements as $element) { + $result = $this->pr_remove_element($element, $preserve_children); + + if (!$result) break; + } + unset($elements); + + return $result; + } +} + +/** + * A return class whose value is represented as a c_theme_dom. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_c_theme_dom extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param DOMNode $value + * Any value so long as it is a c_theme_dom. + * NULL is not allowed. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_value($value) { + if (!$value instanceof c_theme_dom) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof c_theme_dom)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return c_theme_dom $value + * The value c_theme_dom stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof c_theme_dom)) { + $this->value = new c_theme_dom(); + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a DOMNode. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_dom_node extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param DOMNode $value + * Any value so long as it is a DOMNode. + * NULL is not allowed. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_value($value) { + if (!$value instanceof DOMNode) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof DOMNode)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return DOMNode $value + * The value DOMNode stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof DOMNode)) { + $this->value = new DOMNode(); + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a DOMComment. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_dom_comment extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param DOMComment $value + * Any value so long as it is a DOMComment. + * NULL is not allowed. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_value($value) { + if (!$value instanceof DOMComment) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof DOMComment)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return DOMComment $value + * The value DOMComment stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof DOMComment)) { + $this->value = new DOMComment(); + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a DOMElement. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_dom_element extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param DOMElement $value + * Any value so long as it is a DOMElement. + * NULL is not allowed. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_value($value) { + if (!$value instanceof DOMElement) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof DOMElement)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return DOMElement $value + * The value DOMElement stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof DOMElement)) { + $this->value = new DOMElement(); + } + + return $this->value; + } +} + +/** + * A return class whose value is represented as a DOMText. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_dom_text extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param DOMText $value + * Any value so long as it is a DOMText. + * NULL is not allowed. + * + * @return bool + * TRUE on success. + * FALSE with error bit set is returned on error. + */ + public function set_value($value) { + if (!$value instanceof DOMText) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof DOMText)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return DOMText $value + * The value DOMText stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof DOMText)) { + $this->value = new DOMText(); + } + + return $this->value; + } +} diff --git a/common/theme/classes/theme_form.php b/common/theme/classes/theme_form.php new file mode 100644 index 0000000..8b5da3e --- /dev/null +++ b/common/theme/classes/theme_form.php @@ -0,0 +1,478 @@ +attributes = array(); + } + + /** + * Class destructor. + */ + public function __destruct() { + parent::__destruct(); + + unset($this->attributes); + } + + /** + * Assign the specified tag. + * + * @param int $attribute + * The attribute to assign. + * @param $value + * The value of the attribute. + * The actual value type is specific to each attribute type. + * + * @return c_base_return_status + * TRUE on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function set_attribute($attribute, $value) { + if (!is_int($attribute)) { + return c_base_return_error::s_false(); + } + + switch ($attribute) { + case self::ATTRIBUTE_NONE: + unset($this->attribute[$attribute]); + return new c_base_return_true(); + + case self::ATTRIBUTE_ACTION: + case self::ATTRIBUTE_DIRECTION_NAME: + case self::ATTRIBUTE_FOR: + case self::ATTRIBUTE_FORM: + case self::ATTRIBUTE_FORM_ACTION: + case self::ATTRIBUTE_FORM_TARGET: + case self::ATTRIBUTE_KEY_TYPE: + case self::ATTRIBUTE_LABEL: + case self::ATTRIBUTE_LIST: + case self::ATTRIBUTE_NAME: + case self::ATTRIBUTE_ON_ABORT: + case self::ATTRIBUTE_ON_AFTER_PRINT: + case self::ATTRIBUTE_ON_ANIMATION_END: + case self::ATTRIBUTE_ON_ANIMATION_ITERATION: + case self::ATTRIBUTE_ON_ANIMATION_start: + case self::ATTRIBUTE_ON_BEFORE_UNLOAD: + case self::ATTRIBUTE_ON_BEFORE_PRINT: + case self::ATTRIBUTE_ON_BLUR: + case self::ATTRIBUTE_ON_CLICK: + case self::ATTRIBUTE_ON_CONTEXT_MENU: + case self::ATTRIBUTE_ON_COPY: + case self::ATTRIBUTE_ON_CUT: + case self::ATTRIBUTE_ON_CAN_PLAY: + case self::ATTRIBUTE_ON_CAN_PLAY_THROUGH: + case self::ATTRIBUTE_ON_CHANGE: + case self::ATTRIBUTE_ON_DOUBLE_CLICK: + case self::ATTRIBUTE_ON_DRAG: + case self::ATTRIBUTE_ON_DRAG_END: + case self::ATTRIBUTE_ON_DRAG_ENTER: + case self::ATTRIBUTE_ON_DRAG_LEAVE: + case self::ATTRIBUTE_ON_DRAG_OVER: + case self::ATTRIBUTE_ON_DRAG_START: + case self::ATTRIBUTE_ON_DROP: + case self::ATTRIBUTE_ON_DURATION_CHANGE: + case self::ATTRIBUTE_ON_ERROR: + case self::ATTRIBUTE_ON_EMPTIED: + case self::ATTRIBUTE_ON_ENDED: + case self::ATTRIBUTE_ON_ERROR: + case self::ATTRIBUTE_ON_FOCUS: + case self::ATTRIBUTE_ON_FOCUS_IN: + case self::ATTRIBUTE_ON_FOCUS_OUT: + case self::ATTRIBUTE_ON_HASH_CHANGE: + case self::ATTRIBUTE_ON_INPUT: + case self::ATTRIBUTE_ON_INVALID: + case self::ATTRIBUTE_ON_KEY_DOWN: + case self::ATTRIBUTE_ON_KEY_PRESS: + case self::ATTRIBUTE_ON_KEY_UP: + case self::ATTRIBUTE_ON_LOAD: + case self::ATTRIBUTE_ON_LOADED_DATA: + case self::ATTRIBUTE_ON_LOADED_META_DATA: + case self::ATTRIBUTE_ON_LOAD_START: + case self::ATTRIBUTE_ON_MOUSE_DOWN: + case self::ATTRIBUTE_ON_MOUSE_ENTER: + case self::ATTRIBUTE_ON_MOUSE_LEAVE: + case self::ATTRIBUTE_ON_MOUSE_MOVE: + case self::ATTRIBUTE_ON_MOUSE_OVER: + case self::ATTRIBUTE_ON_MOUSE_OUT: + case self::ATTRIBUTE_ON_MOUSE_UP: + case self::ATTRIBUTE_ON_MESSAGE: + case self::ATTRIBUTE_ON_MOUSE_WHEEL: + case self::ATTRIBUTE_ON_OPEN: + case self::ATTRIBUTE_ON_ONLINE: + case self::ATTRIBUTE_ON_OFFLINE: + case self::ATTRIBUTE_ON_PAGE_SHOW: + case self::ATTRIBUTE_ON_PAGE_HIDE: + case self::ATTRIBUTE_ON_PASTE: + case self::ATTRIBUTE_ON_PAUSE: + case self::ATTRIBUTE_ON_PLAY: + case self::ATTRIBUTE_ON_PLAYING: + case self::ATTRIBUTE_ON_PROGRESS: + case self::ATTRIBUTE_ON_POP_STATE: + case self::ATTRIBUTE_ON_RESIZE: + case self::ATTRIBUTE_ON_RESET: + case self::ATTRIBUTE_ON_RATE_CHANGE: + case self::ATTRIBUTE_ON_SCROLL: + case self::ATTRIBUTE_ON_SEARCH: + case self::ATTRIBUTE_ON_SELECT: + case self::ATTRIBUTE_ON_SUBMIT: + case self::ATTRIBUTE_ON_SEEKED: + case self::ATTRIBUTE_ON_SEEKING: + case self::ATTRIBUTE_ON_STALLED: + case self::ATTRIBUTE_ON_SUSPEND: + case self::ATTRIBUTE_ON_SHOW: + case self::ATTRIBUTE_ON_STORAGE: + case self::ATTRIBUTE_ON_TIME_UPDATE: + case self::ATTRIBUTE_ON_TRANSITION_END: + case self::ATTRIBUTE_ON_TOGGLE: + case self::ATTRIBUTE_ON_TOUCH_CANCEL: + case self::ATTRIBUTE_ON_TOUCH_END: + case self::ATTRIBUTE_ON_TOUCH_MOVE: + case self::ATTRIBUTE_ON_TOUCH_START: + case self::ATTRIBUTE_ON_UNLOAD: + case self::ATTRIBUTE_ON_VOLUME_CHANGE: + case self::ATTRIBUTE_ON_WAITING: + case self::ATTRIBUTE_ON_WHEEL: + case self::ATTRIBUTE_PATTERN: + case self::ATTRIBUTE_PLACE_HOLDER: + case self::ATTRIBUTE_READONLY: + case self::ATTRIBUTE_REQUIRED: + case self::ATTRIBUTE_ROWS: + case self::ATTRIBUTE_SELECTED: + case self::ATTRIBUTE_SIZE: + case self::ATTRIBUTE_SOURCE: + case self::ATTRIBUTE_STEP: + case self::ATTRIBUTE_TYPE: + case self::ATTRIBUTE_WRAP: + case self::ATTRIBUTE_VALUE: + if (!is_string($value)) { + return c_base_return_false(); + } + break; + + case self::ATTRIBUTE_FORM_NO_VALIDATED: + case self::ATTRIBUTE_AUTO_COMPLETE: + case self::ATTRIBUTE_AUTO_FOCUS: + case self::ATTRIBUTE_CHALLENGE: + case self::ATTRIBUTE_CHECKED: + case self::ATTRIBUTE_DISABLED: + case self::ATTRIBUTE_MULTIPLE: + if (!is_bool($value)) { + return c_base_return_false(); + } + break; + + case self::ATTRIBUTE_ACCEPT: + case self::ATTRIBUTE_FORM_ENCODE_TYPE: + if (!this->pr_validate_value_mime_type($value)) { + return c_base_return_false(); + } + break; + + case self::ATTRIBUTE_COLUMNS: + case self::ATTRIBUTE_MAXIMUM: + case self::ATTRIBUTE_MAXIMUM_LENGTH: + case self::ATTRIBUTE_MINIMUM: + if (!is_int($value)) { + return c_base_return_false(); + } + break; + + case self::ATTRIBUTE_FORM_METHOD: + if (!this->pr_validate_value_http_method($value)) { + return c_base_return_false(); + } + break; + + default: + return new c_base_return_false(); + } + + $this->attribute[$attribute] = $value; + + return new c_base_return_true(); + } + + /** + * Get the attributes assigned to this object. + * + * @return c_base_return_array + * The attributes assigned to this class. + * FALSE with error bit set is returned on error. + */ + public function get_attributes() { + if (!isset($this->attributes) && !is_array($this->attributes)) { + $this->attributes = array(); + } + + return new c_base_return_array($this->attributes); + } + + /** + * Get the value of a single attribute assigned to this object. + * + * @param int $attribute + * The attribute to assign. + * + * @return c_base_return_int|c_base_return_string|c_base_return_bool|c_base_return_false + * The value assigned to the attribte (the data type is different per attribute). + * FALSE is returned if the element does not exist. + * FALSE with error bit set is returned on error. + */ + public function get_attribute($attribute) { + if (!is_int($attribute)) { + return c_base_return_error::s_false(); + } + + if (!isset($this->attributes) && !is_array($this->attributes)) { + $this->attributes = array(); + } + + if (array_key_exists($attribute, $this->attributes)) { + switch ($attribute) { + case self::ATTRIBUTE_NONE: + // should not be possible, so consider this an error (attributes set to NONE are actually unset from the array). + return c_base_return_error::s_false(); + + case self::ATTRIBUTE_ACTION: + case self::ATTRIBUTE_DIRECTION_NAME: + case self::ATTRIBUTE_FOR: + case self::ATTRIBUTE_FORM: + case self::ATTRIBUTE_FORM_ACTION: + case self::ATTRIBUTE_FORM_TARGET: + case self::ATTRIBUTE_KEY_TYPE: + case self::ATTRIBUTE_LABEL: + case self::ATTRIBUTE_LIST: + case self::ATTRIBUTE_NAME: + case self::ATTRIBUTE_ON_ABORT: + case self::ATTRIBUTE_ON_AFTER_PRINT: + case self::ATTRIBUTE_ON_ANIMATION_END: + case self::ATTRIBUTE_ON_ANIMATION_ITERATION: + case self::ATTRIBUTE_ON_ANIMATION_start: + case self::ATTRIBUTE_ON_BEFORE_UNLOAD: + case self::ATTRIBUTE_ON_BEFORE_PRINT: + case self::ATTRIBUTE_ON_BLUR: + case self::ATTRIBUTE_ON_CLICK: + case self::ATTRIBUTE_ON_CONTEXT_MENU: + case self::ATTRIBUTE_ON_COPY: + case self::ATTRIBUTE_ON_CUT: + case self::ATTRIBUTE_ON_CAN_PLAY: + case self::ATTRIBUTE_ON_CAN_PLAY_THROUGH: + case self::ATTRIBUTE_ON_CHANGE: + case self::ATTRIBUTE_ON_DOUBLE_CLICK: + case self::ATTRIBUTE_ON_DRAG: + case self::ATTRIBUTE_ON_DRAG_END: + case self::ATTRIBUTE_ON_DRAG_ENTER: + case self::ATTRIBUTE_ON_DRAG_LEAVE: + case self::ATTRIBUTE_ON_DRAG_OVER: + case self::ATTRIBUTE_ON_DRAG_START: + case self::ATTRIBUTE_ON_DROP: + case self::ATTRIBUTE_ON_DURATION_CHANGE: + case self::ATTRIBUTE_ON_ERROR: + case self::ATTRIBUTE_ON_EMPTIED: + case self::ATTRIBUTE_ON_ENDED: + case self::ATTRIBUTE_ON_ERROR: + case self::ATTRIBUTE_ON_FOCUS: + case self::ATTRIBUTE_ON_FOCUS_IN: + case self::ATTRIBUTE_ON_FOCUS_OUT: + case self::ATTRIBUTE_ON_HASH_CHANGE: + case self::ATTRIBUTE_ON_INPUT: + case self::ATTRIBUTE_ON_INVALID: + case self::ATTRIBUTE_ON_KEY_DOWN: + case self::ATTRIBUTE_ON_KEY_PRESS: + case self::ATTRIBUTE_ON_KEY_UP: + case self::ATTRIBUTE_ON_LOAD: + case self::ATTRIBUTE_ON_LOADED_DATA: + case self::ATTRIBUTE_ON_LOADED_META_DATA: + case self::ATTRIBUTE_ON_LOAD_START: + case self::ATTRIBUTE_ON_MOUSE_DOWN: + case self::ATTRIBUTE_ON_MOUSE_ENTER: + case self::ATTRIBUTE_ON_MOUSE_LEAVE: + case self::ATTRIBUTE_ON_MOUSE_MOVE: + case self::ATTRIBUTE_ON_MOUSE_OVER: + case self::ATTRIBUTE_ON_MOUSE_OUT: + case self::ATTRIBUTE_ON_MOUSE_UP: + case self::ATTRIBUTE_ON_MESSAGE: + case self::ATTRIBUTE_ON_MOUSE_WHEEL: + case self::ATTRIBUTE_ON_OPEN: + case self::ATTRIBUTE_ON_ONLINE: + case self::ATTRIBUTE_ON_OFFLINE: + case self::ATTRIBUTE_ON_PAGE_SHOW: + case self::ATTRIBUTE_ON_PAGE_HIDE: + case self::ATTRIBUTE_ON_PASTE: + case self::ATTRIBUTE_ON_PAUSE: + case self::ATTRIBUTE_ON_PLAY: + case self::ATTRIBUTE_ON_PLAYING: + case self::ATTRIBUTE_ON_PROGRESS: + case self::ATTRIBUTE_ON_POP_STATE: + case self::ATTRIBUTE_ON_RESIZE: + case self::ATTRIBUTE_ON_RESET: + case self::ATTRIBUTE_ON_RATE_CHANGE: + case self::ATTRIBUTE_ON_SCROLL: + case self::ATTRIBUTE_ON_SEARCH: + case self::ATTRIBUTE_ON_SELECT: + case self::ATTRIBUTE_ON_SUBMIT: + case self::ATTRIBUTE_ON_SEEKED: + case self::ATTRIBUTE_ON_SEEKING: + case self::ATTRIBUTE_ON_STALLED: + case self::ATTRIBUTE_ON_SUSPEND: + case self::ATTRIBUTE_ON_SHOW: + case self::ATTRIBUTE_ON_STORAGE: + case self::ATTRIBUTE_ON_TIME_UPDATE: + case self::ATTRIBUTE_ON_TRANSITION_END: + case self::ATTRIBUTE_ON_TOGGLE: + case self::ATTRIBUTE_ON_TOUCH_CANCEL: + case self::ATTRIBUTE_ON_TOUCH_END: + case self::ATTRIBUTE_ON_TOUCH_MOVE: + case self::ATTRIBUTE_ON_TOUCH_START: + case self::ATTRIBUTE_ON_UNLOAD: + case self::ATTRIBUTE_ON_VOLUME_CHANGE: + case self::ATTRIBUTE_ON_WAITING: + case self::ATTRIBUTE_ON_WHEEL: + case self::ATTRIBUTE_PATTERN: + case self::ATTRIBUTE_READONLY: + case self::ATTRIBUTE_REQUIRED: + case self::ATTRIBUTE_ROWS: + case self::ATTRIBUTE_SELECTED: + case self::ATTRIBUTE_SIZE: + case self::ATTRIBUTE_SOURCE: + case self::ATTRIBUTE_STEP: + case self::ATTRIBUTE_TYPE: + case self::ATTRIBUTE_WRAP: + case self::ATTRIBUTE_PLACE_HOLDER: + case self::ATTRIBUTE_VALUE: + return c_base_return_string::s_new($value); + + case self::ATTRIBUTE_FORM_NO_VALIDATED: + case self::ATTRIBUTE_AUTO_COMPLETE: + case self::ATTRIBUTE_AUTO_FOCUS: + case self::ATTRIBUTE_CHALLENGE: + case self::ATTRIBUTE_CHECKED: + case self::ATTRIBUTE_DISABLED: + case self::ATTRIBUTE_MULTIPLE: + return c_base_return_bool::s_new($value); + + case self::ATTRIBUTE_ACCEPT: + case self::ATTRIBUTE_FORM_ENCODE_TYPE: + return c_base_return_int::s_new($value); + + case self::ATTRIBUTE_COLUMNS: + case self::ATTRIBUTE_MAXIMUM: + case self::ATTRIBUTE_MAXIMUM_LENGTH: + case self::ATTRIBUTE_MINIMUM: + return c_base_return_int::s_new($value); + + case self::ATTRIBUTE_FORM_METHOD: + return c_base_return_int::s_new($value); + + default: + return new c_base_return_false(); + } + } + + $this->attribute[$attribute] = $value; + + return new c_base_return_false(); + } +} diff --git a/common/theme/classes/theme_html.php b/common/theme/classes/theme_html.php new file mode 100644 index 0000000..41bd434 --- /dev/null +++ b/common/theme/classes/theme_html.php @@ -0,0 +1,21 @@ +: + * - May only be defined in: , , <presentation>, <context>, and <content> tags. + * - When defined inside of <heading>: + * - Provides default settings, such as: language or encoding. + * - Example: <tag type="default" name="language">en-us</tag>. + * - Example: <tag type="default" name="encoding">utf-8</tag>. + * - When defined inside of <title>: + * - Each tag may have an "id" attribute, such as: <tag id="unique_name-1"></tag>.. + * - Must not contain any nested markup. + * - When defined inside of <presentation>: + * - Each tag may have an "id" attribute, such as: <tag id="unique_name-2"></tag>. + * - Each tag may have a "class" attribute, such as: <tag class="a b c"></tag>. + * - Each tag may have a "context" attribute, such as: <tag context="some_name"></tag>. + * - This represents the context of how the tag is presented (and HTML5 example would be: "banner"). + * - Each tag may have a "content" attribute, such as: <tag content="message-1"></tag>. + * - Associates some block of information or text. + * - Each tag may have a "tooltip" attribute, such as: <tag tooltip="message-2"></tag>. + * - Each tag may have a "file" attribute, such as: <tag file="image-person.png"></tag>. + * - Alternate text must not be defined for these files here because this is "presentation" and not "content". + * - When defined inside of <context>: + * - @todo: details need to be fleshed out. + * - When defined inside of <content>: + * - Each tag must have an "id" attribute, such as: <tag id="unique_name-3"></tag>. + * - Each tag may have a "context" attribute, such as: <tag context="some_name"></tag>. + * - This represents the context of the content and not about how it is presented (and HTML5 example would be: "aside"). + * - Each tag may have a "file" attribute, such as: <tag file="image-person.png">This text between the open and close tag is the "alternate text" or "description" describing the file.</tag>. + * - When defined inside of <files>: + * - Each tag may have an "id" attribute, such as: <tag id="css-unique_name">css/somewhere.css</tag>. + * - Each tag may have a "type" attribute, such as: <tag type="text/css">http://example.com/css/somewhere.css</tag>. + * - Must not contain any nested markup. + * - <title>: + * - A name representing the titles. + * - May have multiple titles (order-sensitive), such as: + * <title> + * <tag id="unique_title-1">First Title</tag> + * <tag id="unique_title-2">Second Title</tag> + * + * - Must not contain any nested markup. + * - : + * - Provides heading information, such as external javascript or css files. + * - Similar to the HTML tag. + * - May only contain the following tags: (@todo: determine what these will be). + * - @todo: details need to be fleshed out. + * - : + * - Provides how to interpret data (such as text or images). + * - May only contain the following tags: . + * - @todo: details need to be fleshed out. + * - Context may or may not be provided here, instead a global standard based on HTML5, WCAG, ARIA, etc.. may be used to determine a static list of contexts. + * - : + * - All javascript, css, documents, images, et al.. must be defined here. + * - In this way, all files associated with this content are defined here and must have a unique id to be referenced. + * - Example: /images/favicon.ico + * - : + * - All content to be read is provided here. + * - May only contain the following tags: . + * - Example: + * + * This is some text that I wanted to present to you.. + * This could be used anywhere, including 'tooltip' and 'title' sections.. + * This could be used anywhere, but is likely intended for 'alt' attribute like uses.. + * + * - : + * - The flow and presentation of the content is provided here. + * - May only contain the following tags: . + * - example: + */ + +// include required files. +require_once('common/base/classes/base_error.php'); +require_once('common/base/classes/base_return.php'); +require_once('common/theme/classes/theme_dom.php'); + +/** + * Generic tag class for building and renderring markup. + */ +class c_theme_tag { + const TYPE_NONE = 0; + const TYPE_TITLE = 1; + const TYPE_HEADING = 2; + const TYPE_FILES = 3; + const TYPE_CONTEXT = 4; + const TYPE_PRESENTATION = 5; + const TYPE_CONTENT = 6; + + // custom text attatched to the inside of the + private $text = NULL; + + // the attributes, includes all attributes available. + private $attributes = NULL; + private $attributes_length = 0; + + // an array of child tags (each tag must be a class/subclass of c_theme_tag). + private $tags = NULL; + private $tags_length = NULL; + + private $type = NULL; + + /** + * Class constructor. + */ + public function __construct() { + $this->text = NULL; + $this->attributes = array(); + $this->attributes_length = 0; + + $this->tags = array(); + $this->tags_length = NULL; + + $this->type = self::TYPE_NONE; + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->text); + unset($this->attributes); + unset($this->attributes_length); + unset($this->tags); + unset($this->tags_length); + unset($this->type); + } + + /** + * Assign a text to the object. + * + * This will not set text if child tags are defined. + * + * @param string $text + * A string to use as the text. + * + * @return c_base_return_status + * TRUE on success. + * FALSE is returned if the text cannot be set. + * FALSE with error bit set is returned on error. + * + * @see: self::delete_tags() + */ + public function set_text($text) { + if (!is_string($text)) { + return c_base_return_error::s_false(); + } + + // Text may only be defined when there are no child tags and vice-versa. + if ($this->tags_length > 0) { + return new c_base_return_false(); + } + elseif (!is_null($this->tags_length)) { + $this->tags_length = NULL; + } + + // prevent the assigned text from including markup by translating everything to html entities. + $this->text = htmlspecialchars($text, ENT_HTML5 | ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'); + + return new c_base_return_true(); + } + + /** + * Set the type this tag belongs to. + * + * Unsupported attributes that are not allowed will be removed. + * + * @param int $type + * The numeric id representing the type of tag. + * + * @return c_base_return_status + * TRUE is returned on success. + * FALSE is returned if the type cannot be set. + * FALSE with error bit set is returned on error. + */ + public function set_type($type) { + if (!is_int($type) || $type < self::TYPE_NONE) { + return c_base_return_error::s_false(); + } + + if ($this->type === $type) { + return new c_base_return_true(); + } + elseif ($type === self::TYPE_NONE) { + $new_attributes = array(); + } + elseif ($type === self::TYPE_TITLE) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + } + elseif ($type === self::TYPE_HEADING) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + + if (array_key_exists('type', $this->attributes)) { + $new_attributes['type'] = $this->attributes['type']; + } + + if (array_key_exists('name', $this->attributes)) { + $new_attributes['name'] = $this->attributes['name']; + } + } + elseif ($type === self::TYPE_FILES) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + + if (array_key_exists('type', $this->attributes)) { + $new_attributes['type'] = $this->attributes['type']; + } + } + elseif ($type === self::TYPE_CONTEXT) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + } + elseif ($type === self::TYPE_PRESENTATION) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + + if (array_key_exists('class', $this->attributes)) { + $new_attributes['class'] = $this->attributes['class']; + } + + if (array_key_exists('context', $this->attributes)) { + $new_attributes['context'] = $this->attributes['context']; + } + + if (array_key_exists('content', $this->attributes)) { + $new_attributes['content'] = $this->attributes['content']; + } + + if (array_key_exists('tooltip', $this->attributes)) { + $new_attributes['tooltip'] = $this->attributes['tooltip']; + } + + if (array_key_exists('file', $this->attributes)) { + $new_attributes['file'] = $this->attributes['file']; + } + } + elseif ($type === self::TYPE_CONTENT) { + $new_attributes = array(); + + if (array_key_exists('id', $this->attributes)) { + $new_attributes['id'] = $this->attributes['id']; + } + + if (array_key_exists('context', $this->attributes)) { + $new_attributes['context'] = $this->attributes['context']; + } + + if (array_key_exists('file', $this->attributes)) { + $new_attributes['file'] = $this->attributes['file']; + } + } + else { + return new c_base_return_false(); + } + + unset($this->attributes); + $this->attributes = $new_attributes; + $this->attributes_length = count($new_attributes); + unset($new_attributes); + + $this->type = $type; + return new c_base_return_true(); + } + + /** + * This will set a value for the given attribute name. + * + * This is less efficient than calling a specific attribute function such as set_attribute_name(). + * This is because the name must be processed and only allowed names will be supported. + * + * Child classes that add additional attributes should override this function. + * + * @param string $name + * The attribute name to assign. + * @param string $value + * The value of the attribute to assign. + * + * @return c_base_return_status + * TRUE is returned on success. + * FALSE is returned if unable to set the attribute (the tag type might not support the attribute). + * FALSE with error bit is returned on error. + */ + public function set_attribute($name, $value) { + return $this->pr_set_attribute($name, $value); + } + + /** + * Assign tags to this object. + * + * Objects cannot be added if text is assigned. + * + * @param __class__ $tag + * When a __class__ object, the tag is appended to the end of the tags array. + * @param int|null $index + * When an integer, represents the position within the tag array to assign the tag. + * - May not be less than 0 or greater than the length of the array. + * When $tag is NULL, this does nothing. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was added on success. + * When $tag is NULL, TRUE is returned on success. + * FALSE is returned if unable to set tags. + * FALSE with error bit set is returned on error. + * + * @see: self::set_tags() + * @see: self::delete_text() + */ + public function set_tag($tag, $index = NULL) { + if (!is_object($tag) || !($tag instanceof c_theme_tag)) { + return c_base_return_error::s_false(); + } + + // do not allow adding tags if there is assigned text. + if (!is_null($this->text)) { + return new c_base_return_false(); + } + + if (is_null($index)) { + $this->tags[$this->tags_length] = $tag; + $this->tags_length++; + return c_base_return_int::s_new($this->tags_length - 1); + } + else { + if (!is_int($index) || $index < 0 || $index > $this->tags_length) { + return c_base_return_error::s_false(); + } + + $this->tags[$index] = $tag; + return c_base_return_int::s_new($index); + } + + return c_base_return_error::s_false(); + } + + /** + * Assign multiple tags to this object. + * + * Objects will not be added if text is assigned. + * + * @param array $tags + * An array of __class__ objects. + * Each individual item is checked and then appended to the end of the array. + * - The array keys will not be preserved. + * + * @param int|null $index + * When an integer, represents the position within the tag array to insert the tags. + * - May not be less than 0 or greater than the length of the array. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was added on success. + * FALSE is returned if unable to set tags. + * FALSE with error bit set is returned on error. + * + * @see: self::set_tag() + * @see: self::delete_text() + */ + public function set_tags($tags, $index = NULL) { + if (!is_array($tags)) { + return c_base_return_error::s_false(); + } + + // FALSE without error bit set is returned when no tags are to be added (empty array). + if (empty($tags)) { + return new c_base_return_false(); + } + + // do not allow adding tags if there is assigned text. + if (!is_null($this->text)) { + return new c_base_return_false(); + } + + if (is_null($index)) { + $failure = FALSE; + $tags_length = $this->tags_length; + foreach ($tags as $tag) { + // every single item must be validated before any part of the array will be allowed to be added to this object. + if (!is_object($tag) || !($tag instanceof c_theme_tag)) { + $failure = TRUE; + break; + } + + $this->tags[$this->tags_length] = $tag; + $this->tags_length++; + } + unset($tag); + + if ($failure) { + unset($failure); + + // none of the array shall be added if any of the array is invalid. + array_splice($this->tags, $tags_length); + + $this->tags_length = $tags_length; + unset($tags_length); + + return new c_base_return_false(); + } + unset($failure); + unset($tags_length); + + return c_base_return_int::s_new($this->tags_length); + } + + foreach ($tag as $tag) { + // every single item must be validated before any part of the array will be allowed to be added to this object. + if (!is_object($tag) || !($tag instanceof c_theme_tag)) { + unset($tag); + return c_base_return_error::s_false(); + } + } + unset($tag); + + if ($index == 0) { + $original_tags = $this->tags; + unset($this->tags); + + $this->tags = $tags; + + array_merge($this->tags, $original_tags); + unset($original_tags); + + $this->tags_length += count($tags); + return c_base_return_int::s_new(0); + } + + $remaining_tags = array_splice($this->tags, $index, ($this->tags_length - $index), $tags); + array_merge($this->tags, $remaining_tags); + unset($remaining_tags); + + $this->tags_length += count($tags); + return c_base_return_int::s_new($index); + } + + /** + * Get the text assigned to this object. + * + * @return c_base_return_status|c_base_return_string|c_base_return_null + * The assigned text. + * NULL is returned if no text is assigned. + * FALSE with error bit set is returned on error. + * + * @see: self::get_text_decoded() + */ + public function get_text() { + if (is_null($this->text)) { + return new c_base_return_null(); + } + + return c_base_return_string::s_new($this->text); + } + + /** + * Get the text assigned to this object. + * + * The returned text is decoded from the internally stored html entity format. + * + * @return c_base_return_status|c_base_return_string|c_base_return_null + * The assigned text (decoded). + * NULL is returned if no text is assigned. + * FALSE with error bit set is returned on error. + * + * @see: self::get_text() + */ + public function get_text_decoded() { + if (is_null($this->text)) { + return new c_base_return_null(); + } + + return c_base_return_string::s_new(htmlspecialchars_decode($this->text, ENT_HTML5 | ENT_NOQUOTES | ENT_IGNORE, 'UTF-8')); + } + + /** + * Get the type this tag belongs to. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the type is returned on success. + * FALSE is returned if the type is not set. + * FALSE with error bit set is returned on error. + */ + public function get_type() { + if (is_null($this->type)) { + return new c_base_return_false(); + } + + return c_base_return_int::s_new($this->type); + } + + /** + * This will get a value for the given attribute name. + * + * This is less efficient than calling a specific attribute function such as get_attribute_name(). + * This is because the name must be processed and only allowed names will be supported. + * + * Child classes that add additional attributes should override this function. + * + * @param string $name + * The attribute name to get. + * + * @return c_base_return_status|c_base_return_string + * The attribute value string is returned on success. + * NULL is returned if the attribute is not set. + * FALSE is returned if unable to get the attribute (the tag type might not support the attribute). + * FALSE with error bit is returned on error. + */ + public function get_attribute($name) { + return $this->pr_get_attribute($name); + } + + /** + * Get the all of the attributes assigned to this object. + * + * @return c_base_return_status|c_base_return_array + * An array containing all attributes. + * FALSE with error bit set is returned on error. + */ + public function get_attributes() { + if (is_array($this->attributes)) { + return c_base_return_array($this->attributes); + } + + return c_base_return_error::s_false(); + } + + /** + * Get the length of the attributes array. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the length of the attributes array. + * FALSE with error bit set is returned on error. + */ + public function get_attributes_length() { + return c_base_return_int::s_new($this->attributes_length); + } + + /** + * Get a single tag assigned to this object. + * + * @param int|null $index + * (optional) An integer representing the position within the tag array to return. + * When NULL, the tag at the end of the tags array is returned. + * + * @return c_base_return_status|c_theme_return_tag|c_base_return_null + * A c_theme_return_tag object on success. + * NULL might be returned if there is no object assigned to the index. + * Otherwise FALSE is returned. + * FALSE with error bit set is returned on error. + * + * @see: self::get_tags() + */ + public function get_tag($index = NULL) { + if (!is_null($index)) { + if (!is_int($index) || $index < 0 || $index >= $this->tags_length) { + return c_base_return_error::s_false(); + } + } + + if (is_null($index)) { + $last = end($this->tags); + if (!is_array($last)) { + unset($last); + return new c_base_return_false(); + } + + return c_theme_return_tag::s_new($last); + } + + if (is_null($this->tags[$index])) { + return new c_base_return_null(); + } + + return c_theme_return_tag::s_new($this->tags[$index]); + } + + /** + * Get tags assigned to this object. + * + * @param int $index + * When a positive integer, an integer representing the position within the tag array to return. + * @param int|null $length + * (optional) When NULL, returns all tags following the index. + * When an integer, the total number of tags following the index to return. + * @param bool $preserve_keys + * (optional) When TRUE array keys will be preserved. + * When FALSE, the array keys are reset. + * + * @return c_base_return_status|c_theme_return_array + * An array of tags on success, FALSE otherwise. + * FALSE with error bit set is returned on error. + * + * @see: self::get_tag() + * @see: self::array_slice() + */ + public function get_tags($index, $length = NULL, $preserve_keys = TRUE) { + if (!is_int($index) || $index < 0 || $index >= $this->tags_length) { + return c_base_return_error::s_false(); + } + + if (!is_null($length)) { + if (!is_int($length) || $length < 1 || $length > $this->tags_length) { + return c_base_return_error::s_false(); + } + } + + if (!is_bool($preserve_keys)) { + return c_base_return_error::s_false(); + } + + return c_theme_return_array::s_new(array_slice($this->tags, $index, $length, $preserve_keys)); + } + + /** + * Removes text assigned to this object. + * + * @return c_base_return_false + * TRUE is returned on success. + * FALSE without error bit set is returned if text is already deleted. + * FALSE with error bit set is returned on error. + */ + public function delete_text() { + if (is_null($this->text)) { + return new c_base_return_false(); + } + + unset($this->text); + $this->text = NULL; + return new c_base_return_true(); + } + + /** + * This will delete the value for the given attribute name. + * + * This is less efficient than calling a specific attribute function such as delete_attribute_name(). + * This is because the name must be processed and only allowed names will be supported. + * + * Child classes that add additional attributes should override this function. + * + * @param string $name + * The attribute name to delete. + * + * @return c_base_return_status|c_base_return_string + * The attribute value string is returned on success. + * FALSE is returned for unknown attributes names. + * FALSE with error bit is returned on error. + */ + public function delete_attribute($name) { + return $this->pr_delete_attribute($name); + } + + /** + * This will delete all attributes assigned to this object. + * + * @return c_base_return_status + * TRUE is returned on success. + * FALSE with error bit is returned on error. + */ + public function delete_attributes() { + unset($this->attributes); + $this->attributes = array(); + $this->attributes_length = 0; + + return new c_base_return_true(); + } + + /** + * Delete tag from this object. + * + * Tags not stored at the end of the array are set to NULL instead of being deleted. + * The length is therefore not shortened unless the deleted tag is at the end of the array. + * Call $this->sanitize_tags() to ensure that the array structure contains none of these holes. + * + * @param int|null $index + * (optional) When an integer, represents the position of the tag to delete. + * - May not be less than 0 or greater than the length of the array. + * When $tag is NULL, deletes the tag at the end of the tag array. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was deleted on success. + * Otherwise, FALSE is returned. + * FALSE without error bit set is returned if the tag at the specified index is already deleted. + * FALSE with error bit set is returned on error. + * + * @see: self::delete_tags() + * @see: self::sanitize_tags() + */ + public function delete_tag($index = NULL) { + if (is_null($index)) { + $position = $this->tags_length; + $this->tags_length--; + + unset($this->tags[$this->tags_length]); + + return c_base_return_int::s_new($position); + } + + if (!is_int($index) || $index < 0 || $index >= $this->tags_length) { + return c_base_return_error::s_false(); + } + + if (is_null($this->tags[$index])) { + return new c_base_return_false(); + } + + $this->tags[$index] = NULL; + return c_base_return_int::s_new($index); + } + + /** + * Delete multiple tags from this object. + * + * Tags not stored at the end of the array are set to NULL instead of being deleted. + * The length is therefore not shortened unless the deleted tag is at the end of the array. + * Call $this->sanitize_tags() to ensure that the array structure contains none of these holes. + * + * @param int $index + * (optional) When an integer, represents the position of the tag to delete. + * - May not be less than 0 or greater than the length of the array. + * @param int|null $length + * (optional) When NULL, returns all tags following the index. + * When an integer, the total number of tags following the index to return. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the total number of tags deleted on success. + * Otherwise, FALSE is returned. + * FALSE without error bit set is returned if no tags were deleted. + * FALSE with error bit set is returned on error. + * + * @see: self::delete_tag() + * @see: self::sanitize_tags() + */ + public function delete_tags($index, $length = NULL) { + if (!is_int($index) || $index < 0 || $index >= $this->tags_length) { + return c_base_return_error::s_false(); + } + + if (is_null($length)) { + $total = $this->tags_length; + } + else { + if (!is_int($length) || $length < 1 || $length > $this->tags_length) { + return c_base_return_error::s_false(); + } + + $total = $length; + } + + $count = $index; + $deleted = 0; + for (; $count < $total; $count++) { + if (is_null($this->tags[$count])) { + continue; + } + + unset($this->tags[$count]); + $this->tags[$count] = NULL; + $deleted++; + } + unset($count); + + if ($deleted == $total) { + $this->tags = array(); + $this->tags_length = 0; + } + + if ($deleted == 0) { + unset($deleted); + return new c_base_return_false(); + } + + return c_base_return_int::s_new($deleted); + } + + /** + * Determine if text has been added to this object. + * + * @return c_base_return_status + * TRUE is returned if tags have been added, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function has_text() { + if (is_null($this->text)) { + return new c_base_return_false(); + } + + return new c_base_return_true(); + } + + /** + * Determine if an attribute has been added to this object. + * + * @param string $name + * The attribute name to delete. + * + * @return c_base_return_status + * TRUE is returned if added, FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function has_attribute($name) { + return $this->pr_has_attribute($name); + } + + /** + * Convert this object and all of its children or text to markup. + * + * @return c_base_return_string + * A string representing this object in the form of HTMl compatible markup. + */ + public function to_markup() { + $markup = 'tags_length)) { + $markup .= 's'; + } + + if ($this->type === self::TYPE_TITLE) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + } + elseif ($this->type === self::TYPE_HEADING) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + + if (isset($this->attributes['type'])) { + $markup .= ' type="' . $this->attributes['type'] . '"'; + } + + if (isset($this->attributes['name'])) { + $markup .= ' name="' . $this->attributes['name'] . '"'; + } + } + elseif ($this->type === self::TYPE_FILES) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + + if (isset($this->attributes['type'])) { + $markup .= ' type="' . $this->attributes['type'] . '"'; + } + } + elseif ($this->type === self::TYPE_CONTEXT) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + } + elseif ($this->type === self::TYPE_PRESENTATION) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + + if (isset($this->attributes['class'])) { + $markup .= ' class="' . $this->attributes['class'] . '"'; + } + + if (isset($this->attributes['context'])) { + $markup .= ' context="' . $this->attributes['context'] . '"'; + } + + if (isset($this->attributes['content'])) { + $markup .= ' content="' . $this->attributes['content'] . '"'; + } + + if (isset($this->attributes['tooltip'])) { + $markup .= ' tooltip="' . $this->attributes['tooltip'] . '"'; + } + + if (isset($this->attributes['file'])) { + $markup .= ' file="' . $this->attributes['file'] . '"'; + } + } + elseif ($this->type === self::TYPE_CONTENT) { + if (isset($this->attributes['id'])) { + $markup .= ' id="' . $this->attributes['id'] . '"'; + } + + if (isset($this->attributes['context'])) { + $markup .= ' context="' . $this->attributes['context'] . '"'; + } + + if (isset($this->attributes['file'])) { + $markup .= ' file="' . $this->attributes['file'] . '"'; + } + } + + $markup .= '>'; + + if (is_null($this->tags_length)) { + $markup .= $this->text; + $markup .= ''; + } + else { + foreach ($this->tags as $tag) { + $markup .= $tag->to_markup()->get_value_exact(); + } + + $markup .= ''; + } + + return c_base_return_string::s_new($markup); + } + + /** + * Converts this object into a DOMElement object. + * + * @param c_theme_dom|null $dom_document + * (optional) The DOMDocument object to operate with. + * If NULL, then the DOMDocument object is auto-generated. + * + * @return c_base_return_status|c_theme_return_dom_element + * A DOMElement is returned on success. + * Otherwise FALSE is returned. + * FALSE with error bit set is returned on error. + * + * @see: self::p_to_dom_element() + */ + public function to_dom_element($dom_document = NULL) { + if (is_null($dom_document)) { + $dom = new DOMDocument(); + } + else { + if (!is_object($dom_document) || !($dom_document instanceof DOMDocument)) { + return c_base_return_error::s_false(); + } + + $dom = $dom_document; + } + + $element = $this->p_to_dom_element($dom); + unset($dom); + + if ($element === FALSE) { + unset($element); + return new c_base_return_false(); + } + + return c_theme_return_dom_element::s_new($element); + } + + /** + * Converts DOMNode to the structure provided by this class. + * + * @fixme: update this based on c_theme_markup design. + * + * This will delete the entire contents of this object and replace it with the contents of the provided DOMElement object. + * + * This assumes that the class follows the rules of this object. + * + * @param DOMNode $dom_node + * The DOMNode object to load into this class. + * This expects the supplied markup to provided tags used by this class (tag, tags, etc..). + * All other tags are ignored. + * + * @return c_base_return_status + * TRUE on success. + * FALSE otherwise. + * FALSE with error bit set is returned on error. + */ + public function from_dom_node($dom_node) { + if (!is_object($dom_node) || !($dom_node instanceof DOMNode)) { + return c_base_return_error::s_false(); + } + + // objects are passed via reference in function names, prevent accidents by acting on a clone. + $node = clone($dom_node); + + // clear the contents of this class. + $this->__construct(); + + if ($this->type === self::TYPE_TAG) { + // @todo: does this need to be sanitized, or would that result in double-sanitization? + $this->text = $node->textContent; + } + elseif ($this->type === self::TYPE_TAGS && $node->hasChildNodes()) { + foreach ($node->childNodes as $child_node) { + if (!($child_node instanceof DOMNode)) { + continue; + } + + if ($child_node->nodeName == 'tag') { + $child_tag = new c_theme_tag(); + if ($child_node->hasAttributes()) { + foreach ($child_node->attributes as $child_attribute) { + $child_tag->set_attribute($child_attribute->localName, $child_attribute->nodeValue); + } + unset($child_attribute); + } + + // only supports text. + $child_tag->set_text($child_node->textContent); + + $this->set_tag($child_tag); + unset($child_tag); + } + elseif ($child_node->nodeName == 'tags') { + $child_tag = new c_theme_tag(); + if ($child_node->hasAttributes()) { + foreach ($child_node->attributes as $child_attribute) { + $child_tag->set_attribute($child_attribute->localName, $child_attribute->nodeValue); + } + unset($child_attribute); + } + + // only supports or . + if ($child_node->hasChildNodes()) { + foreach ($child_node->childNodes as $child_node_child) { + $child_node_child_tag = new c_theme_tag(); + $result = $child_node_child_tag->from_dom_node($child_node_child); + + if ($result instanceof c_base_return_true) { + $child_tag->set_tag($child_node_child_tag); + } + + unset($result); + unset($child_node_child_tag); + } + unset($child_node_child); + } + + $this->set_tag($child_tag); + unset($child_tag); + } + } + } + + if ($dom_node->hasAttributes()) { + foreach ($dom_node->attributes as $node_attribute) { + $this->set_attribute($node_attribute->localName, $node_attribute->nodeValue); + } + unset($node_attribute); + } + + return new c_base_return_true(); + } + + /** + * Perform attribute "set" operation. + * + * This is provided to reduce duplication of code. + * It is intended to be called by a public function and will return the expected results of that public function. + * + * @param string $attribute_name + * The name of the attribute to operate on. + * @param string|null $attribute_value + * A string to use as the attribute_value. + * + * @return c_base_return_status + * TRUE on success. + * FALSE is returned if the attribute cannot be set (the tag type might not support the attribute). + * FALSE with error bit set is returned on error. + */ + protected function pr_set_attribute($attribute_name, $attribute_value) { + if (!is_string($attribute_name)) { + return c_base_return_error::s_false(); + } + + // require the non-null string to not be empty. + // multi-byte is unecessary here because this is a test not for characters but instead for a non-empty string. + if (!is_string($attribute_value) || strlen($attribute_value) < 1) { + return c_base_return_error::s_false(); + } + + $allowed = $this->p_allowed_attribute($attribute_name); + if (is_null($allowed)) { + unset($allowed); + return c_base_return_error::s_false(); + } + elseif ($allowed) { + unset($allowed); + } + else { + unset($allowed); + return new c_base_return_false(); + } + + // attribute name needs sanitization. + $fixed_name = preg_replace('/[^\w]/', '', $fixed_name); + if (!is_string($fixed_name)) { + unset($fixed_name); + return c_base_return_error::s_false(); + } + + // double quotes are not allowed because the class is designed to wrap attribute values in double quotes. + $this->attributes[$fixed_name] = str_replace('"', '"', $attribute_value); + unset($fixed_name); + + return new c_base_return_true(); + } + + /** + * Perform attribute "get" operation. + * + * This is provided to reduce duplication of code. + * It is intended to be called by a public function and will return the expected results of that public function. + * + * @param string $attribute_name + * The name of the attribute to operate on. + * + * @return c_base_return_status|c_base_return_string|c_base_return_null + * The assigned id string. + * NULL is returned if the attribute is not set. + * FALSE is returned if unable to get the attribute (the tag type might not support the attribute). + * FALSE with error bit set is returned on error. + */ + protected function pr_get_attribute($attribute_name) { + if (!is_string($attribute_name)) { + return c_base_return_error::s_false(); + } + + $allowed = $this->p_allowed_attribute($attribute_name); + if (is_null($allowed)) { + unset($allowed); + return c_base_return_error::s_false(); + } + elseif ($allowed) { + unset($allowed); + } + else { + unset($allowed); + return new c_base_return_false(); + } + + if (!array_key_exists($attribute_name, $this->attributes)) { + return new c_base_return_null(); + } + + return c_base_return_string::s_new($this->attributes[$attribute_name]); + } + + /** + * Perform attribute "delete" operation. + * + * This assigned the attribute value to NULL. + * + * This is provided to reduce duplication of code. + * It is intended to be called by a public function and will return the expected results of that public function. + * + * @param string $attribute_name + * The name of the attribute to operate on. + * + * @return c_base_return_status + * TRUE is returned if the attribute is successfully deleted. + * FALSE is returned if the attribute cannot be deleted (the tag type might not support the attribute). + * FALSE with error bit set is returned on error. + */ + protected function pr_delete_attribute($attribute_name) { + if (!is_string($attribute_name)) { + return c_base_return_error::s_false(); + } + + $allowed = $this->p_allowed_attribute($attribute_name); + if (is_null($allowed)) { + unset($allowed); + return c_base_return_error::s_false(); + } + elseif ($allowed) { + unset($allowed); + } + else { + unset($allowed); + return new c_base_return_false(); + } + + unset($this->attributes[$attribute_name]); + return new c_base_return_true(); + } + + /** + * Perform attribute "has" operation. + * + * This is provided to reduce duplication of code. + * It is intended to be called by a public function and will return the expected results of that public function. + * + * @param string $attribute_name + * The name of the attribute to operate on. + * + * @return c_base_return_status + * TRUE if attribute is assigned. + * FALSE if the attribute is not assigned. + * FALSE with error bit set is returned on error. + */ + protected function pr_has_attribute($attribute_name) { + if (!is_string($attribute_name)) { + return c_base_return_error::s_false(); + } + + $allowed = $this->p_allowed_attribute($attribute_name); + if (is_null($allowed)) { + unset($allowed); + return c_base_return_error::s_false(); + } + elseif ($allowed) { + unset($allowed); + } + else { + unset($allowed); + return new c_base_return_false(); + } + + if (array_key_exists($attribute_name, $this->attributes)) { + return new c_base_return_true(); + } + + return new c_base_return_false(); + } + + /** + * Re-calculates the attributes array length. + * + * @see: self::sanitize_attributes() + */ + private function p_sanitize_attributes() { + $this->attributes_length = count($this->attributes); + + return TRUE; + } + + /** + * Check to see if the given attribute name is allowed according to the tag type. + * + * @param string $attribute_name + * The attribute name to test. + * + * @return bool|null + * TRUE is returned if allowed. + * FALSE is returned if not allowed. + * NULL is returned for unknown tag type. + */ + private function p_allowed_attribute($attribute_name) { + if ($this->type === self::TYPE_TITLE) { + switch ($attribute_name) { + case 'id': + return TRUE; + default: + return FALSE; + } + } + elseif ($this->type === self::TYPE_HEADING) { + switch ($attribute_name) { + case 'id': + case 'type': + case 'name': + return TRUE; + default: + return FALSE; + } + } + elseif ($this->type === self::TYPE_FILES) { + switch ($attribute_name) { + case 'id': + case 'type': + return TRUE; + default: + return FALSE; + } + } + elseif ($this->type === self::TYPE_CONTEXT) { + switch ($attribute_name) { + case 'id': + return TRUE; + default: + return FALSE; + } + } + elseif ($this->type === self::TYPE_PRESENTATION) { + switch ($attribute_name) { + case 'id': + case 'class': + case 'context': + case 'content': + case 'tooltip': + case 'file': + return TRUE; + default: + return FALSE; + } + } + elseif ($this->type === self::TYPE_CONTENT) { + switch ($attribute_name) { + case 'id': + case 'context': + case 'file': + return TRUE; + default: + return FALSE; + } + } + + return NULL; + } + + /** + * Removes all NULL entries from the tags array and re-calculates the length. + * + * @param bool + * TRUE on success. + * FALSE otherwise. + * + * @see: self::sanitize_tags(); + */ + private function p_sanitize_tags() { + if (empty($this->tags)) { + $this->tags_length = 0; + } + else { + $tags = array(); + $total = 0; + foreach ($tags as $tag) { + if (is_null($tag)) { + continue; + } + + $tags[$total] = $tag; + $total++; + } + + unset($this->tags); + $this->tags = $tags; + $this->tags_length = $total; + + unset($tags); + unset($total); + + if ($this->type === self::TYPE_TAG) { + $this->type = self::TYPE_TAGS; + } + } + + return TRUE; + } + + /** + * Ensures that text is consistent with the rules of this class. + * + * @see: self::sanitize_text() + */ + private function p_sanitize_text() { + if ($this->tags_length > 0) { + $this->text = NULL; + + if ($this->type === self::TYPE_TAG) { + $this->type = self::TYPE_TAGS; + } + } + elseif (!is_null($this->text)) { + if ($this->type !== self::TYPE_TAG) { + $this->type = self::TYPE_TAG; + } + } + + return TRUE; + } + + /** + * Converts this object into a DOMElement object. + * + * @param c_theme_dom $dom + * The DOMDocument object to operate with. + * + * @return DOMElement|FALSE + * A DOMElement is returned on success. + * FALSE is returned on error. + * + * @see: self::to_dom_element() + */ + public function p_to_dom_element($dom) { + if (is_null($this->tags_length)) { + $element = $dom->createElement('tag'); + + if (!is_null($this->text)) { + $element->appendChild(new DOMText($this->text)); + } + } + else { + $element = $dom->createElement('tags'); + + if ($this->tags_length > 0) { + foreach ($this->tags as $tag) { + if (is_null($tag)) { + continue; + } + + $child = $tag->to_dom_element($dom); + if ($child instanceOf c_theme_return_dom_element) { + $element->appendChild($child->get_value_exact()); + } + else { + unset($child); + unset($tag); + unset($element); + return FALSE; + } + unset($child); + } + unset($tag); + } + } + + if ($this->type === self::TYPE_TITLE) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + } + elseif ($this->type === self::TYPE_HEADING) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + + if (isset($this->attributes['type'])) { + $element->setAttribute('type', $this->attributes['type']); + } + + if (isset($this->attributes['name'])) { + $element->setAttribute('name', $this->attributes['name']); + } + } + elseif ($this->type === self::TYPE_FILES) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + + if (isset($this->attributes['type'])) { + $element->setAttribute('type', $this->attributes['type']); + } + } + elseif ($this->type === self::TYPE_CONTEXT) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + } + elseif ($this->type === self::TYPE_PRESENTATION) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + + if (isset($this->attributes['class'])) { + $element->setAttribute('class', $this->attributes['class']); + } + + if (isset($this->attributes['context'])) { + $element->setAttribute('context', $this->attributes['context']); + } + + if (isset($this->attributes['content'])) { + $element->setAttribute('content', $this->attributes['content']); + } + + if (isset($this->attributes['tooltip'])) { + $element->setAttribute('tooltip', $this->attributes['tooltip']); + } + + if (isset($this->attributes['file'])) { + $element->setAttribute('file', $this->attributes['file']); + } + } + elseif ($this->type === self::TYPE_CONTENT) { + if (isset($this->attributes['id'])) { + $element->setAttribute('id', $this->attributes['id']); + } + + if (isset($this->attributes['context'])) { + $element->setAttribute('context', $this->attributes['context']); + } + + if (isset($this->attributes['file'])) { + $element->setAttribute('file', $this->attributes['file']); + } + } + + return $element; + } +} + +/** + * A return class whose value is represented as a __class__. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_tag extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param __class__ $value + * Any value so long as it is a __class__. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!$value instanceof c_theme_tag) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof c_theme_tag)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return __class__ $value + * The value c_theme_dom stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof c_theme_tag)) { + $this->value = new c_theme_tag(); + } + + return $this->value; + } +} + +/** + * A complete context markup language class. + */ +class c_theme_markup { + private $title = NULL; + private $heading = NULL; + private $files = NULL; + private $context = NULL; + private $presentation = NULL; + private $content = NULL; + + /** + * Class constructor. + */ + public function __construct() { + $this->title = new c_theme_tag(); + $this->title->set_type(c_theme_tag::TYPE_TITLE); + + $this->heading = new c_theme_tag(); + $this->heading->set_type(c_theme_tag::TYPE_HEADING); + + $this->files = new c_theme_tag(); + $this->files->set_type(c_theme_tag::TYPE_FILES); + + $this->context = new c_theme_tag(); + $this->context->set_type(c_theme_tag::TYPE_CONTEXT); + + $this->presentation = new c_theme_tag(); + $this->presentation->set_type(c_theme_tag::TYPE_PRESENTATION); + + $this->content = new c_theme_tag(); + $this->content->set_type(c_theme_tag::TYPE_CONTENT); + } + + /** + * Class destructor. + */ + public function __destruct() { + unset($this->title); + unset($this->heading); + unset($this->files); + unset($this->context); + unset($this->presentation); + unset($this->content); + } + + /** + * Assign tags to this object. + * + * Tag types must be set prior to this function call. + * + * @param c_theme_tag $tag + * When a c_theme_tag object, the tag is appended to the end of the tags array. + * @param int|null $index + * When an integer, represents the position within the tag array to assign the tag. + * - May not be less than 0 or greater than the length of the array. + * When $tag is NULL, this does nothing. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was added on success. + * When $tag is NULL, TRUE is returned on success. + * FALSE is returned if unable to set tag. + * FALSE with error bit set is returned on error. + */ + public function set_tag($tag, $index = NULL) { + if (!is_object($tag) || !($tag instanceof c_theme_tag)) { + return c_base_return_error::s_false(); + } + + if (!is_null($index) && !is_int($index)) { + return c_base_return_error::s_false(); + } + + $type = $tag->get_type(); + if (!($type instanceof c_base_return_int)) { + return c_base_return_error::s_false(); + } + + $type = $type->get_value_exact(); + if ($type === c_theme_tag::TYPE_TITLE) { + unset($type); + return $this->title->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_HEADING) { + unset($type); + return $this->heading->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_FILES) { + unset($type); + return $this->files->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_CONTEXT) { + unset($type); + return $this->context->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_PRESENTATION) { + unset($type); + return $this->presentation->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_CONTENT) { + unset($type); + return $this->content->set_tag($tag, $index); + } + unset($type); + + return new c_base_return_false(); + } + + /** + * Assign multiple tags to this object. + * + * Tag types must be set prior to this function call. + * + * @param array $tags + * An array of __class__ objects. + * Each individual item is checked and then appended to the end of the array. + * - The array keys will not be preserved. + * + * @param int|null $index + * When an integer, represents the position within the tag array to insert the tags. + * - May not be less than 0 or greater than the length of the array. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was added on success. + * FALSE is returned if unable to set tags. + * FALSE with error bit set is returned on error. + */ + public function set_tags($tags, $index = NULL) { + if (!is_array($tags)) { + return c_base_return_error::s_false(); + } + + if (!is_null($index) && !is_int($index)) { + return c_base_return_error::s_false(); + } + + if (empty($tags)) { + return c_base_return_int::s_new(0); + } + + $total_added = 0; + foreach ($tags as $tag) { + if ($tag instanceof c_theme_tag) { + $type = $tag->get_type(); + if ($type instanceof c_base_return_int) { + $type = $type->get_value_exact(); + if ($type === c_theme_tag::TYPE_TITLE) { + $result = $this->title->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_HEADING) { + $result = $this->heading->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_FILES) { + $result = $this->files->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_CONTEXT) { + $result = $this->context->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_PRESENTATION) { + $result = $this->presentation->set_tag($tag, $index); + } + elseif ($type === c_theme_tag::TYPE_CONTENT) { + $result = $this->content->set_tag($tag, $index); + } + else { + $result = new c_base_return_false(); + } + } + else { + $result = new c_base_return_false(); + } + } + else { + $result = new c_base_return_false(); + } + + // Don't continue if anything goes wrong. + if ($result instanceof c_base_return_false) { + unset($result); + unset($tag); + unset($type); + unset($total_added); + return c_base_return_error::s_false(); + } + + $total_added++; + } + unset($result); + unset($tag); + unset($type); + + return c_base_return_int::s_new($total_added); + } + + /** + * Get a single tag assigned to this object. + * + * @param int $type + * The tag type to load the tag from. + * @param int|null $index + * (optional) An integer representing the position within the tag array to return. + * When NULL, the tag at the end of the tags array is returned. + * + * @return c_base_return_status|c_theme_return_tag|c_base_return_null + * A c_theme_return_tag object on success. + * NULL might be returned if there is no object assigned to the index. + * Otherwise FALSE is returned. + * FALSE with error bit set is returned on error. + * + * @see: self::get_tags() + */ + public function get_tag($type, $index = NULL) { + if (!is_int($type)) { + return c_base_return_error::s_false(); + } + + if ($type === c_theme_tag::TYPE_TITLE) { + return $this->title->get_tag($index); + } + elseif ($type === c_theme_tag::TYPE_HEADING) { + return $this->heading->get_tag($index); + } + elseif ($type === c_theme_tag::TYPE_FILES) { + return $this->files->get_tag($index); + } + elseif ($type === c_theme_tag::TYPE_CONTEXT) { + return $this->context->get_tag($index); + } + elseif ($type === c_theme_tag::TYPE_PRESENTATION) { + return $this->presentation->get_tag($index); + } + elseif ($type === c_theme_tag::TYPE_CONTENT) { + return $this->content->get_tag($index); + } + + return new c_base_return_false(); + } + + /** + * Delete tag from this object. + * + * Tags not stored at the end of the array are set to NULL instead of being deleted. + * The length is therefore not shortened unless the deleted tag is at the end of the array. + * Call $this->sanitize_tags() to ensure that the array structure contains none of these holes. + * + * @param int $type + * The tag type to load the tag from. + * @param int|null $index + * (optional) When an integer, represents the position of the tag to delete. + * - May not be less than 0 or greater than the length of the array. + * When $tag is NULL, deletes the tag at the end of the tag array. + * + * @return c_base_return_status|c_base_return_int + * An integer representing the position the tag was deleted on success. + * Otherwise, FALSE is returned. + * FALSE without error bit set is returned if the tag at the specified index is already deleted. + * FALSE with error bit set is returned on error. + */ + public function delete_tag($type, $index = NULL) { + if (!is_int($type)) { + return c_base_return_error::s_false(); + } + + if ($type === c_theme_tag::TYPE_TITLE) { + return $this->title->delete_tag($index); + } + elseif ($type === c_theme_tag::TYPE_HEADING) { + return $this->heading->delete_tag($index); + } + elseif ($type === c_theme_tag::TYPE_FILES) { + return $this->files->delete_tag($index); + } + elseif ($type === c_theme_tag::TYPE_CONTEXT) { + return $this->context->delete_tag($index); + } + elseif ($type === c_theme_tag::TYPE_PRESENTATION) { + return $this->presentation->delete_tag($index); + } + elseif ($type === c_theme_tag::TYPE_CONTENT) { + return $this->content->delete_tag($index); + } + + return new c_base_return_false(); + } + + /** + * Convert this object and all of its children or text to markup. + * + * @return c_base_return_string + * A string representing this object in the form of HTMl compatible markup. + */ + public function to_markup() { + $markup = ''; + $markup .= ''; + + $markup .= ''; + $markup .= $this->title->to_markup()->get_value_exact(); + $markup .= ''; + + $markup .= ''; + $markup .= $this->heading->to_markup()->get_value_exact(); + $markup .= ''; + + $markup .= ''; + $markup .= $this->files->to_markup()->get_value_exact(); + $markup .= ''; + + $markup .= ''; + $markup .= $this->context->to_markup()->get_value_exact(); + $markup .= ''; + + $markup .= ''; + $markup .= $this->presentation->to_markup()->get_value_exact(); + $markup .= ''; + + $markup .= ''; + $markup .= $this->content->to_markup()->get_value_exact(); + $markup .= ''; + + unset($tag_markup); + $markup .= ''; + + return c_base_return_string::s_new($markup); + } +} + +/** + * A return class whose value is represented as a __class__. + * + * This should be the most commonly used class as it adds some type security over the c_base_return_value class. + */ +class c_theme_return_markup extends c_base_return_value { + use t_base_return_value_exact; + + /** + * @see: t_base_return_value::p_s_new() + */ + public static function s_new($value) { + return self::p_s_new($value, __CLASS__); + } + + /** + * @see: t_base_return_value::p_s_value() + */ + public static function s_value($return) { + return self::p_s_value($return, __CLASS__); + } + + /** + * @see: t_base_return_value_exact::p_s_value_exact() + */ + public static function s_value_exact($return) { + return self::p_s_value_exact($return, __CLASS__, ''); + } + + /** + * Assign the value. + * + * @param __class__ $value + * Any value so long as it is a __class__. + * NULL is not allowed. + * + * @return bool + * TRUE on success, FALSE otherwise. + */ + public function set_value($value) { + if (!$value instanceof c_theme_markup) { + return FALSE; + } + + $this->value = $value; + return TRUE; + } + + /** + * Return the value. + * + * @return string|null $value + * The value array stored within this class. + */ + public function get_value() { + if (!is_null($this->value) && !($this->value instanceof c_theme_markup)) { + $this->value = NULL; + } + + return $this->value; + } + + /** + * Return the value of the expected type. + * + * @return __class__ $value + * The value c_theme_dom stored within this class. + */ + public function get_value_exact() { + if (!($this->value instanceof c_theme_markup)) { + $this->value = new c_theme_tag(); + } + + return $this->value; + } +} diff --git a/common/theme/classes/theme_rss.php b/common/theme/classes/theme_rss.php new file mode 100644 index 0000000..e69de29 diff --git a/documentation/naming.txt b/documentation/naming.txt new file mode 100644 index 0000000..bc8828a --- /dev/null +++ b/documentation/naming.txt @@ -0,0 +1,36 @@ +Functions, Classes, Objects, Traits, and numerous other special types of functionality used by PHP and other languages are used in this project in such a way to provide an obvious and simple way to identify them. + +The project loosely follows the drupal project coding scheme, but one specific exception is "camel casing", which is exclusively denied by this project. +All names used should be lower cased. +Local variables do not have name prefixes such as the ones described below. + +The following is the naming scheme used to communicate what a particular name is: + - f_: This represent a function. + - c_: This represents a class name. + - t_: This represents a class trait. + +Within a class object, special rules apply: + 1) f_ is not prefixed on function names. + 2) p_ is prefixed for private classes and protected classes that are intended to be private (via the use of final in the final class). + 3) s_ is prefixed for static class function names. + 4) All non-private and protected classes intended to be public must not be prefixed with p_. + 5) Common operations will have the following prefixes: + - get_: To load, return, or otherwise obtain some data, such as the value of a variable defined within a class. + - set_: To save, edit, other otherwise alter some data, such as a vlaue of a variable defined within a class. + - push_: Is used to send or write to something that might be remote and is not a variable in this class. + - pull_: Is used to retrieve or load something that might be remote and is not a variable in this class. + 6) Initialization and de-initialization should be used. (This note needs to be moved elsewhere, but was written down here while my mind was on the topic.) + - All classes must unset() all variables during de-initialization. + - This is done to help encourage the freeing of memory for when a garbage collection is performed. + +All uppercase letters represents some form of global variable, be it within a class object or used as a 'define'. + +All functions, classes, etc.. that are part of the API should be grouped in some manner based on their purpose or the best fit purpose. +Each of these overarching purposes should be represented by a single word that can act as a prefix. +This prefix must be applied after the type prefix. + - For example, all basic functionality is grouped by 'base_'. + - A function defined within the base group would therefore be prefixed with: 'f_base_'. + +Sub-group prefixes are also allowed. +To keep things simple, try to keep names and purposes limited to a single sub-group. +If more complexity is needed, uses classes to prevent using more than 1 sub-group prefix. diff --git a/documentation/requirements.txt b/documentation/requirements.txt new file mode 100644 index 0000000..6eadf78 --- /dev/null +++ b/documentation/requirements.txt @@ -0,0 +1,25 @@ +This project is designed for specific software. + +Postgresql: 9.6.0 or later. +- see: https://www.postgresql.org/ +- In general, postgresql is very compatible with itself and rarely has any update issues. +- Any version greater than 9.6.0, such as 9.6.6, 9.7.0, 9.8.52, 10.0.0, 12.2.3, etc.. +- This project uses functionality added as of 9.6.0. + +PHP: 7.1 and any patches. +- see: https://www.php.net/ +- In general, PHP is very incompatible with itself and should use the specified versions. +- This is initially being programmed in 5.6, but is expected to be moved to 7.1. + +PHP Modules: +- mbstring +- intl + +php-lzo: +- see: https://github.com/adsr/php-lzo +- see: http://www.oberhumer.com/opensource/lzo/ +- This must be added to the PHP source code during compile time. + +php-xz: +- see: https://github.com/chobie/php-xz +- This must be added to the PHP source code during compile time.