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.
# 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).
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing system access.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_session.php');
+require_once('common/base/classes/base_ldap.php');
+
+/**
+ * A class for managing roles.
+ *
+ * Roles defined here are general top-level roles used for separating database activity.
+ * The intentions here is to keep the roles as simple and as few as possible while allowing considerable flexibility.
+ * This should cut down on the complexity of the database access control.
+ *
+ * Additional granularity may be supplied via PHP access checks or by extending this class.
+ *
+ * Roles:
+ * - None: no access to anything.
+ * - Public: access to only public information (users who are not logged in have this, such as anonymous).
+ * - System: account is a machine and should not be a human (such as with cron jobs).
+ * - User: account is a user and that user is logged in.
+ * - Requester: account is for requesting something, generally via some sort of form.
+ * - Drafter: account is for making templates, drafts, ideas, etc.. (this is a lesser form of "editer").
+ * - Editer: account is for editors who add/manage/create content.
+ * - Reviewer: account is for users who review something (such as a user who approves content for publishing).
+ * - Publisher: account is for users who perform publishing (marking content available and complete).
+ * - Manager: account is for users who manager the entire system. This is a non-technical administration account.
+ * - Administer: account is for users who have full administrative access to the system. This is a technical administration account and supercedes Manager.
+ */
+class c_base_roles {
+ const NONE = 0;
+ const PUBLIC = 1;
+ const SYSTEM = 2;
+ const USER = 3;
+ const REQUESTER = 4;
+ const DRAFTER = 5;
+ const EDITER = 6;
+ const REVIEWER = 7;
+ const PUBLISHER = 8;
+ const MANAGER = 9;
+ const ADMINISTER = 10;
+
+ private $public;
+ private $system;
+ private $user;
+ private $requester;
+ private $drafter;
+ private $editer;
+ private $reviewer;
+ private $publisher;
+ private $manager;
+ private $administer;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing ASCII.
+ */
+
+/**
+ * A class for managing ASCII.
+ */
+class c_base_ascii {
+ const NULL = 0;
+ const START_OF_HEADER = 1;
+ const START_OF_TEXT = 2;
+ const END_OF_TEXT = 3;
+ const END_OF_TRANSMISSION = 4;
+ const ENQUIRY = 5;
+ const ACKNOWLEDGEMENT = 6;
+ const BELL = 7;
+ const BACKSPACE = 8;
+ const TAB_HORIZONTAL = 9;
+ const NEW_LINE = 10;
+ const TAB_VERTICAL = 11;
+ const FORM_FEED = 12;
+ const CARRIAGE_RETURN = 13;
+ const SHIFT_OUT = 14;
+ const SHIFT_IN = 15;
+ const DATA_LINK_ESCAPE = 16;
+ const DEVICE_CONTROL_1 = 17;
+ const DEVICE_CONTROL_2 = 18;
+ const DEVICE_CONTROL_3 = 19;
+ const DEVICE_CONTROL_4 = 20;
+ const ACKNOWLEDGEMENT_NOT = 21;
+ const SYNCHRONOUS_IDLE = 22;
+ const END_OF_BLOCK = 23;
+ const CANCEL = 24;
+ const END_OF_MEDIUM = 25;
+ const SUBSTITUTE = 26;
+ const ESCAPE = 27;
+ const SEPARATOR_FILE = 28;
+ const SEPARATOR_GROUP = 29;
+ const SEPARATOR_RECORD = 30;
+ const SEPARATOR_UNIT = 31;
+ const SPACE = 32;
+ const EXCLAMATION = 33;
+ const QUOTE_DOUBLE = 34;
+ const HASH = 35;
+ const DOLLAR = 36;
+ const PERCENT = 37;
+ const AMPERSAND = 38;
+ const QUOTE_SINGLE = 39;
+ const PARENTHESIS_OPEN = 40;
+ const PARENTHESIS_CLOSE = 41;
+ const ASTERISK = 42;
+ const PLUS = 43;
+ const COMMA = 44;
+ const MINUS = 45;
+ const PERIOD = 46;
+ const SLASH_FORWARD = 47;
+ const ZERO = 48;
+ const ONE = 49;
+ const TWO = 50;
+ const THREE = 51;
+ const FOUR = 52;
+ const FIVE = 53;
+ const SIX = 54;
+ const SEVEN = 55;
+ const EIGHT = 56;
+ const NINE = 57;
+ const COLON = 58;
+ const COLON_SEMI = 59;
+ const LESS_THAN = 60;
+ const EQUAL = 61;
+ const GREATER_THAN = 62;
+ const QUESTION_MARK = 63;
+ const AT = 64;
+ const UPPER_A = 65;
+ const UPPER_B = 66;
+ const UPPER_C = 67;
+ const UPPER_D = 68;
+ const UPPER_E = 69;
+ const UPPER_F = 70;
+ const UPPER_G = 71;
+ const UPPER_H = 72;
+ const UPPER_I = 73;
+ const UPPER_J = 74;
+ const UPPER_K = 75;
+ const UPPER_L = 76;
+ const UPPER_M = 77;
+ const UPPER_N = 78;
+ const UPPER_O = 79;
+ const UPPER_P = 80;
+ const UPPER_Q = 81;
+ const UPPER_R = 82;
+ const UPPER_S = 83;
+ const UPPER_T = 84;
+ const UPPER_U = 85;
+ const UPPER_V = 86;
+ const UPPER_W = 87;
+ const UPPER_X = 88;
+ const UPPER_Y = 89;
+ const UPPER_Z = 90;
+ const BRACKET_OPEN = 91;
+ const SLASH_BACKWARD = 92;
+ const BRACKET_CLOSE = 93;
+ const CARET = 94;
+ const UNDERSCORE = 95;
+ const GRAVE = 96;
+ const LOWER_A = 97;
+ const LOWER_B = 98;
+ const LOWER_C = 99;
+ const LOWER_D = 100;
+ const LOWER_E = 101;
+ const LOWER_F = 102;
+ const LOWER_G = 103;
+ const LOWER_H = 104;
+ const LOWER_I = 105;
+ const LOWER_J = 106;
+ const LOWER_K = 107;
+ const LOWER_L = 108;
+ const LOWER_M = 109;
+ const LOWER_N = 110;
+ const LOWER_O = 111;
+ const LOWER_P = 112;
+ const LOWER_Q = 113;
+ const LOWER_R = 114;
+ const LOWER_S = 115;
+ const LOWER_T = 116;
+ const LOWER_U = 117;
+ const LOWER_V = 118;
+ const LOWER_W = 119;
+ const LOWER_X = 120;
+ const LOWER_Y = 121;
+ const LOWER_Z = 122;
+ const BRACE_OPEN = 123;
+ const PIPE = 124;
+ const BRACE_CLOSE = 125;
+ const TILDE = 126;
+ const DELETE = 127;
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing common charsets.
+ */
+
+/**
+ * A class for managing common rfc character sets.
+ */
+class c_base_charset {
+ const UNDEFINED = 0;
+ const ASCII = 1;
+ const UTF_8 = 2;
+ const UTF_16 = 3;
+ const UTF_32 = 4;
+ const ISO_8859_1 = 5;
+ const ISO_8859_2 = 6;
+ const ISO_8859_3 = 7;
+ const ISO_8859_4 = 8;
+ const ISO_8859_5 = 9;
+ const ISO_8859_6 = 10;
+ const ISO_8859_7 = 11;
+ const ISO_8859_8 = 12;
+ const ISO_8859_9 = 13;
+ const ISO_8859_10 = 14;
+ const ISO_8859_11 = 15;
+ const ISO_8859_12 = 16;
+ const ISO_8859_13 = 17;
+ const ISO_8859_14 = 18;
+ const ISO_8859_15 = 19;
+ const ISO_8859_16 = 20;
+
+ /**
+ * Determine if the given code is a valid charset code.
+ *
+ * @param int $charset
+ * The integer to validate.
+ *
+ * @return c_base_return_status
+ * TRUE on valid, FALSE otherwise.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_is_valid($charset) {
+ if (!is_int($charset)) {
+ return c_base_return_error::s_false();
+ }
+
+ if ($charset < self::ASCII || $charset > 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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing cookies.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic cookie management class.
+ *
+ * The cookie stored via this class will store the data as an array encoded in json format.
+ *
+ * This class overrides c_base_return_array() such that some of its return values are in a different form than expected.
+ * This will utilize c_base_return_* as return values.
+ *
+ * @see: http://us.php.net/manual/en/features.cookies.php
+ * @see: setcookie()
+ */
+class c_base_cookie extends c_base_return_array {
+ const DEFAULT_LIFETIME = 172800; // 48 hours
+ const DEFAULT_PATH = '/';
+ const CHECKSUM_ALGORITHM = 'sha256';
+
+ private $name;
+ private $secure;
+ private $max_age;
+ private $expires;
+ private $path;
+ private $domain;
+ private $http_only;
+ private $first_only;
+ private $value;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing SQL database communication.
+ *
+ * Following SQL strictly and avoiding non-standard SQL is normally the preferred design.
+ * This is being deviated from for the following reasons:
+ * - SQL fails to define a standard of communication, so connecting to and talking to databases is inconsistent.
+ * - SQL fails to be unclear in certain cases that cause different SQL engines to process the code differently.
+ * - PHP's PDO randomly decides not to support some important functionality with PostgreSQL that it supports across other databases and with non-PDO PHP postgresql code.
+ * - A vast majority of open source projects out there use non-standard MySQL-specific code as a basis, so it is about time that some project optimized to PostgreSQL instead.
+ * - There are numerous cases where MySQL appears to perform better than Postgresql in tests with projects like Drupal.
+ * - This is a flawed logic given that extra, wasteful, code is added to work undo non-standard which will obviously make Postgresql perform slower.
+ * - Much of the advanced functionality of PostgreSQL is going to be used to write a far more secure and well rounded product that other open source databases ever could.
+ * - This project is not like Drupal and others in that it is not designed around PHP and uses SQL, instead, it is designed around SQL and uses PHP.
+ *
+ * One of the particular designs is to use persistent connections as much as possible.
+ * - At any point in time a transaction needs to be performed, do not use the persistent connection, instead create a new connection.
+ * - At any point in time a lock needs to be used, do not use the persistent connection, instead create a new connection.
+ * - For connection re-cycling, persistent connections should be used because they carry over.
+ * - Anonymous connections should in general use persistent.
+ * - Non-anonymous connections should not use persistent connections.
+ * - Non-anonymous connections may keep an anonymous persistent connection for use such as "public preview" of something.
+ * - A reason against persistent connections is the inability to directly close them.
+ * - This is a major weakness and may prevent me from using this persistent connection design (much testing is required).
+ *
+ * @see: http://us.php.net/manual/en/features.persistent-connections.php
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic class for storing and creating a database connection string.
+ *
+ * @see: http://us.php.net/manual/en/function.pg-pconnect.php
+ */
+class c_base_connection_string extends c_base_return_string {
+ const DATA_CLEAR_TEXT_LENGTH = 4096;
+
+ private $host;
+ private $host_addr;
+ private $port;
+ private $dbname;
+ private $user;
+ private $password;
+ private $connect_timeout;
+ private $options;
+ private $ssl_mode;
+ private $service;
+
+ private $error;
+
+ /**
+ * Class destructor.
+ */
+ public function __construct() {
+ $this->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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for performing debugging.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic class for performing debugging.
+ */
+class c_base_debug {
+ private static $ps_debugging = FALSE;
+
+ private $time_start;
+ private $time_stop;
+
+ private $memory_usage_start;
+ private $memory_usage_stop;
+ private $memory_usage_peak;
+ private $memory_allocated_start;
+ private $memory_allocated_stop;
+ private $memory_allocated_peak;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $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;
+ }
+
+ /**
+ * 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);
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing e-mail related functionality.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_ascii.php');
+require_once('common/base/classes/base_utf8.php');
+require_once('common/base/classes/base_rfc_string.php');
+
+/**
+ * A generic class for managing the e-mail related functionality.
+ *
+ * PHP fails to follow the more recent rfc standards at this time.
+ * A custom implementation is provided to handle and process the rfc standards:
+ * - rfc 5322
+ * - rfc 6854
+ * - rfc 7231
+ *
+ * @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
+ *
+ * @require class c_base_rfc_string
+ * @require class c_base_ascii
+ * @require class c_base_utf8
+ */
+class c_base_email extends c_base_rfc_string {
+ const LINE_LENGTH_LIMIT_SOFT = 78;
+ const LINE_LENGTH_LIMIT_HARD = 998;
+
+
+ /**
+ * Decode and check that the given e-mail address is valid.
+ *
+ * Validation is done according to rfc5322, rfc6854, and rfc7231.
+ *
+ * E-mails will be processed in the following approximate manners:
+ * [[name_group]: ["[name_human]"] <[name_machine]@[name_address]>, [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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing return values.
+ */
+
+/*
+ const KERNEL = 0;
+ const USER = 1;
+ const MAIL = 2;
+ const DAEMON = 3;
+ const SECURITY = 4;
+ const MESSAGES = 5;
+ const PRINTER = 6;
+ const NETWORK = 7;
+ const UUCP = 8;
+ const CLOCK = 9;
+ const AUTHORIZATION = 10;
+ const FTP = 11;
+ const NTP = 12;
+ const AUDIT = 13;
+ const ALERT = 14;
+ const CRON = 15;
+ const LOCAL_0 = 16;
+ const LOCAL_1 = 17;
+ const LOCAL_2 = 18;
+ const LOCAL_3 = 19;
+ const LOCAL_4 = 20;
+ const LOCAL_5 = 21;
+ const LOCAL_6 = 22;
+ const LOCAL_7 = 23;
+*/
+
+/**
+ * A generic class for managing errors.
+ *
+ * This class is a dependency of classes provided by base_return.php.
+ * Therefore, it is an exception case to the use of base_return classes as a return value.
+ *
+ * @todo: write this based on my cf_error code.
+ */
+class c_base_error {
+ const EMERGENCY = 0;
+ const ALERT = 1;
+ const CRITICAL = 2;
+ const ERROR = 3;
+ const WARNING = 4;
+ const NOTICE = 5;
+ const INFORMATIONAL = 6;
+ const DEBUG = 7;
+ const UNKNOWN = 8;
+
+ const DEFAULT_BACKTRACE_LIMIT = 4;
+
+ private $name;
+ private $message;
+ private $details;
+ private $severity;
+ private $limit;
+ private $backtrace;
+ private $code;
+ private $reported;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing system forms.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_mime.php');
+require_once('common/base/classes/base_http.php');
+require_once('common/base/classes/base_charset.php');
+require_once('common/base/classes/base_language.php');
+
+// use 'mutable' and have it set to FALSE for server-side only data that cannot be changed client side.
+// - drupal uses 'readonly' and 'disabled', but those are presentation specific, so they must not be used or related to 'mutable' (or in this case, unmutable).
+
+// associated form (sessions) with timestamp, ip address, and agent?
+// generate unique key and/or checksum for each form (and in http header).
+
+/**
+ * A generic class for form attribute types.
+ */
+class c_base_form_attributes {
+ const ATTRIBUTE_NONE = 0;
+ const ATTRIBUTE_ACCESS_KEY = 1; // single letter
+ const ATTRIBUTE_ACCEPT = 2; // c_base_mime integer
+ const ATTRIBUTE_ACTION = 3; // string, URL
+ const ATTRIBUTE_ACTION = 4; // text
+ const ATTRIBUTE_AUTO_COMPLETE = 5; // on or off, use TRUE/FALSE
+ const ATTRIBUTE_AUTO_FOCUS = 6; // autofocus, use TRUE/FALSE
+ const ATTRIBUTE_CLASS = 7; // array of strings
+ const ATTRIBUTE_CHALLENGE = 8; // challenge, use TRUE/FALSE
+ const ATTRIBUTE_CHARACTER_SET = 9; // c_base_charset integer
+ const ATTRIBUTE_CHECKED = 10; // checked, use TRUE/FALSE
+ const ATTRIBUTE_COLUMNS = 11; // number
+ const ATTRIBUTE_CONTENT_EDITABLE = 12; // TRUE, FALSE, INHERIT
+ const ATTRIBUTE_DIRECTION = 13; // ltr, rtl, auto
+ const ATTRIBUTE_DIRECTION_NAME = 14; // text, inputname.dir
+ const ATTRIBUTE_DISABLED = 15; // disabled, use TRUE/FALSE
+ const ATTRIBUTE_ENCODING_TYPE = 16; // c_base_mime integer
+ const ATTRIBUTE_FOR = 17; // text, element id
+ const ATTRIBUTE_FORM = 18; // text, form id
+ const ATTRIBUTE_FORM_ACTION = 19; // text, url
+ const ATTRIBUTE_FORM_ENCODE_TYPE = 20; // c_base_mime integer
+ const ATTRIBUTE_FORM_METHOD = 21; // get or post, use HTTP_METHOD_GET and HTTP_METHOD_POST
+ const ATTRIBUTE_FORM_NO_VALIDATED = 22; // formnovalidate, use TRUE/FALSE
+ const ATTRIBUTE_FORM_TARGET = 23; // text, _blank, _self, _parent, _top, URL
+ const ATTRIBUTE_HIDDEN = 24; // TRUE/FALSE
+ const ATTRIBUTE_KEY_TYPE = 25; // text, rsa, dsa, ec
+ const ATTRIBUTE_LABEL = 26; // text
+ const ATTRIBUTE_LANG = 27; // i_base_language, int
+ const ATTRIBUTE_LIST = 28; // text, datalist_id
+ const ATTRIBUTE_MAXIMUM = 29; // number, date
+ const ATTRIBUTE_MAXIMUM_LENGTH = 30; // number
+ const ATTRIBUTE_MINIMUM = 31; // number, date
+ const ATTRIBUTE_MULTIPLE = 32; // multiple, use TRUE/FALSE
+ const ATTRIBUTE_NAME = 33; // TRUE/FALSE
+ const ATTRIBUTE_NO_VALIDATE = 34; // text
+ const ATTRIBUTE_ON_ABORT = 35; // text
+ const ATTRIBUTE_ON_AFTER_PRINT = 36; // text
+ const ATTRIBUTE_ON_ANIMATION_END = 37; // text
+ const ATTRIBUTE_ON_ANIMATION_ITERATION = 38; // text
+ const ATTRIBUTE_ON_ANIMATION_start = 39; // text
+ const ATTRIBUTE_ON_BEFORE_UNLOAD = 40; // text
+ const ATTRIBUTE_ON_BEFORE_PRINT = 41; // text
+ const ATTRIBUTE_ON_BLUR = 42; // text
+ const ATTRIBUTE_ON_CLICK = 43; // text
+ const ATTRIBUTE_ON_CONTEXT_MENU = 44; // text
+ const ATTRIBUTE_ON_COPY = 45; // text
+ const ATTRIBUTE_ON_CUT = 46; // text
+ const ATTRIBUTE_ON_CAN_PLAY = 47; // text
+ const ATTRIBUTE_ON_CAN_PLAY_THROUGH = 48; // text
+ const ATTRIBUTE_ON_CHANGE = 49; // text
+ const ATTRIBUTE_ON_DOUBLE_CLICK = 50; // text
+ const ATTRIBUTE_ON_DRAG = 51; // text
+ const ATTRIBUTE_ON_DRAG_END = 52; // text
+ const ATTRIBUTE_ON_DRAG_ENTER = 53; // text
+ const ATTRIBUTE_ON_DRAG_LEAVE = 54; // text
+ const ATTRIBUTE_ON_DRAG_OVER = 55; // text
+ const ATTRIBUTE_ON_DRAG_START = 56; // text
+ const ATTRIBUTE_ON_DROP = 57; // text
+ const ATTRIBUTE_ON_DURATION_CHANGE = 58; // text
+ const ATTRIBUTE_ON_ERROR = 59; // text
+ const ATTRIBUTE_ON_EMPTIED = 60; // text
+ const ATTRIBUTE_ON_ENDED = 61; // text
+ const ATTRIBUTE_ON_ERROR = 62; // text
+ const ATTRIBUTE_ON_FOCUS = 63; // text
+ const ATTRIBUTE_ON_FOCUS_IN = 64; // text
+ const ATTRIBUTE_ON_FOCUS_OUT = 65; // text
+ const ATTRIBUTE_ON_HASH_CHANGE = 66; // text
+ const ATTRIBUTE_ON_INPUT = 67; // text
+ const ATTRIBUTE_ON_INVALID = 68; // text
+ const ATTRIBUTE_ON_KEY_DOWN = 69; // text
+ const ATTRIBUTE_ON_KEY_PRESS = 70; // text
+ const ATTRIBUTE_ON_KEY_UP = 71; // text
+ const ATTRIBUTE_ON_LOAD = 72; // text
+ const ATTRIBUTE_ON_LOADED_DATA = 73; // text
+ const ATTRIBUTE_ON_LOADED_META_DATA = 74; // text
+ const ATTRIBUTE_ON_LOAD_START = 75; // text
+ const ATTRIBUTE_ON_MOUSE_DOWN = 76; // text
+ const ATTRIBUTE_ON_MOUSE_ENTER = 77; // text
+ const ATTRIBUTE_ON_MOUSE_LEAVE = 78; // text
+ const ATTRIBUTE_ON_MOUSE_MOVE = 79; // text
+ const ATTRIBUTE_ON_MOUSE_OVER = 80; // text
+ const ATTRIBUTE_ON_MOUSE_OUT = 81; // text
+ const ATTRIBUTE_ON_MOUSE_UP = 82; // text
+ const ATTRIBUTE_ON_MESSAGE = 83; // text
+ const ATTRIBUTE_ON_MOUSE_WHEEL = 84; // text
+ const ATTRIBUTE_ON_OPEN = 85; // text
+ const ATTRIBUTE_ON_ONLINE = 86; // text
+ const ATTRIBUTE_ON_OFFLINE = 87; // text
+ const ATTRIBUTE_ON_PAGE_SHOW = 88; // text
+ const ATTRIBUTE_ON_PAGE_HIDE = 89; // text
+ const ATTRIBUTE_ON_PASTE = 90; // text
+ const ATTRIBUTE_ON_PAUSE = 91; // text
+ const ATTRIBUTE_ON_PLAY = 92; // text
+ const ATTRIBUTE_ON_PLAYING = 93; // text
+ const ATTRIBUTE_ON_PROGRESS = 94; // text
+ const ATTRIBUTE_ON_POP_STATE = 95; // text
+ const ATTRIBUTE_ON_RESIZE = 96; // text
+ const ATTRIBUTE_ON_RESET = 97; // text
+ const ATTRIBUTE_ON_RATE_CHANGE = 98; // text
+ const ATTRIBUTE_ON_SCROLL = 99; // text
+ const ATTRIBUTE_ON_SEARCH = 100; // text
+ const ATTRIBUTE_ON_SELECT = 101; // text
+ const ATTRIBUTE_ON_SUBMIT = 102; // text
+ const ATTRIBUTE_ON_SEEKED = 103; // text
+ const ATTRIBUTE_ON_SEEKING = 104; // text
+ const ATTRIBUTE_ON_STALLED = 105; // text
+ const ATTRIBUTE_ON_SUSPEND = 106; // text
+ const ATTRIBUTE_ON_SHOW = 107; // text
+ const ATTRIBUTE_ON_STORAGE = 108; // text
+ const ATTRIBUTE_ON_TIME_UPDATE = 109; // text
+ const ATTRIBUTE_ON_TRANSITION_END = 110; // text
+ const ATTRIBUTE_ON_TOGGLE = 111; // text
+ const ATTRIBUTE_ON_TOUCH_CANCEL = 112; // text
+ const ATTRIBUTE_ON_TOUCH_END = 113; // text
+ const ATTRIBUTE_ON_TOUCH_MOVE = 114; // text
+ const ATTRIBUTE_ON_TOUCH_START = 115; // text
+ const ATTRIBUTE_ON_UNLOAD = 116; // text
+ const ATTRIBUTE_ON_VOLUME_CHANGE = 117; // text
+ const ATTRIBUTE_ON_WAITING = 118; // text
+ const ATTRIBUTE_ON_WHEEL = 119; // text
+ const ATTRIBUTE_PATTERN = 120; // text, regular expression
+ const ATTRIBUTE_PLACE_HOLDER = 121; // text
+ const ATTRIBUTE_READONLY = 122; // readonly, use TRUE/FALSE
+ const ATTRIBUTE_REQUIRED = 123; // required, use TRUE/FALSE
+ const ATTRIBUTE_ROWS = 124; // number
+ const ATTRIBUTE_SELECTED = 125; // selected, use TRUE/FALSE
+ const ATTRIBUTE_SIZE = 126; // number
+ const ATTRIBUTE_SOURCE = 127; // url
+ const ATTRIBUTE_SPELLCHECK = 125; // TRUE/FALSE
+ const ATTRIBUTE_STEP = 128; // number
+ const ATTRIBUTE_STYLE = 129; // text
+ const ATTRIBUTE_TAB_INDEX = 130; // number
+ const ATTRIBUTE_TARGET = 131; // text, _blank, _self, _parent, _top, URL
+ const ATTRIBUTE_TITLE = 132; // text
+ const ATTRIBUTE_TRANSLATE = 133; // text
+ const ATTRIBUTE_TYPE = 134; // see TYPE_ constanst below.
+ const ATTRIBUTE_VALUE = 135; // text
+ const ATTRIBUTE_WRAP = 136; // hard, soft
+}
+
+/**
+ * A generic class for storing a single item of 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 tag id.
+ * The form id is intended to represent the form tag id or form tag name as used on HTML form id tags.
+ *
+ */
+class c_base_form_data_item {
+
+
+/**
+ * A generic class for form tag types.
+ *
+ * @see: https://www.w3.org/TR/html5/forms.html#forms
+ */
+class c_base_form_tags {
+ const TYPE_NONE = 0;
+ const TYPE_TEXT = 1;
+ const TYPE_BOOLEAN = 2;
+ const TYPE_INTEGER = 3;
+ const TYPE_FLOAT = 4;
+
+ private $id_internal = NULL;
+ private $id_form = NULL;
+ private $type = self::TYPE_NONE;
+ private $data = NULL;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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);
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing HTML5 Markup.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ *
+ * @see: https://www.w3.org/TR/html5/
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic class for HTML tag attributes.
+ *
+ * This is for the internal storage of the attributes and not external.
+ * Externally, every attribute type is a string.
+ * The internal uses PHP structures where easily possible.
+ * Special case string values, such as percentage symbols, are not used internally.
+ *
+ * @todo: should the class name include internal?
+ * @todo: should external be in the html output class?
+ * @todo: what about form processing/validation (which has external values)?
+ * ---- Above is old comments to be reviewed. ----
+ *
+ * A generic class for HTML tag attributes.
+ * This should accept and handle all
+ *
+ * @see: https://www.w3.org/TR/html5/forms.html#forms
+ */
+class c_base_html_attribute_values {
+ const TYPE_NONE = 0;
+ const TYPE_BOOLEAN = 1; // https://www.w3.org/TR/html5/infrastructure.html#boolean-attributes
+ const TYPE_ENUMERATED = 2; // https://www.w3.org/TR/html5/infrastructure.html#keywords-and-enumerated-attributes
+ const TYPE_NUMBER = 3; // https://www.w3.org/TR/html5/infrastructure.html#numbers
+ const TYPE_NUMBER_SIGNED = 4; // https://www.w3.org/TR/html5/infrastructure.html#signed-integers
+ const TYPE_NUMBER_UNSIGNED = 5; // https://www.w3.org/TR/html5/infrastructure.html#non-negative-integers
+ const TYPE_NUMBER_FLOAT = 6; // https://www.w3.org/TR/html5/infrastructure.html#floating-point-numbers
+ const TYPE_NUMBER_DIMENSION = 7; // https://www.w3.org/TR/html5/infrastructure.html#percentages-and-dimensions
+ const TYPE_NUMBER_LIST = 8; // https://www.w3.org/TR/html5/infrastructure.html#lists-of-integers
+ const TYPE_NUMBER_DIMENSION_LIST = 9; // https://www.w3.org/TR/html5/infrastructure.html#lists-of-dimensions
+ const TYPE_DATE = 10; // https://www.w3.org/TR/html5/infrastructure.html#dates-and-times
+ const TYPE_DATE_MONTH = 11; // https://www.w3.org/TR/html5/infrastructure.html#months
+ const TYPE_DATE_DATES = 12; // https://www.w3.org/TR/html5/infrastructure.html#dates
+ const TYPE_DATE_DATES_YEARLESS = 13; // https://www.w3.org/TR/html5/infrastructure.html#yearless-dates
+ const TYPE_DATE_TIMES = 14; // https://www.w3.org/TR/html5/infrastructure.html#times
+ const TYPE_DATE_DATES_FLOATING = 15; // https://www.w3.org/TR/html5/infrastructure.html#floating-dates-and-times
+ const TYPE_DATE_TIMEZONE = 16; // https://www.w3.org/TR/html5/infrastructure.html#time-zones
+ const TYPE_DATE_GLOBAL = 17; // https://www.w3.org/TR/html5/infrastructure.html#global-dates-and-times
+ const TYPE_DATE_WEEKS = 18; // https://www.w3.org/TR/html5/infrastructure.html#weeks
+ const TYPE_DATE_DURATION = 19; // https://www.w3.org/TR/html5/infrastructure.html#durations
+ const TYPE_DATE_VAGUE = 20; // https://www.w3.org/TR/html5/infrastructure.html#vaguer-moments-in-time
+ const TYPE_COLOR = 21; // https://www.w3.org/TR/html5/infrastructure.html#colors
+ const TYPE_TOKENS_SPACE = 22; // https://www.w3.org/TR/html5/infrastructure.html#space-separated-tokenss
+ const TYPE_TOKENS_COMMA = 23; // https://www.w3.org/TR/html5/infrastructure.html#comma-separated-tokens
+ const TYPE_REFERENCE = 24; // https://www.w3.org/TR/html5/infrastructure.html#syntax-references
+ const TYPE_MEDIA = 25; // https://www.w3.org/TR/html5/infrastructure.html#mq
+ const TYPE_URL = 26; // https://www.w3.org/TR/html5/infrastructure.html#urls
+
+ const VALUE_NONE = 0;
+ const VALUE_TRUE = 1;
+ const VALUE_FALSE = 2;
+ const VALUE_INHERITED = 3;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ // do nothing.
+ }
+
+ /**
+ * Class destructor.
+ */
+ public function __destruct() {
+ // do nothing.
+ }
+
+ /**
+ * Validate that value is a boolean.
+ *
+ * @param int $value
+ * The value to validate.
+ * @param bool $include_inherited
+ * (optional) When TRUE, the "Inherited" state is supported.
+ *
+ * @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#boolean-attributes
+ */
+ public static function is_boolean($value, $include_inherited = FALSE) {
+ if (!is_int($value)) {
+ return new c_base_return_false();
+ }
+
+ if (!is_bool($include_inherited)) {
+ return c_base_return_error::s_false();
+ }
+
+ switch ($value) {
+ self::VALUE_NONE:
+ self::VALUE_TRUE:
+ self::VALUE_FALSE:
+ return new c_base_return_true();
+ self::VALUE_INHERITED:
+ if ($include_inherited) {
+ return new c_base_return_true();
+ }
+ break;
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Validate that value is an enumerated (or keyword) value.
+ *
+ * @param string $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#keywords-and-enumerated-attributes
+ */
+ public static function is_enumerated($value) {
+ if (is_string($value)) {
+ // an enumerated (or keyword) value is simply a fancy name for string/text.
+ return new c_base_return_true();
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Validate that value is a number value.
+ *
+ * @param int $value
+ * The value to validate.
+ * @param bool $as_unsigned
+ * (optional) Wnen TRUE, number is treated as unsigned.
+ * When FALSE, number is treated as signed.
+ *
+ * @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#numbers
+ * @see: https://www.w3.org/TR/html5/infrastructure.html#non-negative-integers
+ * @see: https://www.w3.org/TR/html5/infrastructure.html#time-zones
+ * @see: https://www.w3.org/TR/html5/infrastructure.html#global-dates-and-times
+ * @see: https://www.w3.org/TR/html5/infrastructure.html#durations
+ * @see: https://www.w3.org/TR/html5/infrastructure.html#colors
+ */
+ public static function is_number($value, $as_unsigned = FALSE) {
+ if (!is_bool($as_unsigned)) {
+ return c_base_return_error::s_false();
+ }
+
+ if ($as_unsigned) {
+ if (is_int($value)) {
+ if ($value >= 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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing the HTTP protocol.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_charset.php');
+require_once('common/base/classes/base_rfc_string.php');
+require_once('common/base/classes/base_utf8.php');
+require_once('common/base/classes/base_email.php');
+require_once('common/base/classes/base_languages.php');
+require_once('common/base/classes/base_http_status.php');
+require_once('common/base/classes/base_cookie.php');
+require_once('common/base/classes/base_mime.php');
+
+/**
+ * A generic class for managing the HTTP protocol.
+ *
+ * @see: http://www.iana.org/assignments/message-headers/message-headers.xhtml
+ *
+ * @require class base_email
+ * @require class base_rfc_string
+ * @require class base_utf8
+ */
+class c_base_http extends c_base_rfc_string {
+ // standard request headers
+ const REQUEST_ACCEPT = 1;
+ const REQUEST_ACCEPT_CHARSET = 2;
+ const REQUEST_ACCEPT_ENCODING = 3;
+ const REQUEST_ACCEPT_LANGUAGE = 4;
+ const REQUEST_ACCEPT_DATETIME = 5;
+ const REQUEST_ACCESS_CONTROL_REQUEST_METHOD = 6;
+ const REQUEST_ACCESS_CONTROL_REQUEST_HEADERS = 7;
+ const REQUEST_AUTHORIZATION = 8;
+ const REQUEST_CACHE_CONTROL = 9;
+ const REQUEST_CONNECTION = 10;
+ const REQUEST_COOKIE = 11;
+ const REQUEST_CONTENT_LENGTH = 12;
+ const REQUEST_CONTENT_TYPE = 13;
+ const REQUEST_DATE = 14;
+ const REQUEST_EXPECT = 15;
+ const REQUEST_FROM = 16;
+ const REQUEST_HOST = 17;
+ const REQUEST_IF_MATCH = 18;
+ const REQUEST_IF_MODIFIED_SINCE = 19;
+ const REQUEST_IF_NONE_MATCH = 20;
+ const REQUEST_IF_RANGE = 21;
+ const REQUEST_IF_UNMODIFIED_SINCE = 22;
+ const REQUEST_MAX_FORWARDS = 23;
+ const REQUEST_ORIGIN = 24;
+ const REQUEST_PRAGMA = 25;
+ const REQUEST_PROXY_AUTHORIZATION = 26;
+ const REQUEST_RANGE = 27;
+ const REQUEST_REFERER = 28;
+ const REQUEST_TE = 29;
+ const REQUEST_USER_AGENT = 30;
+ const REQUEST_UPGRADE = 31;
+ const REQUEST_VIA = 32;
+ const REQUEST_WARNING = 33;
+ const REQUEST_UNKNOWN = 999;
+
+ // non-standard, but supported, request headers
+ const REQUEST_X_REQUESTED_WITH = 1001;
+ const REQUEST_X_FORWARDED_FOR = 1002;
+ const REQUEST_X_FORWARDED_HOST = 1003;
+ const REQUEST_X_FORWARDED_PROTO = 1004;
+ const REQUEST_CHECKSUM_HEADER = 1005;
+ const REQUEST_CHECKSUM_HEADERS = 1006;
+ const REQUEST_CHECKSUM_CONTENT = 1007;
+ const REQUEST_CONTENT_ENCODING = 1008;
+ const REQUEST_SIGNATURE_PG = 1009;
+
+ // standard response headers
+ const RESPONSE_ACCESS_CONTROL_ALLOW_ORIGIN = 1;
+ const RESPONSE_ACCESS_CONTROL_ALLOW_CREDENTIALS = 2;
+ const RESPONSE_ACCESS_CONTROL_EXPOSE_HEADERS = 3;
+ const RESPONSE_ACCESS_CONTROL_MAX_AGE = 4;
+ const RESPONSE_ACCESS_CONTROL_ALLOW_METHODS = 5;
+ const RESPONSE_ACCESS_CONTROL_ALLOW_HEADERS = 6;
+ const RESPONSE_ACCEPT_PATCH = 7;
+ const RESPONSE_ACCEPT_RANGES = 8;
+ const RESPONSE_AGE = 9;
+ const RESPONSE_ALLOW = 10;
+ const RESPONSE_CACHE_CONTROL = 11;
+ const RESPONSE_CONNECTION = 12;
+ const RESPONSE_CONTENT_DISPOSITION = 13;
+ const RESPONSE_CONTENT_ENCODING = 14;
+ const RESPONSE_CONTENT_LANGUAGE = 15;
+ const RESPONSE_CONTENT_LENGTH = 16;
+ const RESPONSE_CONTENT_LOCATION = 17;
+ const RESPONSE_CONTENT_RANGE = 18;
+ const RESPONSE_CONTENT_TYPE = 19;
+ const RESPONSE_DATE = 20;
+ const RESPONSE_ETAG = 21;
+ const RESPONSE_EXPIRES = 22;
+ const RESPONSE_LAST_MODIFIED = 23;
+ const RESPONSE_LINK = 24;
+ const RESPONSE_LOCATION = 25;
+ const RESPONSE_PRAGMA = 26;
+ const RESPONSE_PROXY_AUTHENTICATE = 27;
+ const RESPONSE_PUBLIC_KEY_PINS = 28;
+ const RESPONSE_RETRY_AFTER = 29;
+ const RESPONSE_SERVER = 30;
+ const RESPONSE_SET_COOKIE = 31;
+ const RESPONSE_STATUS = 32;
+ const RESPONSE_STRICT_TRANSPORT_SECURITY = 33;
+ const RESPONSE_TRAILER = 34;
+ const RESPONSE_TRANSFER_ENCODING = 35;
+ const RESPONSE_UPGRADE = 36;
+ const RESPONSE_VARY = 37;
+ const RESPONSE_WARNING = 38;
+ const RESPONSE_WWW_AUTHENTICATE = 39;
+ const RESPONSE_PROTOCOL = 40;
+
+ // non-standard, but supported, response headers.
+ const RESPONSE_REFRESH = 1001;
+ const RESPONSE_X_CONTENT_SECURITY_POLICY = 1002;
+ const RESPONSE_X_CONTENT_TYPE_OPTIONS = 1003;
+ const RESPONSE_X_UA_COMPATIBLE = 1004;
+ const RESPONSE_CHECKSUM_HEADER = 1005;
+ const RESPONSE_CHECKSUM_HEADERS = 1006;
+ const RESPONSE_CHECKSUM_CONTENT = 1007;
+ const RESPONSE_CONTENT_REVISION = 1008;
+ const RESPONSE_DATE_ACTUAL = 1009;
+
+ // accept delimiters (the syntax for the separators can be confusing and misleading)
+ const DELIMITER_ACCEPT_SUP = ',';
+ const DELIMITER_ACCEPT_SUB = ';';
+ const DELIMITER_ACCEPT_SUB_0 = 'q';
+ const DELIMITER_ACCEPT_SUB_1 = '=';
+
+ const ACCEPT_LANGUAGE_CLASS_DEFAULT = 'c_base_language_us_limited';
+
+ // cache control options
+ const CACHE_CONTROL_NO_CACHE = 1;
+ const CACHE_CONTROL_NO_STORE = 2;
+ const CACHE_CONTROL_NO_TRANSFORM = 3;
+ const CACHE_CONTROL_MAX_AGE = 4;
+ const CACHE_CONTROL_MAX_AGE_S = 5;
+ const CACHE_CONTROL_MAX_STALE = 6;
+ const CACHE_CONTROL_MIN_FRESH = 7;
+ const CACHE_CONTROL_ONLY_IF_CACHED = 8;
+ const CACHE_CONTROL_PUBLIC = 9;
+ const CACHE_CONTROL_PRIVATE = 10;
+ const CACHE_CONTROL_MUST_REVALIDATE = 11;
+ const CACHE_CONTROL_PROXY_REVALIDATE = 12;
+
+ // supported checksums
+ const CHECKSUM_MD2 = 1;
+ const CHECKSUM_MD4 = 2;
+ const CHECKSUM_MD5 = 3;
+ const CHECKSUM_SHA1 = 4;
+ const CHECKSUM_SHA224 = 5;
+ const CHECKSUM_SHA256 = 6;
+ const CHECKSUM_SHA384 = 7;
+ const CHECKSUM_SHA512 = 8;
+ const CHECKSUM_CRC32 = 9;
+ const CHECKSUM_PG = 10; // such as: GPG or PGP.
+
+ // checksum actions
+ const CHECKSUM_ACTION_NONE = 0;
+ const CHECKSUM_ACTION_AUTO = 1;
+ const CHECKSUM_ACTION_MANUAL = 2;
+
+ // checksum whats
+ const CHECKSUM_WHAT_FULL = 1;
+ const CHECKSUM_WHAT_PARTIAL = 2;
+ const CHECKSUM_WHAT_SIGNED = 3;
+ const CHECKSUM_WHAT_UNSIGNED = 4;
+
+ // uri path types
+ const URI_PATH_SITE = 1; // such as: '//example.com/main/index.html'
+ const URI_PATH_BASE = 2; // such as: '/main/index.html'
+ const URI_PATH_THIS = 3; // such as: 'index.html'
+
+ // uri host ip addresses
+ const URI_HOST_IPV4 = 1;
+ const URI_HOST_IPV6 = 2;
+ const URI_HOST_IPVX = 3;
+ const URI_HOST_NAME = 4;
+
+ // transfer encoding choices
+ const ENCODING_CHUNKED = 1;
+ const ENCODING_COMPRESS = 2;
+ const ENCODING_DEFLATE = 3;
+ const ENCODING_GZIP = 4; // Compression Options: -1 -> 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.
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing the HTTP protocol status codes.
+ */
+
+/**
+ * A generic class for managing the HTTP protocol status codes.
+ *
+ * @see: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ */
+class c_base_http_status {
+ const UNDEFINED = 0;
+ const INVALID = 1;
+ const UNKNOWN = 2;
+
+ const CONTINUE_REQUEST = 100; // https://tools.ietf.org/html/rfc7231#section-6.2.1 (cannot use "CONTINUE" here because it is reserved by PHP.)
+ const SWITCHING_PROTOCOLS = 101; // https://tools.ietf.org/html/rfc7231#section-6.2.2
+ const PROCESSING = 102;
+
+ const OK = 200; // https://tools.ietf.org/html/rfc7231#section-6.3.1
+ const CREATED = 201; // https://tools.ietf.org/html/rfc7231#section-6.3.2
+ const ACCEPTED = 202; // https://tools.ietf.org/html/rfc7231#section-6.3.3
+ const NON_AUTHORATATIVE = 203; // https://tools.ietf.org/html/rfc7231#section-6.3.4
+ const NO_CONTENT = 204; // https://tools.ietf.org/html/rfc7231#section-6.3.5
+ const RESET_CONTENT = 205; // https://tools.ietf.org/html/rfc7231#section-6.3.6
+ const PARTIAL_CONTENT = 206; // https://tools.ietf.org/html/rfc7233#section-4.1
+ const MULTI_STATUS = 207;
+ const ALREADY_REPORTED = 208;
+ const IM_USED = 209;
+
+ const MULTIPLE_CHOICES = 300; // https://tools.ietf.org/html/rfc7231#section-6.4.1
+ const MOVED_PERMANENTLY = 301; // https://tools.ietf.org/html/rfc7231#section-6.4.2
+ const FOUND = 302; // https://tools.ietf.org/html/rfc7231#section-6.4.3
+ const SEE_OTHER = 303; // https://tools.ietf.org/html/rfc7231#section-6.4.4
+ const NOT_MODIFIED = 304; // https://tools.ietf.org/html/rfc7232#section-4.1
+ const USE_PROXY = 305; // https://tools.ietf.org/html/rfc7231#section-6.4.5
+ const SWITCH_PROXY = 306;
+ const TEMPORARY_REDIRECT = 307; // https://tools.ietf.org/html/rfc7231#section-6.4.7
+ const PERMANENT_REDIRECT = 308;
+
+ const BAD_REQUEST = 400; // https://tools.ietf.org/html/rfc7231#section-6.5.1
+ const UNAUTHORIZED = 401; // https://tools.ietf.org/html/rfc7235#section-3.1
+ const PAYMENT_REQUIRED = 402; // https://tools.ietf.org/html/rfc7231#section-6.5.2
+ const FORBIDDEN = 403; // https://tools.ietf.org/html/rfc7231#section-6.5.3
+ const NOT_FOUND = 404; // https://tools.ietf.org/html/rfc7231#section-6.5.4
+ const METHOD_NOT_ALLOWED = 405; // https://tools.ietf.org/html/rfc7231#section-6.5.5
+ const NOT_ACCEPTABLE = 406; // https://tools.ietf.org/html/rfc7231#section-6.5.6
+ const PROXY_AUTHENTICATION_REQUIRED = 407; // https://tools.ietf.org/html/rfc7235#section-3.2
+ const REQUEST_TIMEOUT = 408; // https://tools.ietf.org/html/rfc7231#section-6.5.7
+ const CONFLICT = 409; // https://tools.ietf.org/html/rfc7231#section-6.5.8
+ const GONE = 410; // https://tools.ietf.org/html/rfc7231#section-6.5.9
+ const LENGTH_REQUIRED = 411; // https://tools.ietf.org/html/rfc7231#section-6.5.10
+ const PRECONDITION_FAILED = 412; // https://tools.ietf.org/html/rfc7232#section-4.2
+ const PAYLOAD_TOO_LARGE = 413; // https://tools.ietf.org/html/rfc7231#section-6.5.11
+ const REQUEST_URI_TOO_LONG = 414; // https://tools.ietf.org/html/rfc7231#section-6.5.12
+ const UNSUPPORTED_MEDIA_TYPE = 415; // https://tools.ietf.org/html/rfc7231#section-6.5.13
+ const REQUESTED_RANGE_NOT_SATISFIABLE = 416; // https://tools.ietf.org/html/rfc7233#section-4.4
+ const EXPECTATION_FAILED = 417; // https://tools.ietf.org/html/rfc7231#section-6.5.14
+ const MISDIRECTED_REQUEST = 422;
+ const LOCKED = 423;
+ const FAILED_DEPENDENCY = 424;
+ const UPGRADE_REQUIRED = 426; // https://tools.ietf.org/html/rfc7231#section-6.5.15
+ const PRECONDITION_REQUIRED = 428;
+ const TOO_MANY_REQUESTS = 429;
+ const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
+ const UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+
+ const INTERNAL_SERVER_ERROR = 500; // https://tools.ietf.org/html/rfc7231#section-6.6.1
+ const NOT_IMPLEMENTED = 501; // https://tools.ietf.org/html/rfc7231#section-6.6.2
+ const BAD_GATEWAY = 502; // https://tools.ietf.org/html/rfc7231#section-6.6.3
+ const SERVICE_UNAVAILABLE = 503; // https://tools.ietf.org/html/rfc7231#section-6.6.4
+ const GATEWAY_TIMEOUT = 504; // https://tools.ietf.org/html/rfc7231#section-6.6.5
+ const HTTP_VERSION_NOT_SUPPORTED = 505; // https://tools.ietf.org/html/rfc7231#section-6.6.6
+ const VARIANT_ALSO_NEGOTIATES = 506;
+ const INSUFFICIENT_STORAGE = 507;
+ const LOOP_DETECTED = 508;
+ const NOT_EXTENDED = 510;
+ const NETWORK_AUTHENTICATION_REQUIRED = 511;
+
+
+ /**
+ * Convert the given status code into a text statement.
+ *
+ * @param int $status
+ * The code to convert
+ *
+ * @return c_base_return_string|c_base_return_status
+ * The status text string on success, FALSE otherwise.
+ * FALSE with the error bit set is returned on error.
+ */
+ public static function to_text($status) {
+ if (!is_int($status)) {
+ return c_base_return_error::s_false();
+ }
+
+ $string = "";
+ switch ($status) {
+ case self::CONTINUE_REQUEST:
+ $string = "Continue Request";
+ break;
+
+ case self::SWITCHING_PROTOCOLS:
+ $string = "Switching Protocols";
+ break;
+
+ case self::PROCESSING:
+ $string = "Processing";
+ break;
+
+ case self::OK:
+ $string = "OK";
+ break;
+
+ case self::CREATED:
+ $string = "Created";
+ break;
+
+ case self::ACCEPTED:
+ $string = "Accepted";
+ break;
+
+ case self::NON_AUTHORATATIVE:
+ $string = "Non-Authoratative";
+ break;
+
+ case self::NO_CONTENT:
+ $string = "No Content";
+ break;
+
+ case self::RESET_CONTENT:
+ $string = "Reset Content";
+ break;
+
+ case self::PARTIAL_CONTENT:
+ $string = "Partial Content";
+ break;
+
+ case self::MULTI_STATUS:
+ $string = "Multi-Status";
+ break;
+
+ case self::ALREADY_REPORTED:
+ $string = "Already Reported";
+ break;
+
+ case self::IM_USED:
+ $string = "IM Used";
+ break;
+
+ case self::MULTIPLE_CHOICES:
+ $string = "Multiple Choices";
+ break;
+
+ case self::MOVED_PERMANENTLY:
+ $string = "Moved Permanently";
+ break;
+
+ case self::FOUND:
+ $string = "Found";
+ break;
+
+ case self::SEE_OTHER:
+ $string = "See Other";
+ break;
+
+ case self::NOT_MODIFIED:
+ $string = "Not Modified";
+ break;
+
+ case self::USE_PROXY:
+ $string = "Use Proxy";
+ break;
+
+ case self::SWITCH_PROXY:
+ $string = "Switch Proxy";
+ break;
+
+ case self::TEMPORARY_REDIRECT:
+ $string = "Temporary Redirect";
+ break;
+
+ case self::PERMANENT_REDIRECT:
+ $string = "Permanent Redirect";
+ break;
+
+ case self::BAD_REQUEST:
+ $string = "Bad Request";
+ break;
+
+ case self::UNAUTHORIZED:
+ $string = "Unauthorized";
+ break;
+
+ case self::PAYMENT_REQUIRED:
+ $string = "Payment Required";
+ break;
+
+ case self::FORBIDDEN:
+ $string = "Forbidden";
+ break;
+
+ case self::NOT_FOUND:
+ $string = "Not Found";
+ break;
+
+ case self::METHOD_NOT_ALLOWED:
+ $string = "Method Not Allowed";
+ break;
+
+ case self::NOT_ACCEPTABLE:
+ $string = "Not Acceptable";
+ break;
+
+ case self::PROXY_AUTHENTICATION_REQUIRED:
+ $string = "Proxy Authentication Required";
+ break;
+
+ case self::REQUEST_TIMEOUT:
+ $string = "Request Timeout";
+ break;
+
+ case self::CONFLICT:
+ $string = "Conflict";
+ break;
+
+ case self::GONE:
+ $string = "Gone";
+ break;
+
+ case self::LENGTH_REQUIRED:
+ $string = "Length Required";
+ break;
+
+ case self::PRECONDITION_FAILED:
+ $string = "Pre-condition Failed";
+ break;
+
+ case self::PAYLOAD_TOO_LARGE:
+ $string = "Payload Too Large";
+ break;
+
+ case self::REQUEST_URI_TOO_LONG:
+ $string = "Request URI Too Long";
+ break;
+
+ case self::UNSUPPORTED_MEDIA_TYPE:
+ $string = "Unsupported Media Type";
+ break;
+
+ case self::REQUESTED_RANGE_NOT_SATISFIABLE:
+ $string = "Requested Range Not Satisfiable";
+ break;
+
+ case self::EXPECTATION_FAILED:
+ $string = "Expectation Failed";
+ break;
+
+ case self::MISDIRECTED_REQUEST:
+ $string = "Misdirected Request";
+ break;
+
+ case self::LOCKED:
+ $string = "Locked";
+ break;
+
+ case self::FAILED_DEPENDENCY:
+ $string = "Failed Dependency";
+ break;
+
+ case self::UPGRADE_REQUIRED:
+ $string = "Upgrade Required";
+ break;
+
+ case self::PRECONDITION_REQUIRED:
+ $string = "Pre-Condition Required";
+ break;
+
+ case self::TOO_MANY_REQUESTS:
+ $string = "Too Many Requests";
+ break;
+
+ case self::REQUEST_HEADER_FIELDS_TOO_LARGE:
+ $string = "Request Header Fields Too Large";
+ break;
+
+ case self::UNAVAILABLE_FOR_LEGAL_REASONS:
+ $string = "Unavailable for Legal Reasons";
+ break;
+
+ case self::INTERNAL_SERVER_ERROR:
+ $string = "Internal Server Error";
+ break;
+
+ case self::NOT_IMPLEMENTED:
+ $string = "Not Implemented";
+ break;
+
+ case self::BAD_GATEWAY:
+ $string = "Bad Gateway";
+ break;
+
+ case self::SERVICE_UNAVAILABLE:
+ $string = "Service Unavailable";
+ break;
+
+ case self::GATEWAY_TIMEOUT:
+ $string = "Gateway Timeout";
+ break;
+
+ case self::HTTP_VERSION_NOT_SUPPORTED:
+ $string = "HTTP Version Not Supported";
+ break;
+
+ case self::VARIANT_ALSO_NEGOTIATES:
+ $string = "Variant Also Negotiates";
+ break;
+
+ case self::INSUFFICIENT_STORAGE:
+ $string = "Unsufficient Storage";
+ break;
+
+ case self::LOOP_DETECTED:
+ $string = "Loop Detected";
+ break;
+
+ case self::NOT_EXTENDED:
+ $string = "Not Extended";
+ break;
+
+ case self::NETWORK_AUTHENTICATION_REQUIRED:
+ $string = "Network Authentication Required";
+ break;
+
+ case self::UNDEFINED:
+ case self::UNKNOWN:
+ $string = "";
+ break;
+
+ case self::INVALID:
+ // invalid will not be processed because it is invalid.
+
+ default:
+ return c_base_return_false();
+ }
+
+ return c_base_return_string::s_new($string);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing the different supported languages.
+ */
+
+/**
+ * A generic interface for managing the different supported languages.
+ *
+ * Additional known sub-languages, such as en-us, are added even though they do not appear in the iso standard.
+ *
+ * @see: http://www.loc.gov/standards/iso639-2/php/code_list.php
+ */
+interface i_base_language {
+ const AFAR = 1; // aar, aa
+ const ABKHAZIAN = 2; // abk, ab
+ const ACHINESE = 3; // ace
+ const ACOLI = 4; // ach
+ const ADANGME = 5; // ada
+ const ADYGHE = 6; // ady
+ const AFRO_ASIATIC = 7; // afa
+ const AFRIHILI = 8; // afh
+ const AFRIKAANS = 9; // afr, af
+ const AINU = 10; // ain
+ const AKAN = 11; // aka, ak
+ const AKKADIAN = 12; // akk
+ const ALBANIAN = 13; // alb (b), sqi (t), sq
+ const ALEUT = 14; // ale
+ const ALGONQUIAN = 15; // alg
+ const SOUTHERN_ALTAI = 16; // alt
+ const AMHARIC = 17; // amh, am
+ const ENGLISH_OLD = 18; // ang
+ const ANGIKA = 19; // anp
+ const APACHE = 20; // apa
+ const ARABIC = 21; // ara, ar
+ const ARAMAIC = 22; // arc
+ const ARAGONESE = 23; // arg, an
+ const ARMENIAN = 24; // arm (b), hye (t), hy
+ const MAPUDUNGUN = 25; // am
+ const ARAPAHO = 26; // arp
+ const ARTIFICIAL = 27; // art
+ const ARAWAK = 28; // arw
+ const ASSAMESE = 29; // asm, as
+ const ASTURIAN = 30; // ast
+ const ATHAPASCAN = 31; // ath
+ const AUSTRALIAN = 32; // aus
+ const AVARIC = 33; // ava, av
+ const AVESTAN = 34; // ave, ae
+ const AWADHI = 35; // awa
+ const AYMARA = 36; // aym, ay
+ const AZERBAIJANI = 37; // aze, az
+ const BANDA = 38; // bad
+ const BAMILEKE = 39; // bai
+ const BASHKIR = 40; // bak, ba
+ const BALUCHI = 41; // bal
+ const BAMBARA = 42; // bam, bm
+ const BALINESE = 43; // ban
+ const BASQUE = 44; // baq (b), eus (t), eu
+ const BASA = 45; // bas
+ const BALTIC = 46; // bat
+ const BEJA = 47; // bej
+ const BELARUSIAN = 48; // bel, be
+ const BEMBA = 49; // bem
+ const BENGALI = 50; // ben, bn
+ const BERBER = 51; // ber
+ const BHOJPURI = 52; // bho
+ const BIHARI = 53; // bih, bh
+ const BIKOL = 54; // bik
+ const BINI = 55; // bin
+ const BISLAMA = 56; // bis, bi
+ const SIKSIKA = 57; // bla
+ const BANTU = 58; // bnt
+ const TIBETAN = 59; // tib (b), bod (t), bo
+ const BOSNIAN = 60; // bos, bs
+ const BRAJ = 61; // bra
+ const BRETON = 62; // bre
+ const BATAK = 63; // btk
+ const BURIAT = 64; // bua
+ const BUGINESE = 65; // bug
+ const BULGARIAN = 66; // bul
+ const BURMESE = 67; // bur (b), mya (t), my
+ const BLIN = 68; // byn
+ const CADDO = 69; // cad
+ const AMERICAN_INDIAN_CENTRAL = 70; // cai
+ const GALIBI_CARIB = 71; // car
+ const CATALAN = 72; // cat, ca
+ const CAUCASIAN = 73; // cau
+ const CEBUANO = 74; // ceb
+ const CELTIC = 75; // cel
+ const CZECH = 76; // cze (b), ces (t), cs
+ const CHAMORRO = 77; // cha, ch
+ const CHIBCHA = 78; // chb
+ const CHECHEN = 79; // che, ce
+ const CHAGATAI = 80; // chg
+ const CHINESE = 81; // chi (b), zho (t), zh
+ const CHUUKESE = 82; // chk
+ const MARI = 83; // chm
+ const CHINOOK_JARGON = 84; // chn
+ const CHOCTAW = 85; // cho
+ const CHIPEWYAN = 86; // chp
+ const CHEROKEE = 87; // chr
+ const CHURCH_SLAVIC = 88; // chu, cu
+ const CHUVASH = 89; // chv, cv
+ const CHEYENNE = 90; // chy
+ const CHAMIC = 91; // cmc
+ const COPTIC = 92; // cop
+ const CORNISH = 93; // cor
+ const CORSICAN = 94; // cos, co
+ const CREOLES_ENGLISH = 95; // cpe
+ const CREOLES_FRENCH = 96; // cpf
+ const CREOLES_PORTUGESE = 97; // cpp
+ const CREE = 98; // cre, cr
+ const CRIMEAN_TATAR = 99; // crh
+ const CREOLES = 100; // crp
+ const KASHUBIAN = 101; // csb
+ const CUSHITIC = 102; // cus
+ const WELSH = 103; // wel (b), cym (t), cy
+ const DAKOTA = 104; // dak
+ const DANISH = 105; // dan, da
+ const DARGWA = 106; // dar
+ const LAND_DAYAK = 107; // day
+ const DELAWARE = 108; // del
+ const SLAVE = 109; // den
+ const GERMAN = 110; // ger (b), deu (t), de
+ const DOGRIB = 111; // dgr
+ const DINKA = 112; // din
+ const DIVEHI = 113; // div, dv
+ const DOGRI = 114; // doi
+ const DRAVIDIAN = 115; // dra
+ const LOWER_SORBIAN = 116; // dsb
+ const DUALA = 117; // dua
+ const DUTCH_MIDDLE = 118; // dum
+ const DUTCH_FLEMISH = 119; // dut (b), nld (t), nl
+ const DYULA = 120; // dyu
+ const DZONGKHA = 121; // dzo, dz
+ const EFIK = 122; // efi
+ const EGYPTIAN = 123; // egy
+ const EKAJUK = 124; // eka
+ const GREEK_MODERN = 125; // gre (b), ell (t), el
+ const ELAMITE = 126; // elx
+ const ENGLISH = 127; // eng, en
+ const ENGLISH_MIDDLE = 128; // enm
+ const ESPERANTO = 129; // epo, eo
+ const ESTONIAN = 130; // est, et
+ const EWE = 131; // ewe, ee
+ const EWONDO = 132; // ewo
+ const FANG = 133; // fan
+ const FAROESE = 134; // fao, fo
+ const PERSIAN = 135; // per (b), fas (t), fa
+ const FANTI = 136; // fat
+ const FIJIAN = 137; // fij, fj
+ const FILIPINO = 138; // fil
+ const FINNISH = 139; // fin, fi
+ const FINNO_UGRIAN = 140; // fiu
+ const FON = 141; // fon
+ const FRENCH = 142; // fre (b), fra (t), fr
+ const FRENCH_MIDDLE = 143; // frm
+ const FRENCH_OLD = 144; // fro
+ const FRISIAN_NORTHERN = 145; // frr
+ const FRISIAN_EASTERN = 146; // frs
+ const FRISIAN_WESTERN = 147; // fry, fy
+ const FULAH = 148; // ful, ff
+ const FRIULIAN = 149; // fur
+ const GA = 150; // gaa
+ const GAYO = 151; // gay
+ const GBAYA = 152; // gba
+ const GERMANIC = 153; // gem
+ const GEORGIAN = 154; // geo (b), kat (t), ka
+ const GEEZ = 155; // gez
+ const GILBERTESE = 156; // gil
+ const GAELIC = 157; // gla, gd
+ const IRISH = 158; // gle, ga
+ const GALICIAN = 159; // glg, gl
+ const MANX = 160; // glv, gv
+ const GERMAN_MIDDLE_HIGH = 161; // gmh
+ const GERMAN_OLD_HIGH = 162; // goh
+ const GONDI = 163; // gon
+ const GORONTALO = 164; // gor
+ const GOTHIC = 165; // got
+ const GREBO = 166; // grb
+ const GREEK_ANCIENT = 167; // grc
+ const GUARANI = 168; // grm, gn
+ const GERMAN_SWISS = 169; // gsw
+ const GUJARATI = 170; // guj, gu
+ const GWICHIN = 171; // gwi
+ const HAIDA = 172; // hai
+ const HAITIAN = 173; // hat, ht
+ const HAUSA = 174; // hau, ha
+ const HAWAIIAN = 175; // haw
+ const HEBREW = 176; // heb, he
+ const HERERO = 177; // her, hz
+ const HILIGAYNON = 178; // hil
+ const HIMACHALI = 179; // him
+ const HINDI = 180; // hin, hi
+ const HITTITE = 181; // hit
+ const HMONG = 182; // hmn
+ const HIRI_MOTU = 183; // hmo, ho
+ const CROATIAN = 184; // hrv
+ const SORBIAN_UPPER = 185; // hsb
+ const HUNGARIAN = 186; // hun, hu
+ const HUPA = 187; // hup
+ const IBAN = 188; // iba
+ const IGBO = 189; // ibo, ig
+ const ICELANDIC = 190; // ice (b), isl (t), is
+ const IDO = 191; // ido, io
+ const SICHUAN_YI = 192; // iii, ii
+ const IJO = 193; // ijo
+ const INUKTITUT = 194; // iku, iu
+ const INTERLINGUE = 195; // ile, ie
+ const ILOKO = 196; // ilo
+ const INTERLINGUA = 197; // ina, ia
+ const INDIC = 198; // inc
+ const INDONESIAN = 199; // ind, id
+ const INDO_EUROPEAN = 200; // ine
+ const INGUSH = 201; // inh
+ const INUPIAQ = 202; // ipk, ik
+ const IRANIAN = 203; // ira
+ const IROQUOIAN = 204; // iro
+ const ITALIAN = 205; // ita, it
+ const JAVANESE = 206; // jav, jv
+ const LOJBAN = 207; // jbo
+ const JAPANESE = 208; // jpn, ja
+ const JUDEO_PERSIAN = 209; // jpr
+ const JUDEO_ARABIC = 210; // jrb
+ const KARA_KALPAK = 211; // kaa
+ const KABYLE = 212; // kab
+ const KACHIN = 213; // kac
+ const KALAALLISUT = 214; // kal, kl
+ const KAMBA = 215; // kam
+ const KANNADA = 216; // kan, kn
+ const KAREN = 217; // kar
+ const KASHMIRI = 218; // kas, ks
+ const KANURI = 219; // kau, kr
+ const KAWI = 220; // kaw
+ const KAZAKH = 221; // kaz
+ const KABARDIAN = 222; // kbd
+ const KHASI = 223; // kha
+ const KHOISAN = 224; // khi
+ const CENTRAL_KHMER = 225; // khm, km
+ const KHOTANESE = 226; // kho
+ const KIKUYU = 227; // kik, ki
+ const KINYARWANDA = 228; // kin, rw
+ const KIRGHIZ = 229; // kir, ky
+ const KIMBUNDU = 230; // kmb
+ const KONKANI = 231; // kok
+ const KOMI = 232; // kom, kv
+ const KONGO = 233; // kon, kg
+ const KOREAN = 234; // kor, ko
+ const KOSRAEAN = 235; // kos
+ const KPELLE = 236; // kpe
+ const KARACHAY_BALKAR = 237; // krc
+ const KARELIAN = 238; // krl
+ const KRU = 239; // kro
+ const KURUKH = 240; // kru
+ const KUANYAMA = 241; // kua, kj
+ const KUMYK = 242; // kum
+ const KURDISH = 243; // kur, ku
+ const KUTENAI = 244; // kut
+ const LADINO = 245; // lad
+ const LAHNDA = 246; // lah
+ const LAMBA = 247; // lam
+ const LAO = 248; // lao, lo
+ const LATIN = 249; // lat, la
+ const LATVIAN = 250; // lav, lv
+ const LEZGHIAN = 251; // lez
+ const LIMBURGAN = 252; // lim, li
+ const LINGALA = 253; // lin, ln
+ const LITHUANIAN = 254; // lit, lt
+ const MONGO = 255; // lol
+ const LOZI = 256; // loz
+ const LUXEMBOURGISH = 257; // ltz, lb
+ const LUBA_LULUA = 258; // lua
+ const LUBA_KATANGA = 259; // lub, lu
+ const GANDA = 260; // lug, lg
+ const LUISENO = 261; // lui
+ const LUNDA = 262; // lun
+ const LUO = 263; // luo
+ const LUSHAI = 264; // lus
+ const MACEDONIAN = 265; // mac (b), mkd (t), mk
+ const MADURESE = 266; // mad
+ const MAGAHI = 267; // mag
+ const MARSHALLESE = 268; // mah
+ const MAITHILI = 269; // mai
+ const MAKASAR = 270; // mak
+ const MALAYALAM = 271; // mal
+ const MANDINGO = 272; // man
+ const MAORI = 273; // mao (b), mri (t), mi
+ const AUSTRONESIAN = 274; // map
+ const MARATHI = 275; // mar, mr
+ const MASAI = 276; // mas
+ const MALAY = 277; // may (b), msa (t), ms
+ const MOKSHA = 278; // mdf
+ const MANDAR = 279; // mdr
+ const MENDE = 280; // men
+ const IRISH_MIDDLE = 281; // mga
+ const MIKMAQ = 282; // mic
+ const MINANGKABAU = 283; // min
+ const UNCODED = 284; // mis
+ const MON_KHMER = 285; // mkh
+ const MALAGASY = 286; // mlg
+ const MALTESE = 287; // mlt
+ const MANCHU = 288; // mnc
+ const MANIPURI = 289; // mni
+ const MANOBO = 290; // mno
+ const MOHAWK = 291; // moh
+ const MONGOLIAN = 292; // mon, mn
+ const MOSSI = 293; // mos
+ const MULTIPLE = 294; // mul
+ const MUNDA = 295; // mun
+ const CREEK = 296; // mus
+ const MIRANDESE = 297; // mwl
+ const MARWARI = 298; // mwr
+ const MAYAN = 299; // myn
+ const ERZYA = 300; // myv
+ const NAHUATL = 301; // nah
+ const AMERICAN_INDIAN_NORTH = 302; // nai
+ const NEAPOLITAN = 303; // nap
+ const NAURU = 304; // nau, na
+ const NAVAJO = 305; // nav, nv
+ const NDEBELE_SOUTH = 306; // nbl, nr
+ const NDEBELE_NORTH = 307; // nde, nd
+ const NDONGA = 308; // ndo, ng
+ const LOW_GERMAN = 309; // nds
+ const NEPALI = 310; // nep, ne
+ const NEPAL_BHASA = 311; // new
+ const NIAS = 312; // nia
+ const NIGER_KORDOFANIAN = 313; // nic
+ const NIUEAN = 314; // niu
+ const NORWEGIAN_NYNORSK = 315; // nno, nn
+ const BOKMAL = 316; // nob, nb
+ const NOGAI = 317; // nog
+ const NORSE_OLD = 318; // non
+ const NORWEGIAN = 319; // nor, no
+ const NKO = 320; // nqo
+ const PEDI = 321; // nso
+ const NUBIAN = 322; // nub
+ const CLASSICAL_NEWARI = 323; // nwc
+ const CHICHEWA = 324; // nya, ny
+ const NYAMWEZI = 325; // nym
+ const NYANKOLE = 326; // nyn
+ const NYORO = 327; // nyo
+ const NZIMA = 328; // nzi
+ const OCCITAN = 329; // oci, oc
+ const OJIBWA = 330; // oji, oj
+ const ORIYA = 331; // ori, or
+ const OROMO = 332; // orm, om
+ const OSAGE = 333; // osa
+ const OSSETIAN = 334; // oss, os
+ const OTTOMAN = 335; // ota
+ const OTOMIAN = 336; // oto
+ const PAPUAN = 337; // paa
+ const PANGASINAN = 338; // pag
+ const PAHLAVI = 339; // pal
+ const PAMPANGA = 340; // pam
+ const PANJABI = 341; // pan, pa
+ const PAPIAMENTO = 342; // pap
+ const PALAUAN = 342; // pau
+ const PERSIAN_OLD = 343; // peo
+ const PHILIPPINE = 344; // phi
+ const PHOENICIAN = 345; // phn
+ const PALI = 346; // pli, pi
+ const POLISH = 347; // pol, pl
+ const POHNPEIAN = 348; // pon
+ const PORTUGUESE = 349; // por, pt
+ const PRAKRIT = 350; // pra
+ const PROVENCAL = 351; // pro
+ const PUSHTO = 352; // pus, ps
+ const QUECHUA = 353; // que, qu
+ const RAJASTHANI = 354; // raj
+ const RAPANUI = 355; // rap
+ const RAROTONGAN = 356; // rar
+ const ROMANCE = 357; // roa
+ const ROMANSH = 358; // roh, rm
+ const ROMANY = 359; // rom
+ const ROMANIAN = 360; // rum (b), ron (t), ro
+ const RUNDI = 361; // run, rn
+ const AROMANIAN = 362; // rup
+ const RUSSIAN = 363; // rus, ru
+ const SANDAWE = 364; // sad
+ const SANGO = 365; // sag, sg
+ const YAKUT = 366; // sah
+ const AMERICAN_INDIAN_SOUTH = 367; // sai
+ const SALISHAN = 368; // sal
+ const SAMARITAN = 369; // sam
+ const SANSKRIT = 370; // san, sa
+ const SASAK = 371; // sas
+ const SANTALI = 372; // sat
+ const SICILIAN = 373; // scn
+ const SCOTS = 374; // sco
+ const SELKUP = 375; // sel
+ const SEMITIC = 376; // sem
+ const IRISH_OLD = 377; // sga
+ const SIGN = 378; // sgn
+ const SHAN = 379; // shn
+ const SIDAMO = 380; // sid
+ const SINHALA = 381; // sin, si
+ const SIOUAN = 382; // sio
+ const SINO_TIBETAN = 383; // sit
+ const SLAVIC = 384; // sla
+ const SLOVAK = 385; // slo (b), slk (t), sk
+ const SLOVENIAN = 386; // slv, sl
+ const SAMI_SOUTHERN = 387; // sma
+ const SAMI_NORTHERN = 388; // sme, se
+ const SAMI = 389; // smi
+ const SAMI_LULE = 390; // smj
+ const SAMI_IRARI = 391; // smn
+ const SAMOAN = 392; // smo, sm
+ const SAMI_SKOLT = 393; // sms
+ const SHONA = 394; // sna, sn
+ const SINDHI = 395; // snd, sd
+ const SONINKE = 396; // snk
+ const SOGDIAN = 397; // sog
+ const SOMALI = 398; // som, so
+ const SONGHAI = 399; // son
+ const SOTHO_SOUTHERN = 400; // sot, st
+ const SPANISH = 401; // spa, es
+ const SARDINIAN = 402; // srd, sc
+ const SRANAN_TONGO = 403; // sm
+ const SERBIAN = 404; // srp, sr
+ const SERER = 405; // srr
+ const NILO_SAHARAN = 406; // ssa
+ const SWATI = 407; // ssw, ss
+ const SUKUMA = 408; // suk
+ const SUNDANESE = 409; // sun, su
+ const SUSU = 410; // sus
+ const SUMERIAN = 411; // sux
+ const SWAHILI = 412; // swa, sw
+ const SWEDISH = 413; // swe, sv
+ const SYRIAC_CLASSICAL = 414; // syc
+ const SYRIAC = 415; // syr
+ const TAHITIAN = 416; // tah, ty
+ const TAI = 417; // tai
+ const TAMIL = 418; // tam, ta
+ const TATAR = 419; // tat, tt
+ const TELUGU = 420; // tel, te
+ const TIMNE = 421; // tem
+ const TERENO = 422; // ter
+ const TETUM = 423; // tet
+ const TAJIK = 424; // tgk, tg
+ const TAGALOG = 425; // tgl, tl
+ const THAI = 426; // tha, th
+ const TIGRE = 427; // tig
+ const TIGRINYA = 428; // tir, ti
+ const TIV = 429; // tiv
+ const TOKELAU = 430; // tkl
+ const KLINGON = 431; // tlh
+ const TLINGIT = 432; // tli
+ const TAMASHEK = 433; // tmh
+ const TONGA_NYASA = 434; // tog
+ const TONGA_ISLANDS = 435; // ton, to
+ const TOK_PISIN = 436; // tpi
+ const TSIMSHIAN = 437; // tsi
+ const TSWANA = 438; // tsn, tn
+ const TSONGA = 439; // tso, ts
+ const TURKMEN = 440; // tuk, tk
+ const TUMBUKA = 441; // tum
+ const TUPI = 442; // tup
+ const TURKISH = 443; // tur, tr
+ const ALTAIC = 444; // tut
+ const TUVALU = 445; // tvl
+ const TWI = 446; // twi, tw
+ const TUVINIAN = 447; // tyv
+ const UDMURT = 448; // udm
+ const UGARITIC = 449; // uga
+ const UIGHUR = 450; // uig, ug
+ const UKRAINIAN = 451; // ukr, uk
+ const UMBUNDU = 452; // umb
+ const UNDETERMINED = 453; // und
+ const URDU = 454; // urd, ur
+ const UZBEK = 455; // uzb, uz
+ const VAI = 456; // vai
+ const VENDA = 457; // ven, ve
+ const VIETNAMESE = 458; // vie, vi
+ const VOLAPUK = 459; // vol, vo
+ const VOTIC = 460; // vot
+ const WAKASHAN = 461; // wak
+ const WOLAITTA = 462; // wal
+ const WARAY = 463; // war
+ const WASHO = 464; // was
+ const SORBIAN = 465; // wen
+ const WALLOON = 466; // wln, wa
+ const WOLOF = 467; // wol, wo
+ const KALMYK = 468; // xal
+ const XHOSA = 469; // xho, xh
+ const YAO = 470; // yao
+ const YAPESE = 471; // yap
+ const YIDDISH = 472; // yid, yi
+ const YORUBA = 473; // yor, yo
+ const YUPIK = 474; // ypk
+ const ZAPOTEC = 475; // zap
+ const BLISSYMBOLS = 476; // zbl
+ const ZENAGA = 477; // zen
+ const MOROCCAN_TAMAZIGHT = 478; // zgh
+ const ZHUANG = 479; // zha, za
+ const ZANDE = 480; // znd
+ const ZULU = 481; // zul, zu
+ const ZUNI = 482; // zun
+ const NOT_APPLICABLE = 483; // zxx
+ const ZAZA = 484; // zza
+ const ENGLISH_CA = 485; // en-ca
+ const ENGLISH_GB = 486; // en-gb
+ const ENGLISH_US = 487; // en-us
+
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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();
+
+ /**
+ * 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();
+}
+
+/**
+ * A language class specifically for english only languages.
+ *
+ * @see: http://www.loc.gov/standards/iso639-2/php/code_list.php
+ */
+final class c_base_language_us_only implements i_base_language {
+
+ private static $s_aliases = array(
+ self::ENGLISH_US => 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]);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing ldap connections.
+ *
+ * This is initially designed just to select/read from the ldap and not meant to modify or manage ldap databases.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A class for managing ldap connections.
+ */
+class c_base_ldap {
+ private $ldap;
+ private $name;
+
+ private $bind_name;
+ private $bind_password;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing the logs.
+ */
+
+/**
+ * A generic class for managing the logs.
+ */
+class c_base_log {
+ const TYPE_NONE = 0;
+ const TYPE_BASE = 1; // for low-level entries.
+ const TYPE_REQUEST = 2; // accessing the site (generally page requests).
+ const TYPE_INTERPET = 3; // interpretting (such as a PHP-related).
+ const TYPE_DATABASE = 4; // the database.
+ const TYPE_USER = 5; // related to users.
+ const TYPE_PROXY = 6; // proxying as some other user.
+ const TYPE_ACCESS = 7; // access control.
+ const TYPE_CONTENT = 8; // content itself.
+ const TYPE_THEME = 9; // theme (such as renderring a theme).
+ const TYPE_RESPONSE = 10; // response to requests.
+ const TYPE_CONNECT = 11; // relating connecting and disconnecting from the site.
+ const TYPE_CLIENT = 12; // client information.
+ const TYPE_SERVER = 13; // server information.
+ const TYPE_LEGAL = 14; // legal or law-based information.
+ const TYPE_AUDIT = 15; // legal or law-based information.
+ const TYPE_CACHE = 16; // caching.
+ const TYPE_SYSTEM = 17; // system.
+ const TYPE_FILE = 18; // files.
+ const TYPE_TIME = 19; // time-related matters (such as cron jobs).
+ const TYPE_EVENT = 20; // time and place related matters.
+ const TYPE_SESSION = 21; // sessions.
+ const TYPE_MAIL = 22; // e-mails.
+ const TYPE_SIGN = 23; // signatures, such as PGP/GPG.
+ const TYPE_SYNC = 24; // synchronization of information.
+ const TYPE_WORKFLOW = 25; // workflow.
+ const TYPE_REQUEST = 26; // workflow: requesting.
+ const TYPE_COMMENT = 27; // workflow: commenting.
+ const TYPE_DRAFT = 28; // workflow: drafting.
+ const TYPE_REVIEW = 29; // workflow: reviewing.
+ const TYPE_EDIT = 30; // workflow: editting.
+ const TYPE_AMEND = 31; // workflow: ammending.
+ const TYPE_UNDO = 32; // workflow: undoing an edit.
+ const TYPE_APPROVE = 33; // workflow: approving.
+ const TYPE_DISPROVE = 34; // workflow: disproving.
+ const TYPE_PUBLISH = 35; // workflow: publushing.
+ const TYPE_UNPUBLISH = 36; // workflow: publushing.
+ const TYPE_ACCEPT = 37; // workflow: accepting.
+ const TYPE_DENY = 38; // workflow: denying.
+ const TYPE_CANCEL = 39; // workflow: cancelling.
+ const TYPE_UNCANCEL = 40; // workflow: cancelling.
+ const TYPE_AUDIT = 41; // workflow: auditing.
+ const TYPE_TRANSITION = 42; // workflow: transitioning.
+ const TYPE_REVERT = 43; // workflow: revert.
+ const TYPE_DELETE = 44; // workflow: delete.
+ const TYPE_RESTORE = 45; // workflow: restore (undelete).
+ const TYPE_UPGRADE = 46; // upgrade.
+ const TYPE_DOWNGRADE = 47; // downgrade.
+
+ // severity defines how important or the context of the log entry.
+ const SEVERITY_NONE = 0;
+ const SEVERITY_DEBUG = 1;
+ const SEVERITY_INFORMATION = 2; // regular logging information.
+ const SEVERITY_NOTICE = 3; // information worth noting.
+ const SEVERITY_WARNING = 4; // this could be a problem.
+ const SEVERITY_ERROR = 5; // this is a problem.
+ const SEVERITY_CRITICAL = 6; // this is a big problem.
+ const SEVERITY_EMERGENCY = 7; // this is the most serious type of problem.
+
+ private $type;
+ private $data;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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]);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for htmo markup.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic class for html tags.
+ *
+ * The structure and attributes may be used to communicate information, therefore the attributes extend to both input and output (theme).
+ * This class is not intended to be used for generate the theme but is instead intended to be used as a base class for both the input and the output classes for their respective purposes.
+ *
+ * Each tag has an internal id that is expected to be processed.
+ * This is not the same as the HTML 'id' attribute but can be the same.
+ *
+ * Many of the attributes are defined from HTML forms because of the number of forms.
+ *
+ * @see: https://www.w3.org/TR/html5/forms.html#forms
+ */
+class c_base_markup_tag {
+ private $id;
+ private $attributes;
+ private $tags;
+ private $tags_total;
+ private $parent;
+ private $children;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing mime-type information.
+ */
+
+/**
+ * A generic class for managing mime-type information.
+ */
+class c_base_mime {
+ const CATEGORY_UNKNOWN = 0;
+ const CATEGORY_PROVIDED = 1;
+ const CATEGORY_STREAM = 2;
+ const CATEGORY_TEXT = 1000;
+ const CATEGORY_IMAGE = 2000;
+ const CATEGORY_AUDIO = 3000;
+ const CATEGORY_VIDEO = 4000;
+ const CATEGORY_DOCUMENT = 5000;
+ const CATEGORY_CONTAINER = 6000;
+ const CATEGORY_APPLICATION = 7000; // only for application/* that values not covered by any other category.
+
+
+ const TYPE_UNKNOWN = 0;
+ const TYPE_PROVIDED = 1;
+ const TYPE_STREAM = 2;
+
+ const TYPE_TEXT_PLAIN = 1001;
+ const TYPE_TEXT_HTML = 1002;
+ const TYPE_TEXT_RSS = 1003;
+ const TYPE_TEXT_ICAL = 1004;
+ const TYPE_TEXT_CSV = 1005;
+ const TYPE_TEXT_XML = 1006;
+ const TYPE_TEXT_CSS = 1007;
+ const TYPE_TEXT_JS = 1008;
+ const TYPE_TEXT_JSON = 1009;
+ const TYPE_TEXT_RICH = 1010;
+ const TYPE_TEXT_XHTML = 1011;
+ const TYPE_TEXT_PS = 1012;
+
+ const TYPE_IMAGE_PNG = 2001;
+ const TYPE_IMAGE_GIF = 2002;
+ const TYPE_IMAGE_JPEG = 2003;
+ const TYPE_IMAGE_BMP = 2004;
+ const TYPE_IMAGE_SVG = 2005;
+ const TYPE_IMAGE_TIFF = 2006;
+
+ const TYPE_AUDIO_WAV = 3001;
+ const TYPE_AUDIO_OGG = 3002;
+ const TYPE_AUDIO_MP3 = 3003;
+ const TYPE_AUDIO_MP4 = 3004;
+ const TYPE_AUDIO_MIDI = 3005;
+
+ const TYPE_VIDEO_MPEG = 4001;
+ const TYPE_VIDEO_OGG = 4002;
+ const TYPE_VIDEO_H264 = 4003;
+ const TYPE_VIDEO_QUICKTIME = 4004;
+ const TYPE_VIDEO_DV = 4005;
+ const TYPE_VIDEO_JPEG = 4006;
+ const TYPE_VIDEO_WEBM = 4007;
+
+ const TYPE_DOCUMENT_LIBRECHART = 5001;
+ const TYPE_DOCUMENT_LIBREFORMULA = 5002;
+ const TYPE_DOCUMENT_LIBREGRAPHIC = 5003;
+ const TYPE_DOCUMENT_LIBREPRESENTATION = 5004;
+ const TYPE_DOCUMENT_LIBRESPREADSHEET = 5005;
+ const TYPE_DOCUMENT_LIBRETEXT = 5006;
+ const TYPE_DOCUMENT_LIBREHTML = 5007;
+ const TYPE_DOCUMENT_PDF = 5008;
+ const TYPE_DOCUMENT_ABIWORD = 5009;
+ const TYPE_DOCUMENT_MSWORD = 5010;
+ const TYPE_DOCUMENT_MSEXCEL = 5011;
+ const TYPE_DOCUMENT_MSPOWERPOINT = 5012;
+
+ const TYPE_CONTAINER_TAR = 6001;
+ const TYPE_CONTAINER_CPIO = 6002;
+ const TYPE_CONTAINER_JAVA = 6003;
+
+
+ private static $s_names_provided = array(
+ self::TYPE_PROVIDED => 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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing return values.
+ *
+ * Each function needs to return TRUE/FALSE to represent success or failure.
+ * However, if TRUE/FALSE could also represent a specific value, then TRUE/FALSE can have different contexts.
+ *
+ * This is a problem and the return result needs to be as context free as possible (aka: only 1 context).
+ * Having custom error codes can also be useful.
+ *
+ * Each class should instead return a type of base_return that can be easily tested for success or failure.
+ *
+ * The downside of this is that returning an object can waste more resources than a simple TRUE/FALSE.
+ * For consistency purposes, TRUE/FALSE should never be directly returned.
+ *
+ * Functions defined in this class will return the normal TRUE/FALSE and not the class-based TRUE/FALSE as an exception to this rule.
+ * - This is done because this class defines those objects.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+
+/**
+ * A generic trait for a return value class to have some value data.
+ *
+ * Return classes can be defined without this.
+ * - An example would be a boolean class whose name defines its state.
+ *
+ * Most return classes will use this trait.
+ *
+ * @require class base_error
+ */
+trait t_base_return_value {
+ protected $value = NULL;
+
+ /**
+ * Class destructor.
+ */
+ public function __destruct() {
+ unset($this->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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing common rfc character testing cases.
+ */
+
+// include required files.
+require_once('common/base/classes/base_ascii.php');
+require_once('common/base/classes/base_utf8.php');
+
+
+/**
+ * A class for managing common rfc character testing cases.
+ *
+ * This currently utilizes some of the rules defined in the following rfcs:
+ * - rfc 4234
+ * - rfc 5234
+ * - rfc 6532
+ *
+ * Most of the rules reference the following rfcs as a guideline (but many of the rules defined therein are replaced by rules in the later rfcs):
+ * - rfc 822
+ *
+ * This checks a single character, therefore:
+ * - This does not perform tests that require more than a single character to identify what the text represents.
+ * - This expects UTF_8 characters to be provided as a single 32-bit ordinal integer (as opposed to two distinct integers).
+ *
+ * WARNING: I am very new to UTF_8 coding and may be interpreting the rfc documentation incorrectly.
+ *
+ * Note: rfc 6532 does not mention whitespace of any kind and so non-ASCII "whitespace" is _not_ considered whitespace by this standard.
+ *
+ * @see: https://tools.ietf.org/html/rfc822
+ * @see: https://tools.ietf.org/html/rfc4234
+ * @see: https://tools.ietf.org/html/rfc5234
+ * @see: https://tools.ietf.org/html/rfc5335
+ * @see: https://tools.ietf.org/html/rfc6530
+ * @see: https://tools.ietf.org/html/rfc6531
+ * @see: https://tools.ietf.org/html/rfc6532
+ * @see: https://tools.ietf.org/html/rfc6533
+ * @see: http://www.herongyang.com/Unicode/UTF-8-UTF-8-Encoding.html
+ *
+ * @require class c_base_ascii
+ * @require class c_base_utf8
+ */
+abstract class c_base_rfc_char {
+
+ /**
+ * Check to see if character is: text.
+ *
+ * dtext = ASCII character codes 33, 35->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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing common rfc string testing cases.
+ */
+
+// include required files.
+require_once('common/base/classes/base_ascii.php');
+require_once('common/base/classes/base_utf8.php');
+require_once('common/base/classes/base_rfc_char.php');
+
+/**
+ * A class for managing common rfc string testing cases.
+ *
+ * This checks a a string of characters.
+ * The c_base_rf_string_is_* functions that require specific characters to start, such as DQUOTE, assume that the start position is after the initial special character, such as DQUOTE.
+ *
+ * This currently utilizes some of the rules defined in the following rfcs:
+ * - rfc 4234
+ * - rfc 5234
+ * - rfc 6532
+ *
+ * Most of the rules reference the following rfcs as a guideline (but many of the rules defined therein are replaced by rules in the later rfcs):
+ * - rfc 822
+ *
+ * WARNING: I am very new to utf8 coding and may be interpreting the rfc documentation incorrectly.
+ *
+ * Note: rfc 6532 does not mention whitespace of any kind and so non-ASCII "whitespace" is _not_ considered whitespace by this standard.
+ *
+ * @see: https://tools.ietf.org/html/rfc822
+ * @see: https://tools.ietf.org/html/rfc4234
+ * @see: https://tools.ietf.org/html/rfc5234
+ * @see: https://tools.ietf.org/html/rfc5335
+ * @see: https://tools.ietf.org/html/rfc6530
+ * @see: https://tools.ietf.org/html/rfc6531
+ * @see: https://tools.ietf.org/html/rfc6532
+ * @see: https://tools.ietf.org/html/rfc6533
+ * @see: http://www.herongyang.com/Unicode/UTF-8-UTF-8-Encoding.html
+ *
+ * @require class c_base_ascii
+ * @require class c_base_utf8
+ * @require class c_base_rfc_char
+ */
+abstract class c_base_rfc_string extends c_base_rfc_char {
+ const STOP_AT_CLOSING_CHARACTER = -1;
+
+ /**
+ * Converts a string into a ordinals array and a characters array.
+ *
+ * The ordinals and characters returned by this are utf8-friendly such that the caller need only use a counter to navigate.
+ * - This eliminates the need for constant checking for utf8 or non-utf8 while traversing the string.
+ *
+ * @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 array of characters representing the string (utf8-safe).
+ * 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($text) {
+ $result = array(
+ 'ordinals' => 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);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing sessions.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A class for managing sessions.
+ *
+ * This utilizes the custom session project called 'sessionize_accounts' and does not use PHP's session management.
+ * The current design does not store session variables, only the session key, username, ip address, and password.
+ * This session key can be used to retrieve a password between requests and to access the database.
+ * The database can then be used to retrieve any session variables.
+ */
+class c_base_session {
+ const PACKET_MAX_LENGTH = 8192;
+ const SOCKET_PATH_PREFIX = '/var/www/sockets/sessionize_accounts/';
+ const SOCKET_PATH_SUFFIX = '/sessions.socket';
+ const PASSWORD_CLEAR_TEXT_LENGTH = 2048;
+
+ private $socket;
+ private $socket_path;
+ private $socket_timeout;
+
+ private $system_name;
+
+ private $name;
+ private $id_user;
+ private $ip;
+ private $password;
+ private $session_id;
+ private $settings;
+
+ private $error;
+
+ private $timeout_expire;
+ private $timeout_max;
+
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides UTF-8 support.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A class for managing and processing utf-8.
+ */
+class c_base_utf8 {
+ const BOM = "\xef\xbb\xbf";
+ const UTF_8 = 'UTF-8';
+
+ const SPACE_NO_BREAK = 160;
+ const SPACE_NO_BREAK_NARROW = 8239;
+ const SPACE_OGHAM_MARK = 5760;
+ const SPACE_MONGOLIAN_VOWEL = 6158;
+ const SPACE_EN = 8194;
+ const SPACE_EN_QUAD = 8192;
+ const SPACE_EM = 8195;
+ const SPACE_EM_QUAD = 8193;
+ const SPACE_EM_THREE_PER = 8196;
+ const SPACE_EM_FOUR_PER = 8197;
+ const SPACE_EM_SIX_PER = 8198;
+ const SPACE_FIGURE = 8199;
+ const SPACE_PUNCTUTION = 8200;
+ const SPACE_THIN = 8201;
+ const SPACE_HAIR = 8202;
+ const SPACE_LINE = 8232;
+ const SPACE_PARAGRAPH = 8233;
+ const SPACE_MATHEMATICAL = 8287;
+ const SPACE_IDEOGRAPHIC = 12288;
+
+ /**
+ * UTF_8-X masks and marks.
+ * - UTF_8-1: 0bxxxxxxxx (0b0xxxxxxx) (0000 0000)
+ * - UTF_8-2: 0byyyyyxxxxxx (0b110yyyyy, 0b10xxxxxx) (1100 0000 1000 0000)
+ * - UTF_8-3: 0bzzzzyyyyyyxxxxxx (0b1110zzzz, 0b10yyyyyy, 0b10xxxxxx) (1110 0000 1000 0000 1000 0000)
+ * - UTF_8-4: 0bvvvzzzzzzyyyyyyxxxxxx (0b11110vvv, 0b10zzzzzz, 0b10yyyyyy, 0b10xxxxxx) (1111 1000 1000 0000 1000 0000 1000 0000)
+ *
+ * The masks for UTF_8 are intended to represent the bits that will never be present for the given UTF_8 group.
+ *
+ * The marks for UTF_8 are intended to represent the exact "left" order bit combination that are required to represent the UTF_8 group.
+ *
+ * The bits are for checking a single 8-bit character value (specifically, checking the first bits).
+ */
+ const MASK_1 = 0b11111111111111111111111110000000;
+ const MASK_2 = 0b11111111111111110000000000000000;
+ const MASK_3 = 0b11111111000000000000000000000000;
+ const MASK_4 = 0b00000000000000000000000000000000;
+
+ const MARK_1 = 0b00000000000000000000000000000000;
+ const MARK_2 = 0b00000000000000001100000010000000;
+ const MARK_3 = 0b00000000111000001000000010000000;
+ const MARK_4 = 0b11110000100000001000000010000000;
+
+ const BIT_1 = 0b10000000;
+ const BIT_2 = 0b11000000;
+ const BIT_3 = 0b11100000;
+ const BIT_4 = 0b11110000;
+
+
+ /**
+ * Checks whether the passed string contains only byte sequances that appear valid UTF-8 characters.
+ *
+ * @param string $text
+ * The string to be checked.
+ *
+ * @return c_base_return_status
+ * TRUE on success, FALSE otherwise.
+ *
+ * @see: mb_detect_encoding()
+ */
+ public static function s_is_UTF_8($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (mb_check_encoding($text, self::UTF_8)) {
+ return new c_base_return_true();
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Calculates integer value of the given UTF-8 encoded character.
+ *
+ * @param string $character
+ * The character of which to calculate ordinal of.
+ *
+ * @return c_base_return_int|c_base_return_status
+ * Ordinal value of the given character.
+ * FALSE without error bit set is returned on invalid UTF-8 byte sequence
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_character_to_ordinal($character) {
+ if (!is_string($character)) {
+ return c_base_return_error::s_false();
+ }
+
+ $ordinal = self::p_s_character_to_ordinal($character);
+ if ($ordinal === FALSE) {
+ unset($ordinal);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_int::s_new($ordinal);
+ }
+
+ /**
+ * Generates a UTF-8 encoded character from the given ordinal.
+ *
+ * @param int $ordinal
+ * The ordinal for which to generate a character.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * Milti-Byte character returns empty string on failure to encode.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_ordinal_to_character($ordinal) {
+ if (!is_int($ordinal)) {
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_string::s_new(self::p_s_ordinal_to_character($ordinal));
+ }
+
+ /**
+ * Finds the length of the given string in terms of number of valid UTF-8 characters it contains.
+ *
+ * Invalid characters are ignored.
+ *
+ * @param string $text
+ * The string for which to find the character length.
+ *
+ * @return c_base_return_int|c_base_return_status
+ * Length of the Unicode String.
+ * FALSE with error bit set is returned on error.
+ *
+ * @see: mb_strlen()
+ */
+ public static function s_length_string($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ $length = self::p_s_length_string($text);
+ if ($length === FALSE) {
+ unset($length);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_int::s_new($length);
+ }
+
+ /**
+ * Removes all non-UTF-8 characters from a given string.
+ *
+ * @param string $text
+ * The string to be sanitized.
+ * @param bool $remove_bom
+ * (optional) When TRUE, the UTF_8 BOM character is removed.
+ * When FALSE, no action related to the BOM character is performed.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * Clean UTF-8 encoded string.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_clean($text, $remove_bom = FALSE) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_bool($remove_bom)) {
+ return c_base_return_error::s_false();
+ }
+
+ $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($sanitized);
+ }
+
+ /**
+ * Convert a string to an array of Unicode characters.
+ *
+ * @param string $text
+ * The string to split into array.
+ * @param int $split_length
+ * Max character length of each array element.
+ *
+ * @return c_base_return_array|c_base_return_status
+ * An array containing chunks of the string.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_split($text, $split_length = 1) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_int($split_length)) {
+ return c_base_return_error::s_false();
+ }
+
+ $split = self::p_s_split($text, $split_length);
+ if ($split === FALSE) {
+ unset($split);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_array::s_new($split);
+ }
+
+ /**
+ * Generates an array of each character of a Unicode string separated into individual bytes.
+ *
+ * @param string $text
+ * The original Unicode string.
+ *
+ * @return c_base_return_array|c_base_return_status
+ * An array of byte lengths of each character.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_character_size_list($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ $size_list = self::p_s_character_size_list($text);
+ if ($size_list === FALSE) {
+ unset($size_list);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_array::s_new($size_list);
+ }
+
+ /**
+ * Calculates and returns the maximum number of bytes taken by any UTF-8 encoded character in the given string.
+ *
+ * @param string $text
+ * The original Unicode string.
+ *
+ * @return c_base_return_int|c_base_return_status
+ * An integer representing the max character width found within the passed string.
+ * FALSE with error bit set is returned on error.
+ *
+ * @see: p_s_character_size_list()
+ */
+ public static function s_character_max_width($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ $size_list = self::p_s_character_size_list($text);
+ if ($size_list === FALSE) {
+ unset($size_list);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_int::s_new(max($size_list));
+ }
+
+ /**
+ * Converts a UTF-8 character to HTML Numbered Entity like {
+ *
+ * @param string $character
+ * The Unicode character to be encoded as numbered entity
+ *
+ * @return c_base_return_string|c_base_return_status
+ * HTML numbered entity.
+ * FALSE with error bit set is returned on error.
+ *
+ * @see: p_s_encode_html_character()
+ */
+ public static function s_encode_html_character($character) {
+ if (!is_string($character)) {
+ return c_base_return_error::s_false();
+ }
+
+ $encoded = self::p_s_encode_html_character($character);
+ if ($encoded === FALSE) {
+ unset($encoded);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_string::s_new($encoded);
+ }
+
+ /**
+ * Converts a UTF-8 string to a series of HTML Numbered Entities, such as: {'ی...
+ *
+ * @param string $text
+ * The Unicode string to be encoded as numbered entities.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * HTML numbered entities.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_encode_html($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ $split = self::p_s_split($text);
+ if ($split === FALSE) {
+ unset($split);
+ return c_base_return_error::s_false();
+ }
+
+ $new_array = array();
+ foreach ($split as $character) {
+ $encoded = self::p_s_character_to_ordinal($character);
+ if ($encoded === FALSE) {
+ // should some error reporting/handling be performed here?
+ continue;
+ }
+
+ $new_array[] = $ordinal;
+ }
+ unset($encoded);
+ unset($character);
+
+ return c_base_return_string::s_new(implode($new_array));
+ }
+
+ /**
+ * UTF-8 aware substr().
+ *
+ * substr() works with bytes, while s_substring works with characters and are identical in all other aspects.
+ *
+ * @param string $text
+ * The string to obtain a substring from.
+ * @param int $start
+ * (optional) The start position.
+ * @param int|null $length
+ * (optional) The length of the substring.
+ * If NULL, then the length is PHP_INT_MAX.
+ *
+ * @see: mb_substr()
+ */
+ public static function s_substring($text, $start = 0, $length = NULL) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_int($start)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_null($length) && !is_int($length)) {
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_string::s_new(self::p_s_substring($text, $start, $length));
+ }
+
+ /**
+ * Checks if the given string is a Byte Order Mark.
+ *
+ * @param string $text
+ * The input string.
+ *
+ * @return c_base_return_status
+ * TRUE if the $text is Byte Order Mark, FALSE otherwise.
+ *
+ * @see: p_s_is_bom()
+ */
+ public static function s_is_bom($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (self::p_s_is_bom($text)) {
+ return new c_base_return_true();
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Checks if a file starts with BOM character.
+ *
+ * @param string $file_path
+ * Path to a valid file.
+ *
+ * @return c_base_return_status
+ * TRUE if the file has BOM at the start, FALSE otherwise.
+ */
+ public static function s_file_has_bom($file_path) {
+ if (!is_string($file_path)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!file_exists($file_path)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (self::p_s_is_bom(file_get_contents($file_path, 0, NULL, -1, 3))) {
+ return new c_base_return_true();
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Checks if string starts with BOM character.
+ *
+ * @param string $text
+ * The input string.
+ *
+ * @return c_base_return_status
+ * TRUE if the string has BOM at the start, FALSE otherwise.
+ */
+ public static function s_string_has_bom($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (self::p_s_is_bom(substr($text, 0, 3))) {
+ return new c_base_return_true();
+ }
+
+ return new c_base_return_false();
+ }
+
+ /**
+ * Prepends BOM character to the string and returns the whole string.
+ *
+ * If BOM already existed there, the Input string is returned.
+ *
+ * @param string $text
+ * The input string.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * The output string that contains BOM.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_prepend_bom($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!s_is_bom(substr($text, 0, 3))) {
+ return c_base_return_string::s_new(self::BOM . $text);
+ }
+
+ return c_base_return_string::s_new($text);
+ }
+
+ /**
+ * Reverses characters order in the string.
+ *
+ * @param string $text
+ * The input string.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * The string with characters in the reverse sequence.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_reverse($text) {
+ if (!is_string($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ $split = self::p_s_split($text);
+ if ($split === FALSE) {
+ unset($split);
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_string::s_new(implode(array_reverse($split)));
+ }
+
+ /**
+ * Finds the number of Characters to the left of first occurance of the needle.
+ *
+ * strpos works with bytes, while s_position_string works with characters and are identical in all other aspects.
+ *
+ * @param string $haystack
+ * The string to search within.
+ * @param string|int $needle
+ * The characters to search for or a single ordinal integer to search for.
+ * $param int $offset
+ * (optional) position in the string to start searching.
+ * The offset cannot be negative.
+ *
+ * @return c_base_return_int|c_base_return_status
+ * A number representing the character position is returned when the needle is found in the haystack.
+ * FALSE with error bit set is returned on error.
+ * FALSE without the error bit set is returned when the needle is not found in the haystack.
+ *
+ * @see: mb_strpos()
+ */
+ public static function s_position_string($haystack, $needle, $offset = 0) {
+ if (!is_string($haystack)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_int($needle) && !is_string($needle)) {
+ return c_base_return_error::s_false();
+ }
+
+ if (!is_int($offset) || $offset < 0) {
+ return c_base_return_error::s_false();
+ }
+
+ $search_for = $needle;
+ if (is_int($needle)) {
+ $search_for = self::p_s_ordinal_to_character($needle);
+ }
+
+ $search_for = self::p_s_clean($search_for);
+ $target = self::p_s_clean($haystack);
+
+ return c_base_return_int::s_new(mb_strpos($target, $search_for, $offset, self::UTF_8));
+ }
+
+ /**
+ * Accepts a string and returns an array of ordinals.
+ *
+ * @param string|array $text
+ * A UTF-8 encoded string or an array of such strings.
+ *
+ * @return c_base_return_array|c_base_return_status
+ * The array of ordinals.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_string_to_ordinals($text) {
+ if (!is_string($text) && !is_array($text)) {
+ return c_base_return_error::s_false();
+ }
+
+ return c_base_return_array::s_new(self::p_s_string_to_ordinals($text));
+ }
+
+ /**
+ * Converts an array of ordinals into a string.
+ *
+ * @param array $ordinals
+ * An array of Unicode code points.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * A string represnting the ordinals array.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_ordinals_to_string($ordinals) {
+ if (!is_array($ordinals)) {
+ return c_base_return_error::s_false();
+ }
+
+ $string = '';
+ foreach ($ordinals as $ordinal) {
+ $character = self::p_s_ordinal_to_character($ordinal);
+ if ($character === FALSE) {
+ // @todo: generate a warning or error of some sort.
+ continue;
+ }
+
+ $string .= $character;
+ }
+ unset($ordinal);
+ unset($character);
+
+ return c_base_return_string::s_new($string);
+ }
+
+ /**
+ * Converts an array of ordinals into an array of characters.
+ *
+ * @param array $ordinals
+ * An array of ordinals.
+ *
+ * @return c_base_return_array|c_base_return_status
+ * An array of characters represnting the ordinals array.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_ordinals_to_string_array($ordinals) {
+ if (!is_array($ordinals)) {
+ return c_base_return_error::s_false();
+ }
+
+ $array = array();
+ foreach ($ordinals as $ordinal) {
+ $character = self::p_s_ordinal_to_character($ordinal);
+ if ($character === FALSE) {
+ // @todo: generate a warning or error of some sort.
+ continue;
+ }
+
+ $array[] = $character;
+ }
+ unset($ordinal);
+ unset($character);
+
+ return c_base_return_array::s_new($array);
+ }
+
+ /**
+ * Makes a UTF-8 string from a ordinal array.
+ *
+ * @param array $ordinals
+ * An array of rdinals.
+ *
+ * @return c_base_return_string|c_base_return_status
+ * UTF-8 encoded string.
+ * FALSE with error bit set is returned on error.
+ */
+ public static function s_ordinal_array_to_string($ordinals) {
+ if (!is_array($ordinals)) {
+ return c_base_return_error::s_false();
+ }
+
+ $array = array();
+ foreach ($ordinals as $key => $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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing HTML DOM.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+
+/**
+ * A generic class for managing HTML DOM.
+ *
+ * This class uses DOMDocument and assigns a type of doctype HTML.
+ * This is not technically HTML5, but HTML5 must be used to make sure the dom functionality operates as expected.
+ *
+ * The purpose of this class is to provide a context-based language for later renderring of content, be it HTML5 or otherwise.
+ *
+ * It is primarily meant to help simplify management of the DOMDocument and DOMNode classes without re-implementing anything.
+ */
+class c_theme_dom extends DOMDocument {
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ super.__construct('1.0', 'UTF-8');
+ }
+
+ /**
+ * Class destructor.
+ */
+ public function __destruct() {
+ }
+
+ /**
+ * Initialize the object to explicit HTML5 settings.
+ */
+ public function initialize_as_html() {
+ $this->preserveWhiteSpace = TRUE;
+ $this->formatOutput = FALSE;
+ @$this->loadHTML('<!DOCTYPE html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body></body>');
+ }
+
+ /**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing HTML5 Markup.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ *
+ * @see: https://www.w3.org/TR/html5/
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_form.php');
+
+// global:
+//accesskey
+//class
+//contenteditable
+//dir
+//hidden
+//id
+//lang
+//spellcheck
+//style
+//tabindex
+//title
+//translate
+//onabort
+//onblur*
+//oncancel
+//oncanplay
+//oncanplaythrough
+//onchange
+//onclick
+//oncuechange
+//ondblclick
+//ondurationchange
+//onemptied
+//onended
+//onerror*
+//onfocus*
+//oninput
+//oninvalid
+//onkeydown
+//onkeypress
+//onkeyup
+//onload*
+//onloadeddata
+//onloadedmetadata
+//onloadstart
+//onmousedown
+//onmouseenter
+//onmouseleave
+//onmousemove
+//onmouseout
+//onmouseover
+//onmouseup
+//onmousewheel
+//onpause
+//onplay
+//onplaying
+//onprogress
+//onratechange
+//onreset
+//onresize*
+//onscroll*
+//onseeked
+//onseeking
+//onselect
+//onshow
+//onstalled
+//onsubmit
+//onsuspend
+//ontimeupdate
+//ontoggle
+//onvolumechange
+//onwaiting
+
+//accept-charset - Character encodings to use for form submission
+//action - URL to use for form submission
+//autocomplete - Default setting for autofill feature for controls in the form
+//enctype - Form data set encoding type to use for form submission
+//method - HTTP method to use for form submission
+//name - Name of form to use in the document.forms API
+//novalidate - Bypass form control validation for form submission
+//target - Browsing context for form submission
+
+
+/**
+ * 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_theme_form_tag extends c_base_form_tag {
+ private $attributes;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing HTML5 Markup.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ *
+ * @see: https://www.w3.org/TR/html5/
+ */
+
+// include required files.
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+/**
+ * A generic class for HTML tag attributes.
+ *
+ * @see: https://www.w3.org/TR/html5/forms.html#forms
+ */
+class c_theme_html_attribute_value_boolean {
+}
--- /dev/null
+<?php
+/**
+ * @file
+ * Provides a class for managing HTML DOM for the custom markup language used by this project.
+ *
+ * This is currently a draft/brainstorm and is subject to be completely rewritten/redesigned.
+ *
+ * This project provides a context-specific markup language for context-independent design with context-specific presentation.
+ *
+ * The following are the rules for this specific markup language:
+ * 1) There is no "block" or "inline", all things are "blocks" and may be presented inline (or conversely).
+ * - That is to say, the markup does not define the block/not-block status, the theme does.
+ *
+ * 2) The only available tags are:
+ * - <tag>:
+ * - May only be defined in: <heading>, <title>, <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>
+ * </title>
+ * - Must not contain any nested markup.
+ * - <heading>:
+ * - Provides heading information, such as external javascript or css files.
+ * - Similar to the HTML <head> tag.
+ * - May only contain the following tags: (@todo: determine what these will be).
+ * - @todo: details need to be fleshed out.
+ * - <context>:
+ * - Provides how to interpret data (such as text or images).
+ * - May only contain the following tags: <tag>.
+ * - @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.
+ * - <files>:
+ * - 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: <tag id="image-favicon.ico" type="image/ico" alternate="Minature logo for website.">/images/favicon.ico</tag>
+ * - <content>:
+ * - All content to be read is provided here.
+ * - May only contain the following tags: <tag>.
+ * - Example:
+ * <content>
+ * <tag id="message-1">This is some text that I wanted to present to you.</tag>.
+ * <tag id="message-2">This could be used anywhere, including 'tooltip' and 'title' sections.</tag>.
+ * <tag id="alternate-1">This could be used anywhere, but is likely intended for 'alt' attribute like uses.</tag>.
+ * </content>
+ * - <presentation>:
+ * - The flow and presentation of the content is provided here.
+ * - May only contain the following tags: <tag>.
+ * - 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 = '<tag';
+
+ if (!is_null($this->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 .= '</tag>';
+ }
+ else {
+ foreach ($this->tags as $tag) {
+ $markup .= $tag->to_markup()->get_value_exact();
+ }
+
+ $markup .= '</tags>';
+ }
+
+ 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);
+ }
+
+ // <tag> 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);
+ }
+
+ // <tags> only supports <tags> or <tag>.
+ 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 = '<!DOCTYPE cml>';
+ $markup .= '<cml>';
+
+ $markup .= '<title>';
+ $markup .= $this->title->to_markup()->get_value_exact();
+ $markup .= '</title>';
+
+ $markup .= '<heading>';
+ $markup .= $this->heading->to_markup()->get_value_exact();
+ $markup .= '</heading>';
+
+ $markup .= '<files>';
+ $markup .= $this->files->to_markup()->get_value_exact();
+ $markup .= '</files>';
+
+ $markup .= '<context>';
+ $markup .= $this->context->to_markup()->get_value_exact();
+ $markup .= '</context>';
+
+ $markup .= '<presentation>';
+ $markup .= $this->presentation->to_markup()->get_value_exact();
+ $markup .= '</presentation>';
+
+ $markup .= '<content>';
+ $markup .= $this->content->to_markup()->get_value_exact();
+ $markup .= '</content>';
+
+ unset($tag_markup);
+ $markup .= '</cml>';
+
+ 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;
+ }
+}
--- /dev/null
+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.
--- /dev/null
+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.