]> Kevux Git Server - koopa/commitdiff
Progress: designing path handling, designing form processing, and other fixes.
authorKevin Day <thekevinday@gmail.com>
Mon, 24 Apr 2017 21:26:39 +0000 (16:26 -0500)
committerKevin Day <thekevinday@gmail.com>
Mon, 24 Apr 2017 21:29:37 +0000 (16:29 -0500)
Path management is done via classes.
The form processing code is just a rough draft of ideas and is subject to massive changes.

24 files changed:
common/base/classes/base_defaults_global.php
common/base/classes/base_email.php
common/base/classes/base_form.php
common/base/classes/base_html.php
common/base/classes/base_http.php
common/base/classes/base_markup.php
common/base/classes/base_path.php
common/base/classes/base_return.php
common/base/classes/base_rfc_string.php
common/theme/classes/theme_dom.php
documentation/url_paths.txt
program/reservation/index.php
program/reservation/internal/access_denied.php [new file with mode: 0644]
program/reservation/internal/bad_method.php [new file with mode: 0644]
program/reservation/internal/not_found.php [new file with mode: 0644]
program/reservation/paths/a/reservation_dashboard.php [new file with mode: 0644]
program/reservation/paths/u/dashboard.php [new file with mode: 0644]
program/reservation/paths/u/login.php [new file with mode: 0644]
program/reservation/paths/u/logout.php [new file with mode: 0644]
program/reservation/reservation_database.php
program/reservation/reservation_paths.php
program/reservation/reservation_redirects.php [new file with mode: 0644]
program/reservation/reservation_session.php
program/reservation/reservation_theme.php [new file with mode: 0644]

index 023e53afaa7b4e3841b7a1cbe6e9cdda6590a14a..ed43a6f59aa81bf40e3a305e2ebf421fe15f970b 100644 (file)
  * A collection of global settings for use by the entire project.
  *
  * This is intended to be modified by the developers or site developers of a project.
+ *
+ * Warning: Any variables defined here (due to being global) must be considered not thread-safe.
+ *          Be sure to handle carefully when using threads.
+ *          It is recommended to process and pre-set as much of this as possible before starting threads.
  */
 class c_base_defaults_global {
+  // set to NULL for auto, TRUE to always enable backtrace on error, and FALSE to always disable backtrace on error.
+  const ERROR_BACKTRACE_ALWAYS = NULL;
+
+  // set to NULL for auto, TRUE to enable backtrace for parameter-type errors, and FALSE to disable.
+  // this is overriden when ERROR_BACKTRACE_ALWAYS is not NULL.
+  const ERROR_BACKTRACE_ARGUMENTS = FALSE;
+
+  // provide a language to fallback to if none is set.
+  const LANGUAGE_CLASS_DEFAULT = 'c_base_language_us_only';
+
+  // reserved path groups: array(97, 99, 100, 102, 115, 116, 120, 121).
+  const RESERVED_PATH_GROUP = array(c_base_ascii::LOWER_A, c_base_ascii::LOWER_C, c_base_ascii::LOWER_D, c_base_ascii::LOWER_F, c_base_ascii::LOWER_S, c_base_ascii::LOWER_T, c_base_ascii::LOWER_U, c_base_ascii::LOWER_X);
+
+  // a class name to prepend to css classes or id attributes.
+  const CSS_BASE = 'reservation-';
+
+
   // Represents the current timestamp of this PHP process/session, see: self::s_get_timestamp_session().
   private static $s_timestamp_session = NULL;
 
@@ -36,17 +57,6 @@ class c_base_defaults_global {
   private static $s_language = NULL;
 
 
-  // set to NULL for auto, TRUE to always enable backtrace on error, and FALSE to always disable backtrace on error.
-  const ERROR_BACKTRACE_ALWAYS = NULL;
-
-  // set to NULL for auto, TRUE to enable backtrace for parameter-type errors, and FALSE to disable.
-  // this is overriden when ERROR_BACKTRACE_ALWAYS is not NULL.
-  const ERROR_BACKTRACE_ARGUMENTS = FALSE;
-
-  // provide a language to fallback to if none is set.
-  const LANGUAGE_CLASS_DEFAULT = 'c_base_language_us_only';
-
-
   /**
    * Get a date string, relative to UTC, with support for milliseconds and microseconds.
    *
@@ -215,7 +225,7 @@ class c_base_defaults_global {
   }
 
   /**
-   * Get the current timestamp of the session, relative to UTC..
+   * Get the current timestamp of the session, relative to UTC.
    *
    * @param bool $use_request_time
    *   (optional) Set to TRUE to attempt to use REQUEST_TIME_FLOAT or REQUEST_TIME where available.
index fdfe70980191fdcb12c5a7f8cc6c20871331cf4d..c0d876e2c217fe2b3fc117ff2a9afbe0b5dd1242 100644 (file)
@@ -29,6 +29,8 @@ require_once('common/base/classes/base_rfc_string.php');
  * @require class c_base_utf8
  */
 class c_base_email extends c_base_rfc_string {
+  use t_base_return_value_exact;
+
   const LINE_LENGTH_LIMIT_SOFT = 78;
   const LINE_LENGTH_LIMIT_HARD = 998;
 
@@ -55,6 +57,54 @@ class c_base_email extends c_base_rfc_string {
   }
 
   /**
+   * 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 (!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_string($this->value)) {
+      return 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 (!is_string($this->value)) {
+      return '';
+    }
+
+    return $this->value;
+  }
+
+  /**
    * Decode and check that the given e-mail address is valid.
    *
    * Validation is done according to rfc5322, rfc6854, and rfc7231.
index 59f1f30de3e19a9bdeac6c8b4f1bb6ade91aae77..256f94e1c1632fb7ed806e736952b5aefb3443d1 100644 (file)
@@ -8,6 +8,7 @@
 require_once('common/base/classes/base_error.php');
 require_once('common/base/classes/base_return.php');
 
+
 /**
  * A class for form problems.
  *
index c5fafad6eb1d67ba1dde42a83c820b925d0247da..d5bed593f31288fba20c62626569823c992bcffa 100644 (file)
@@ -77,7 +77,7 @@ class c_base_html extends c_base_return {
   }
 
   /**
-   * Assign a unique numeric id to represent this form.
+   * Assign a unique numeric id to represent this HTML page.
    *
    * @param int $id
    *   The unique form tag id to assign.
@@ -97,7 +97,7 @@ class c_base_html extends c_base_return {
   }
 
   /**
-   * Get the unique id assigned to this object.
+   * Get the unique id assigned to this HTML page.
    *
    * @return c_base_return_int|c_base_return_status
    *   The unique numeric id assigned to this object.
index 9b120ed8aa0bf28be8a4591df4ffe3a0b03ae44d..4beffbd571719c31eb3a76726ee604806918e19f 100644 (file)
@@ -45,23 +45,27 @@ class c_base_http extends c_base_rfc_string {
   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_GET                            = 17;
+  const REQUEST_HOST                           = 18;
+  const REQUEST_IF_MATCH                       = 19;
+  const REQUEST_IF_MODIFIED_SINCE              = 20;
+  const REQUEST_IF_NONE_MATCH                  = 21;
+  const REQUEST_IF_RANGE                       = 22;
+  const REQUEST_IF_UNMODIFIED_SINCE            = 23;
+  const REQUEST_MAX_FORWARDS                   = 24;
+  const REQUEST_ORIGIN                         = 25;
+  const REQUEST_POST                           = 26;
+  const REQUEST_PRAGMA                         = 27;
+  const REQUEST_PROXY_AUTHORIZATION            = 28;
+  const REQUEST_RANGE                          = 29;
+  const REQUEST_REFERER                        = 30;
+  const REQUEST_SCRIPT_NAME                    = 31;
+  const REQUEST_TE                             = 32;
+  const REQUEST_UPGRADE                        = 33;
+  const REQUEST_URI                            = 34;
+  const REQUEST_USER_AGENT                     = 35;
+  const REQUEST_VIA                            = 36;
+  const REQUEST_WARNING                        = 37;
   const REQUEST_UNKNOWN                        = 999;
 
   // non-standard, but supported, request headers
@@ -74,6 +78,7 @@ class c_base_http extends c_base_rfc_string {
   const REQUEST_CHECKSUM_CONTENT        = 1007;
   const REQUEST_CONTENT_ENCODING        = 1008;
   const REQUEST_SIGNATURE_PG            = 1009;
+  // @todo: should PHP's REMOTE_ADDRESS be added and handled here?
 
   // standard response headers
   const RESPONSE_NONE                             = 0;
@@ -229,6 +234,9 @@ class c_base_http extends c_base_rfc_string {
   const SEPARATOR_HEADER_NAME = ': ';
   const SEPARATOR_HEADER_LINE = "\n";
 
+  // fallbacks/failsafes
+  const FALLBACK_PROTOCOL = 'HTTP/1.1';
+
   private $headers;
   private $headers_sent;
   private $request;
@@ -550,9 +558,11 @@ class c_base_http extends c_base_rfc_string {
       self::REQUEST_PROXY_AUTHORIZATION,
       self::REQUEST_RANGE,
       self::REQUEST_REFERER,
+      self::REQUEST_SCRIPT_NAME,
       self::REQUEST_TE,
-      self::REQUEST_USER_AGENT,
       self::REQUEST_UPGRADE,
+      self::REQUEST_URI,
+      self::REQUEST_USER_AGENT,
       self::REQUEST_VIA,
       self::REQUEST_WARNING,
       self::REQUEST_X_REQUESTED_WITH,
@@ -732,21 +742,31 @@ class c_base_http extends c_base_rfc_string {
       unset($headers['referer']);
     }
 
+    if (array_key_exists('script_name', $this->headers)) {
+      $this->p_load_request_script_name();
+      unset($headers['script_name']);
+    }
+
     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('uri', $this->headers)) {
+      $this->p_load_request_uri();
+      unset($headers['uri']);
+    }
+
+    if (array_key_exists('user_agent', $this->headers)) {
+      $this->p_load_request_user_agent();
+      unset($headers['user_agent']);
+    }
+
     if (array_key_exists('via', $this->headers)) {
       $this->p_load_request_via();
       unset($headers['via']);
@@ -2765,6 +2785,7 @@ class c_base_http extends c_base_rfc_string {
    *   TRUE on success, FALSE otherwise.
    *   FALSE with error bit set is returned on error.
    *
+   * // @fixme: there are more response status definitions, the see: needs to be updated.
    * @see: https://tools.ietf.org/html/rfc7232#section-4
    */
   public function set_response_status($code) {
@@ -2950,7 +2971,7 @@ class c_base_http extends c_base_rfc_string {
    * Assign HTTP response header: HTTP Protocol.
    *
    * @param string $protocol
-   *   A string representing the HTTP protocol, such as: "HTTP 1.1".
+   *   A string representing the HTTP protocol, such as: "HTTP/1.1".
    *
    * @return c_base_return_status
    *   TRUE on success, FALSE otherwise.
@@ -4598,8 +4619,13 @@ class c_base_http extends c_base_rfc_string {
     // 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] . ' ';
+    if (array_key_exists(self::RESPONSE_STATUS, $this->response)) {
+      if (array_key_exists(self::RESPONSE_PROTOCOL, $this->response)) {
+        $status_string = $this->response[self::RESPONSE_PROTOCOL] . ' ';
+      }
+      else {
+        $status_string = self::FALLBACK_PROTOCOL . ' ';
+      }
 
       $status_text = c_base_http_status::to_text($this->response[self::RESPONSE_STATUS]);
       if ($status_text instanceof c_base_return_false) {
@@ -4715,6 +4741,22 @@ class c_base_http extends c_base_rfc_string {
   }
 
   /**
+   * Returns whether or not response content is defined.
+   *
+   * @return c_base_return_bool
+   *   TRUE if content is defined.
+   *   FALSE otherwise.
+   *   FALSE with error bit set is returned on error.
+   */
+  public function has_response_content() {
+    if (empty($this->content)) {
+      return new c_base_return_false();
+    }
+
+    return new c_base_return_true();
+  }
+
+  /**
    * Encode the HTTP response content.
    *
    * This must be performed after all content has been buffered and before the HTTP headers are sent.
@@ -4889,6 +4931,40 @@ class c_base_http extends c_base_rfc_string {
   }
 
   /**
+   * Sanitizes a given path to ensure certain combinations of characters are not allowed.
+   *
+   * This removes any '../' in the path.
+   * This removes multiple consecutive '/'.
+   * This removes any '/' prefix.
+   * This removes any '/' suffix.
+   *
+   * @param string $path
+   *   The path to sanitize
+   *
+   * @return c_base_return_string
+   *   The sanitized string on success.
+   *   An empty string with the error bit set is returned on error.
+   */
+  public function sanitize_path($path) {
+    if (!is_string($path)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value('', 'c_base_return_string', $error);
+    }
+
+    // do not support '../' in the url paths (primarily for security reasons).
+    $sanitized = preg_replace('@^\.\./(\.\./)*@', '', $path);
+    $sanitized = preg_replace('@/(\.\./)+@', '/', $sanitized);
+
+    // remove redundant path parts, such as replacing '//////' with '/'.
+    $sanitized = preg_replace('@/(/)+@', '/', $sanitized);
+
+    // remove leading and trailing slashes.
+    $sanitized = preg_replace('@(^/|/$)@', '', $sanitized);
+
+    return c_base_return_string::s_new($sanitized);
+  }
+
+  /**
    * 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.
@@ -6338,6 +6414,39 @@ class c_base_http extends c_base_rfc_string {
   }
 
   /**
+   * Load and process the HTTP request parameter: uri.
+   *
+   * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3
+   */
+  private function p_load_request_uri() {
+    if (empty($this->headers['uri'])) {
+      $this->request[self::REQUEST_URI]['invalid'] = TRUE;
+      return;
+    }
+
+    $text = $this->pr_rfc_string_prepare($this->headers['uri']);
+    if ($text['invalid']) {
+      $this->request[self::REQUEST_URI]['invalid'] = TRUE;
+      unset($text);
+      return;
+    }
+
+    $this->request[self::REQUEST_URI]['data'] = $this->pr_rfc_string_is_uri($text['ordinals'], $text['characters']);
+    unset($text);
+
+    if ($this->request[self::REQUEST_URI]['data']['invalid']) {
+      $this->request[self::REQUEST_URI]['invalid'] = TRUE;
+    }
+    else {
+      $this->request[self::REQUEST_URI]['defined'] = TRUE;
+    }
+    unset($this->request[self::REQUEST_URI]['data']['invalid']);
+    unset($this->request[self::REQUEST_URI]['data']['current']);
+
+    $this->request[self::REQUEST_URI]['invalid'] = FALSE;
+  }
+
+  /**
    * Load and process the HTTP request parameter: via.
    *
    * @see: https://tools.ietf.org/html/rfc7231#section-5.5.3
@@ -7828,6 +7937,7 @@ class c_base_http extends c_base_rfc_string {
         // break the header name so that it is consistent until such time that PHP stops clobbering the header names.
         $broken = preg_replace('/-/u', '_', $key);
         $broken = c_base_utf8::s_lowercase($broken)->get_value_exact();
+
         $this->headers[$broken] = $value;
         unset($broken);
       }
@@ -7854,6 +7964,11 @@ class c_base_http extends c_base_rfc_string {
       }
     }
 
+    $this->headers['uri'] = '';
+    if (isset($_SERVER['REQUEST_URI'])) {
+      $this->headers['uri'] = $_SERVER['REQUEST_URI'];
+    }
+
     $timestamp = c_base_defaults_global::s_get_timestamp_session();
     if (!$timestamp->has_error()) {
       $this->request_time = $timestamp->get_value_exact();
@@ -8596,7 +8711,7 @@ class c_base_http extends c_base_rfc_string {
       unset($header_output[self::RESPONSE_CONTENT_ENCODING]);
     }
     else {
-      $header_output[self::RESPONSE_CONTENT_ENCODING] .= substr($output, 1);
+      $header_output[self::RESPONSE_CONTENT_ENCODING] .= c_base_utf8::s_substring($output, 1)->get_value_exact();
     }
   }
 
@@ -8630,7 +8745,7 @@ class c_base_http extends c_base_rfc_string {
     unset($language);
 
     if (!is_null($output)) {
-      $header_output[self::RESPONSE_CONTENT_LANGUAGE] = $header_name . ': ' . substr($output, 2);
+      $header_output[self::RESPONSE_CONTENT_LANGUAGE] = $header_name . ': ' . c_base_utf8::s_substring($output, 2)->get_value_exact();
     }
     unset($output);
   }
index 2ee90f0215023f6d5ccca6fb3bbc38d2badd6f5e..56b7f460d26374c3b1d63736fd2cef6e37457a34 100644 (file)
@@ -10,6 +10,7 @@ require_once('common/base/classes/base_return.php');
 require_once('common/base/classes/base_mime.php');
 require_once('common/base/classes/base_charset.php');
 require_once('common/base/classes/base_languages.php');
+require_once('common/base/classes/base_rfc_string.php');
 
 /**
  * A generic class for html attribute types.
@@ -338,6 +339,23 @@ class c_base_markup_attributes {
 }
 
 /**
+ * A generic class for html sanitization filters.
+ */
+class c_base_markup_filters {
+  const FILTER_NONE                 = 0;
+  const FILTER_ALPHA_NUMERIC        = 1;
+  const FILTER_DATE                 = 2;
+  const FILTER_EMAIL                = 3;
+  const FILTER_MONTH                = 4;
+  const FILTER_NUMERIC              = 5;
+  const FILTER_TEXT                 = 6;
+  const FILTER_TEXT_PLAIN           = 7;
+  const FILTER_TEXT_HTML            = 8;
+  const FILTER_TYPE_DATE_TIME_LOCAL = 9;
+  const FILTER_WEEK                 = 10;
+}
+
+/**
  * 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).
@@ -352,7 +370,7 @@ class c_base_markup_attributes {
  *
  * @todo: add support for non-standard tag attributes, which will just be a string or NULL.
  */
-class c_base_markup_tag extends c_base_return {
+class c_base_markup_tag extends c_base_rfc_string {
   const TYPE_NONE                       = 0;
   const TYPE_A                          = 1;
   const TYPE_ABBR                       = 2;
@@ -514,11 +532,11 @@ class c_base_markup_tag extends c_base_return {
   const TYPE_WEEK                       = 158;
   const TYPE_WIDE_BREAK                 = 159;
 
-  private $attributes;
-  private $tags;
-  private $tags_total;
-  private $text;
-  private $type;
+  protected $attributes;
+  protected $tags;
+  protected $tags_total;
+  protected $text;
+  protected $type;
 
   /**
    * Class constructor.
@@ -1354,6 +1372,174 @@ class c_base_markup_tag extends c_base_return {
   }
 
   /**
+   * Checks that the assigned HTML attribute 'value' is valid according to the tag type.
+   *
+   * @param int $attribute
+   *   The attribute to assign.
+   * @param bool $sanitize
+   *   (optional) When TRUE, the text is altered and replaced with new text.
+   *   When FALSE, no changes are made.
+   * @param int|null $type
+   *   (optional) The filter type to sanitize as.
+   *   When NULL, evaluate the attribute based on the tag type (if supported, only a few form tags are supported).
+   * @param int|string|null $sub_type
+   *   The sub-type to validate "value" as.
+   *   This is not directly implemented by this class and is provided so that extending classes may have more fine-tuned control.
+   *   This should accept either an int, a string, or NULL.
+   * @param array $options
+   *   (optional) any additional options that are specific to a given sanitization type.
+   *
+   * @return c_base_return_status
+   *   NULL is returned if not defined.
+   *   TRUE if changes were made, when $sanitize is TRUE.
+   *   TRUE on valid string, when $sanitize is FALSE.
+   *   TRUE with error bit set is returned on error, when $sanitize is TRUE.
+   *   FALSE if no changes, when $sanitize is TRUE.
+   *   FALSE on invalid string, when $sanitize is FALSE.
+   *   FALSE with error bit set is returned on error and no changes were made.
+   *
+   * @see: self::pr_check_attribute_as_text()
+   */
+  public function check_attribute($attribute, $sanitize = FALSE, $type = NULL, $sub_type = NULL, $options = array()) {
+    if (!is_int($attribute)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'attribute', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_bool($sanitize)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'sanitize', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_int($type)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'type', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_null($sub_type) && !is_int($sub_type) && !is_string($sub_type)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'sub_type', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_array($options)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'options', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_array($this->attributes) || !array_key_exists($attribute, $this->attributes)) {
+      return new c_base_return_null();
+    }
+
+    if (is_null($type)) {
+      if ($this->type === TYPE_CHECKBOX) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_COLOR) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_DATE) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_DATE_TIME_LOCAL) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_EMAIL) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_HIDDEN) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_IMAGE) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_INPUT) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_MONTH) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_NUMBER) {
+        return $this->pr_check_attribute_as_numeric($sanitize);
+      }
+
+      if ($this->type === TYPE_OPTION) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_PASSWORD) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_RADIO) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_SEARCH) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_TELEPHONE) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_TEXT) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_TEXT_AREA) {
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_TIME) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_URL) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      if ($this->type === TYPE_WEEK) {
+        // @todo: sanitize specifically to possible types.
+        return $this->pr_check_attribute_as_text($sanitize);
+      }
+
+      // for all other types, do nothing.
+      return new c_base_return_false();
+    }
+
+    // when type is not null, process based on passed validation filter code.
+    if ($type === c_base_markup_filters::FILTER_TEXT) {
+      return $this->pr_check_attribute_as_text($sanitize);
+    }
+    elseif ($type === c_base_markup_filters::FILTER_NUMERIC) {
+      return $this->pr_check_attribute_as_numeric($sanitize);
+    }
+
+    // @todo: finish this.
+
+    // for all other types, do nothing.
+    return new c_base_return_false();
+  }
+
+  /**
    * Add or append a given tag to the object.
    *
    * @param c_base_markup_tag $tag
@@ -1842,4 +2028,155 @@ class c_base_markup_tag extends c_base_return {
 
     return TRUE;
   }
+
+  /**
+   * Removes all invalid characters for text fields.
+   *
+   * @param bool $sanitize
+   *   (optional) When TRUE, the text is altered and replaced with new text.
+   *   When FALSE, no changes are made.
+   *
+   * @return c_base_return_status
+   *   TRUE if changes were made, when $sanitize is TRUE.
+   *   TRUE on valid string, when $sanitize is FALSE.
+   *   TRUE with error bit set is returned on error, when $sanitize is TRUE.
+   *   FALSE if no changes, when $sanitize is TRUE.
+   *   FALSE on invalid string, when $sanitize is FALSE.
+   *   FALSE with error bit set is returned on error and no changes were made.
+   *
+   * @see: self::check_attribute()
+   */
+  protected function pr_check_attribute_as_text($attribute, $sanitize = TRUE) {
+    if (is_string($this->attributes[$attribute])) {
+      $value = $this->attributes[$attribute];
+    }
+    elseif (is_numeric($this->attributes[$attribute])) {
+      $value = (string) $this->attributes[$attribute];
+    }
+    else {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'value attribute', ':expected_format' => 'text', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
+      return c_base_return_error::s_false($error);
+    }
+
+    $prepared = $this->pr_rfc_string_prepare($value);
+    if ($prepared instanceof c_base_return_false) {
+      unset($prepared);
+      unset($value);
+
+      $this->attributes[$attribute] = NULL;
+
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'value attribute', ':expected_format' => 'text', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
+
+      if ($sanitize) {
+        return c_base_return_error::s_true($error);
+      }
+
+      return c_base_return_error::s_false($error);
+    }
+    unset($value);
+
+    $text = $prepared->get_value_exact();
+    unset($prepared);
+
+    $invalid = FALSE;
+    $changed = FALSE;
+    $sanitized = '';
+    $current = 0;
+    $stop = count($ordinals);
+
+    for (; $current < $stop; $current++) {
+      if (!array_key_exists($current, $ordinals) || !array_key_exists($current, $characters)) {
+        $invalid = TRUE;
+        break;
+      }
+
+      $code = $ordinals[$current];
+
+      if (!$this->pr_rfc_char_is_text($code)) {
+        $invalid = TRUE;
+        $changed = TRUE;
+        continue;
+      }
+
+      $sanitized .= $characters[$current];
+    }
+    unset($code);
+    unset($current);
+    unset($stop);
+
+    if ($sanitize && $changed) {
+      $this->attributes[$attribute] = $sanitized;
+    }
+    unset($sanitized);
+
+    if ($invalid) {
+      unset($invalid);
+
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'value attribute', ':expected_format' => 'text', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
+      if ($changed) {
+        unset($changed);
+        return c_base_return_error::s_true($error);
+      }
+      unset($changed);
+
+      return c_base_return_error::s_false($error);
+    }
+    unset($invalid);
+
+    if ($changed) {
+      unset($changed);
+      return new c_base_return_true();
+    }
+    unset($changed);
+
+    return new c_base_return_false();
+  }
+
+  /**
+   * Removes all invalid characters for numeric fields.
+   *
+   * @param bool $sanitize
+   *   (optional) When TRUE, the text is altered and replaced with new text.
+   *   When FALSE, no changes are made.
+   *
+   * @return c_base_return_status
+   *   TRUE if changes were made, when $sanitize is TRUE.
+   *   TRUE on valid string, when $sanitize is FALSE.
+   *   TRUE with error bit set is returned on error, when $sanitize is TRUE.
+   *   FALSE if no changes, when $sanitize is TRUE.
+   *   FALSE on invalid string, when $sanitize is FALSE.
+   *   FALSE with error bit set is returned on error and no changes were made.
+   *
+   * @see: self::check_attribute()
+   */
+  protected function pr_check_attribute_as_numeric($attribute, $sanitize = TRUE) {
+    if (!is_numeric($this->attributes[$attribute])) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'value attribute', ':expected_format' => 'number', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
+      return c_base_return_error::s_false($error);
+    }
+
+    // @fixme: this is just a quick and dirty implementation, come back and re-design this more effectively.
+
+    $value = floatval($this->attributes[$attribute]);
+    if (floor($value) == $value) {
+      $value = (int) $value;
+    }
+
+    if ($this->attribute[$attribute] == $value) {
+      unset($value);
+
+      if ($sanitize) {
+        return new c_base_return_false();
+      }
+
+      return new c_base_return_true();
+    }
+
+    if ($sanitize) {
+      $this->attribute[$attribute] = $value;
+      return new c_base_return_true();
+    }
+
+    return new c_base_return_false();
+  }
 }
index cb3ea6dbff9f5df8ddf3fb80b3ef2c78c2e11722..88215330575ef269ef28e2139fb17700e4648a65 100644 (file)
@@ -22,10 +22,16 @@ 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_http.php');
+require_once('common/base/classes/base_http_status.php');
+require_once('common/base/classes/base_markup.php');
+require_once('common/base/classes/base_html.php');
+require_once('common/base/classes/base_cookie.php');
 
 /**
  * A generic class for managing paths information.
  *
+ * @todo: update this class documentation.
+ *
  * Note: There are still a.lot of changes going on in this and this comments may become out of date.
  *       At this time, I am considering having system defined paths be 'group-paths', existing at: '/a/, '/b/', '/c/', .., '/0/', '/1/', .., '/9'.
  *       All other non-'group-paths' are dynamic paths used by the database.
@@ -52,32 +58,33 @@ require_once('common/base/classes/base_http.php');
  *
  * The variables provided by this object are based on v_paths and not t_paths.
  *
+ * $id_group is used to represent the 'group-path' using an ordinal of one of the following 'a-z', 'A-Z', or '0-9'.
+ * These paths are case-sensitive.
  *
- * $id_path is used to represent the 'group-path' using an ordinal of one of the following 'a-z', 'A-Z', or '0-9'.
- * Interpretation of case-sensitivity is implimentation specific.
+ * @todo: add support to conditionally handle HTTP Request Methods and handle errors as desired by implementing classes.
  *
  * // c_base_utf8::s_substring($path_string, 0, 1);
  */
-abstract class c_base_path extends c_base_return_string {
-  private $id       = NULL;
-  private $id_path  = NULL;
-  private $id_sort  = NULL;
+class c_base_path extends c_base_rfc_string {
+  use t_base_return_value_exact;
+
+  protected $id_group = NULL;
 
-  private $is_content  = NULL;
-  private $is_alias    = NULL;
-  private $is_redirect = NULL;
-  private $is_private  = NULL;
-  private $is_locked   = NULL;
+  protected $is_content  = NULL;
+  protected $is_alias    = NULL;
+  protected $is_redirect = NULL;
+  protected $is_private  = NULL;
+  protected $is_locked   = NULL;
+  protected $is_root     = NULL;
 
-  //private $field_path  = NULL; // stored as $this->value (this is temporary notation and will be removed).
-  private $field_destination   = NULL;
-  private $field_response_code = NULL;
+  protected $field_destination   = NULL;
+  protected $field_response_code = NULL;
 
-  private $date_created = NULL;
-  private $date_changed = NULL;
-  private $date_locked  = NULL;
+  protected $date_created = NULL;
+  protected $date_changed = NULL;
+  protected $date_locked  = NULL;
 
-  private $processed = NULL;
+  protected $include = NULL;
 
   /**
    * Class constructor.
@@ -85,15 +92,15 @@ abstract class c_base_path extends c_base_return_string {
   public function __construct() {
     parent::__construct();
 
-    $this->id       = NULL;
-    $this->id_path  = NULL;
-    $this->id_sort  = NULL;
+    $this->id_group = NULL;
 
     $this->is_content  = TRUE;
     $this->is_alias    = FALSE;
     $this->is_redirect = FALSE;
     $this->is_private  = TRUE;
     $this->is_locked   = FALSE;
+    $this->is_root     = FALSE;
+
 
     $this->field_destination   = NULL;
     $this->field_response_code = NULL;
@@ -102,22 +109,21 @@ abstract class c_base_path extends c_base_return_string {
     $this->date_changed = NULL;
     $this->date_locked  = NULL;
 
-    $this->processed = NULL;
+    $this->include = NULL;
   }
 
   /**
    * Class destructor.
    */
   public function __destruct() {
-    unset($this->id);
-    unset($this->id_path);
-    unset($this->id_sort);
+    unset($this->id_group);
 
     unset($this->is_content);
     unset($this->is_alias);
     unset($this->is_redirect);
     unset($this->is_private);
     unset($this->is_locked);
+    unset($this->is_root);
 
     unset($this->field_destination);
     unset($this->field_response_code);
@@ -126,7 +132,7 @@ abstract class c_base_path extends c_base_return_string {
     unset($this->date_changed);
     unset($this->date_locked);
 
-    unset($this->processed);
+    unset($this->include);
 
     parent::__destruct();
   }
@@ -153,11 +159,96 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
+   * Assign the value.
+   *
+   * The string must be a valid URL path.
+   *
+   * This removes multiple consecutive '/'.
+   * This removes any '/' prefix.
+   * This removes any '/' suffix.
+   * This limits the string size to 256 characters.
+   *
+   * @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;
+    }
+
+    $sanitized = self::pr_sanitize_path($value);
+
+    // the path wildcard is intentionally non-standard.
+    // remove it so that it does not cause the validator to fail.
+    $without_wildcard = preg_replace('@(^%/|^%$|/%/|/%$)@', '', $sanitized);
+    if (!is_string($without_wildcard)) {
+      return FALSE;
+    }
+
+    // check to see if sanitized value is allowed.
+    $without_wilcard_parts = $this->pr_rfc_string_prepare($without_wildcard);
+    unset($without_wildcard);
+
+    if ($without_wilcard_parts['invalid']) {
+      unset($without_wilcard_parts);
+      unset($sanitized);
+      return FALSE;
+    }
+
+    $validated = $this->pr_rfc_string_is_path($without_wilcard_parts['ordinals'], $without_wilcard_parts['characters']);
+    if ($validated['invalid']) {
+      unset($without_wilcard_parts);
+      unset($validated);
+      unset($sanitized);
+      return FALSE;
+    }
+    unset($without_wilcard_parts);
+    unset($validated);
+
+    $this->value = $sanitized;
+    unset($sanitized);
+
+    return TRUE;
+  }
+
+  /**
+   * Return the value.
+   *
+   * @return string|null $value
+   *   The value array stored within this class.
+   */
+  public function get_value() {
+    if (!is_string($this->value)) {
+      return 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 (!is_string($this->value)) {
+      return '';
+    }
+
+    return $this->value;
+  }
+
+  /**
    * Create a content path.
    *
-   * @param int $id_path
+   * @param int $id_group
    *   An ordinal of one of the characters a-z, A-Z, or 0-9.
-   *   Used for grouping paths.
+   *   0 may be assigned to represent no group.
    * @param string $field_path
    *   The URL path assigned to this field.
    *   This is not assigned on parameter error.
@@ -170,14 +261,16 @@ abstract class c_base_path extends c_base_return_string {
    *   Always returns the newly created tag.
    *   Error bit is set on error.
    */
-  public static function s_create_content($id_path, $field_path, $is_private = TRUE) {
-    $path = new __CLASS__();
+  public static function s_create_content($id_group, $field_path, $is_private = TRUE) {
+    $class = __CLASS__;
+    $path = new $class();
+    unset($class);
 
-    // store all errors on return.
+    // @todo: store all errors on return.
     $errors = array();
 
-    if (is_int($id_path)) {
-      $path->set_id_path($id_path);
+    if (is_int($id_group)) {
+      $path->set_id_group($id_group);
     }
 
     if (is_string($field_path)) {
@@ -191,6 +284,8 @@ abstract class c_base_path extends c_base_return_string {
       $path->set_is_private(TRUE);
     }
 
+    $path->set_is_content(TRUE);
+
     $timestamp_session = c_base_defaults_global::s_get_timestamp_session();
     $path->set_date_created($timestamp_session);
     $path->set_date_changed($timestamp_session);
@@ -204,8 +299,9 @@ abstract class c_base_path extends c_base_return_string {
    *
    * Defaults are silently forced on invalid parameters.
    *
-   * @param int $id_path
+   * @param int $id_group
    *   An ordinal of one of the characters a-z, A-Z, or 0-9.
+   *   0 may be assigned to represent no group.
    * @param string $field_path
    *   The URL path assigned to this field.
    *   This is not assigned on parameter error.
@@ -220,14 +316,16 @@ abstract class c_base_path extends c_base_return_string {
    *   A newly created tag is returned on success.
    *   FALSE with the error bit set is returned on error.
    */
-  public static function s_create_alias($id_path, $field_path, $field_destination, $is_private = TRUE) {
-    $path = new __CLASS__();
+  public static function s_create_alias($id_group, $field_path, $field_destination, $is_private = TRUE) {
+    $class = __CLASS__;
+    $path = new $class();
+    unset($class);
 
-    // store all errors on return.
+    // @todo: store all errors on return.
     $errors = array();
 
-    if (is_int($id_path)) {
-      $path->set_id_path($id_path);
+    if (is_int($id_group)) {
+      $path->set_id_group($id_group);
     }
 
     if (is_string($field_path)) {
@@ -245,6 +343,8 @@ abstract class c_base_path extends c_base_return_string {
       $path->set_is_private(TRUE);
     }
 
+    $path->set_is_alias(TRUE);
+
     $timestamp_session = c_base_defaults_global::s_get_timestamp_session();
     $path->set_date_created($timestamp_session);
     $path->set_date_changed($timestamp_session);
@@ -258,11 +358,6 @@ abstract class c_base_path extends c_base_return_string {
    *
    * Defaults are silently forced on invalid parameters.
    *
-   * @param int $id_path
-   *   An ordinal of one of the characters a-z, A-Z, or 0-9.
-   * @param string $field_path
-   *   The URL path assigned to this field.
-   *   This is not assigned on parameter error.
    * @param string $field_destination
    *   A destination URL to redirect to.
    * @param int $response_code
@@ -285,26 +380,20 @@ abstract class c_base_path extends c_base_return_string {
    *   A newly created tag is returned on success.
    *   FALSE with the error bit set is returned on error.
    */
-  public static function s_create_redirect($id_path, $field_path, $field_destination, $field_response_code, $is_private = TRUE) {
-    $path = new __CLASS__();
+  public static function s_create_redirect($field_destination, $field_response_code, $is_private = TRUE) {
+    $class = __CLASS__;
+    $path = new $class();
+    unset($class);
 
-    // store all errors on return.
+    // @todo: store all errors on return.
     $errors = array();
 
-    if (is_int($id_path)) {
-      $path->set_id_path($id_path);
-    }
-
-    if (is_string($field_path)) {
-      $path->set_value($field_path);
-    }
-
     if (is_string($field_destination)) {
       $path->set_field_destination($field_destination);
     }
 
     if (is_int($field_response_code)) {
-      $path->set_response_code($field_response_code);
+      $path->set_field_response_code($field_response_code);
     }
 
     if (is_bool($is_private)) {
@@ -314,6 +403,8 @@ abstract class c_base_path extends c_base_return_string {
       $path->set_is_private(TRUE);
     }
 
+    $path->set_is_redirect(TRUE);
+
     $timestamp_session = c_base_defaults_global::s_get_timestamp_session();
     $path->set_date_created($timestamp_session);
     $path->set_date_changed($timestamp_session);
@@ -323,76 +414,26 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
-   * Assigns the machine name setting.
-   *
-   * @param int $id
-   *   The machine name associated with the path.
-   *
-   * @return c_base_return_status
-   *   TRUE on success, FALSE otherwise.
-   */
-  public function set_id($id) {
-    if (!is_int($id)) {
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'id', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
-      return c_base_return_error::s_false($error);
-    }
-
-    $this->id = $id;
-    return new c_base_return_true();
-  }
-
-  /**
-   * Assigns the machine name setting.
+   * Assigns sort id.
    *
-   * @param int $id_path
-   *   The machine name associated with the path.
+   * @param int $id_group
+   *   A id used for grouping and sorting the path.
+   *   Must be >= 0.
    *
    * @return c_base_return_status
    *   TRUE on success, FALSE otherwise.
    */
-  public function set_id_path($id_path) {
-    if (!is_int($id_path)) {
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'id_path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+  public function set_id_group($id_group) {
+    if (!is_int($id_group) || $id_group < 0) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'id_group', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
       return c_base_return_error::s_false($error);
     }
 
-    $this->id_path = $id_path;
+    $this->id_group = $id_group;
     return new c_base_return_true();
   }
 
   /**
-   * Assign the value.
-   *
-   * This removes multiple consecutive '/'.
-   * This removes any '/' prefix.
-   * This removes any '/' suffix.
-   * This limits the string size to 256 characters.
-   *
-   * @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 = $this->p_sanitize_value($value);
-
-    if (mb_strleng($this->value) == 0) {
-      $this->id_sort = 0;
-    }
-    else {
-      $this->id_sort = ord(c_base_utf8::s_substring($this->value, 0, 1)->get_value_exact());
-    }
-
-    return TRUE;
-  }
-
-  /**
    * Assigns the is content boolean setting.
    *
    * @param bool $is_content
@@ -545,6 +586,25 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
+   * Assigns the is root boolean setting.
+   *
+   * @param bool $is_root
+   *   The is root boolean associated with the path.
+   *
+   * @return c_base_return_status
+   *   TRUE on success, FALSE otherwise.
+   */
+  public function set_is_root($is_root) {
+    if (!is_bool($is_root)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'is_root', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    $this->is_root = $is_root;
+    return new c_base_return_true();
+  }
+
+  /**
    * Assigns the destination field setting.
    *
    * @param string $field_destination
@@ -640,56 +700,41 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
-   * Gets the id setting.
+   * Assign an include file needed to process this path.
    *
-   * @return c_base_return_int
-   *   ID on success.
-   *   An ID of 0 means that there is no valid ID specified.
-   *   Error bit is set on error.
-   */
-  public function get_id() {
-    if (!is_int($this->id)) {
-      return c_base_return_int::s_new(0);
-    }
-
-    return c_base_return_int::s_new($this->id);
-  }
-
-  /**
-   * Gets the ID path setting.
+   * @param string $path
+   *   A path to a file that may be found via the PHP search path.
    *
-   * @return c_base_return_int
-   *   ID group on success.
-   *   A path ID of 0 means that there is no valid ID specified.
-   *   Error bit is set on error.
+   * @return c_base_return_status
+   *   TRUE on success, FALSE otherwise.
    */
-  public function get_id_path() {
-    if (!is_int($this->id_path)) {
-      return c_base_return_int::s_new(0);
+  public function set_include($path) {
+    if (!is_string($path)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
     }
 
-    return c_base_return_int::s_new($this->id_path);
+    $this->include = $path;
+    return new c_base_return_true();
   }
 
   /**
-   * Gets the ID sort value.
-   *
-   * The ID sort value is the ordinal of the first character of the path.
-   * This is used for minor optimization.
+   * Gets the ID sort setting.
    *
    * @return c_base_return_int
    *   ID group on success.
    *   A path ID of 0 means that there is no valid ID specified.
    *   Error bit is set on error.
    */
-  public function get_id_sort() {
-    if (!is_int($this->id_sort)) {
+  public function get_id_group() {
+    if (!is_int($this->id_group)) {
       return c_base_return_int::s_new(0);
     }
 
-    return c_base_return_int::s_new($this->id_sort);
+    return c_base_return_int::s_new($this->id_group);
   }
 
+
   /**
    * Gets the is content boolean setting.
    *
@@ -766,6 +811,21 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
+   * Gets the is root boolean setting.
+   *
+   * @return c_base_return_bool
+   *   Is root on success.
+   *   Error bit is set on error.
+   */
+  public function get_is_root() {
+    if (!is_bool($this->is_root)) {
+      $this->is_root = FALSE;
+    }
+
+    return c_base_return_bool::s_new($this->is_root);
+  }
+
+  /**
    * Gets the destination field setting.
    *
    * @return c_base_return_string
@@ -834,7 +894,7 @@ abstract class c_base_path extends c_base_return_string {
    *
    * @return c_base_return_float|c_base_return_null
    *   Date locked on success.
-   *   FALSE is returned if the date is not assigned.
+   *   NULL is returned if the date is not assigned.
    *   Error bit is set on error.
    */
   public function get_date_locked() {
@@ -846,88 +906,268 @@ abstract class c_base_path extends c_base_return_string {
   }
 
   /**
-   * Get the results of when this given path is executed.
+   * Get the assigned include path.
    *
-   * @return c_base_return_null|c_base_return_bool|c_base_return_int|c_base_return_float|c_base_return_array
-   *   This can be any of NULL, bool, int, float, or array as defined by this class.
-   *   The class c_base_return can be used as a catch all.
-   *   NULL is intended to represent that execution has not happend or do_execute was called and no operations were performed.
-   *   This does set the error bit.
+   * @return c_base_return_string|c_base_return_null
+   *   Include path string on success.
+   *   NULL is returned if the include path is not assigned.
+   *   Error bit is set on error.
+   */
+  public function get_include() {
+    if (!is_string($this->include)) {
+      return new c_base_return_null();
+    }
+
+    return c_base_return_string::s_new($this->include);
+  }
+
+  /**
+   * Execute using the specified path, rendering the page.
    *
-   * @see: $this->do_execute()
+   * @param c_base_http $http
+   *   The entire HTTP information to allow for the execution to access anything that is necessary.
+   * @param c_base_database $database
+   *   The database object, which is usually used by form and ajax paths.
+   * @param c_base_session &$session
+   *   The current session.
+   * @pram c_base_html &$html
+   *   The HTML object.
+   *   If the content is not renderring HTML, the output parameter should instead be used for providing content.
+   * @param array $settings
+   *   (optional) An array of additional settings that are usually site-specific.
+   *
+   * @return c_base_path_executed
+   *   An executed array object is returned on success.
+   *   An executed array object with error bit set is returned on error.
    */
-  final public function get_processed() {
-    if (is_bool($this->processed)) {
-      if ($this->processed) {
-        return new c_base_return_true();
-      }
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    if (!($database instanceof c_base_database)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'database', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
-      return new c_base_return_false();
+    if (!($http instanceof c_base_http)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'http', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
     }
 
-    if (is_int($this->processed)) {
-      return c_base_return_int::s_new($this->processed);
+    if (!($session instanceof c_base_session)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'session', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
     }
 
-    if (is_float($this->processed)) {
-      return c_base_return_float::s_new($this->processed);
+    if (!($html instanceof c_base_html)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'html', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
     }
 
-    if (is_array($this->processed)) {
-      return c_base_return_array::s_new($this->processed);
+    if (!is_array($settings)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'settings', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
     }
 
-    return new c_base_return_null();
+    return new c_base_path_executed();
   }
 
   /**
-   * Execute using the specified path.
+   * Returns a sanitized string for use as the url path string.
    *
-   * The results of this function are stored in a 'processed' string.
+   * This removes any '../' in the path.
+   * This removes multiple consecutive '/'.
+   * This removes any '/' prefix.
+   * This removes any '/' suffix.
+   * This limits the string size to 256 characters.
    *
-   * @param c_base_http $http
-   *   The entire HTTP information to allow for the execution to access anything that is necessary.
+   * @param string $path
+   *   The path string to sanitize.
    *
-   * @return c_base_return_null|c_base_return_bool
-   *   NULL is returned if no operation was performed.
-   *   TRUE is returned if function executed.
-   *   FALSE is returned if function was not execution, but should have.
-   *   NULL is returned with error bit set if no operation was performed because $http is an invalid parameter.
-   *   TRUE is returned with error bit set if function executed but an error occured.
-   *   FALSE is returned with error bit set if function did not execute, but should have, and an error occured.
+   * @return string
+   *   The sanitized string.
+   */
+  protected static function pr_sanitize_path($path) {
+    // do not support '../' in the url paths (primarily for security reasons).
+    $sanitized = preg_replace('@^\.\./(\.\./)*@', '', $path);
+    $sanitized = preg_replace('@/(\.\./)+@', '/', $sanitized);
+
+    // remove redundant path parts, such as replacing '//////' with '/'.
+    $sanitized = preg_replace('@/(/)+@', '/', $sanitized);
+
+    // remove leading and trailing slashes.
+    $sanitized = preg_replace('@(^/|/$)@', '', $sanitized);
+
+    // enforce a max path size of 256 characters.
+    $sanitized = c_base_utf8::s_substring($sanitized, 0, 256);
+
+    return $sanitized->get_value_exact();
+  }
+
+  /**
+   * @todo: write type sanitization code by implementing protected functions that can be overwritten by the calling class as necessary.
+   *
+   * It may be useful to provide the variables, such as $http, $database, $settings, etc.., to this class to allow for more advanced handling of types.
+   *
+   * @param string $id
+   *   The unique id representing the value.
+   * @param int $type
+   *   The data type to sanitize the value as.
+   * @param string|int|null $type_sub
+   *   (optional) a sub-type to provide additional customization on each type.
+   *   This is intended to be used in site-specific ways and is not implemented by the base project.
+   *   Instead, extend this class and implement this function to utilize $type_sub.
+   *   If this is not a string, int, or null, it is forced to be NULL without an error bit being assigned.
+   *
+   * @return c_base_return_value|c_base_return_null
+   *   The sanitized value, in whatever form it needs to be.
+   *   NULL is returned if unable to process or value is supposed to be NULL.
+   *   NULL is returned with error bit set on error.
+   */
+  protected function pr_sanitize($id, $type, $type_sub = NULL) {
+    if (!is_string($id) && mb_strlen($id) > 0) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'id', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(NULL, 'c_base_return_null', $error);
+    }
+
+    if (!is_int($type)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'type', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(NULL, 'c_base_return_null', $error);
+    }
+
+    if (!is_array($_POST) || !array_key_exists($id, $_POST)) {
+      return new c_base_return_null();
+    }
+
+    if (!is_null($type_sub) && !is_int($type_sub) && !is_string($type_sub)) {
+      $type_sub = NULL;
+    }
+
+    // @fixme: $value is returned unsanitized until this code is implemented.
+    return c_base_return_value::s_new($_POST[$id]);
+  }
+}
+
+/**
+ * A generic class for provided returns values of the do_execute() functions from c_base_path.
+ *
+ * The array value of this class is intended to be used for any additional return values (such as form errors).
+ * The $cookies is meant to hold any HTTP cookies to be processed after the execution.
+ * The $output is meant to hold the output for any non-HTML content in the event that HTML is not to be renderred..
+ *
+ * @see: c_base_path
+ */
+class c_base_path_executed extends c_base_return_array {
+  private $cookies = NULL;
+  private $output  = NULL;
+  private $form    = NUll;
+
+
+  /**
+   * Class constructor.
+   */
+  public function __construct() {
+    parent::__construct();
+
+    $this->cookies = NULL;
+    $this->output  = NULL;
+    $this->form    = NULL;
+  }
+
+  /**
+   * Class destructor.
+   */
+  public function __destruct() {
+    unset($this->cookies);
+    unset($this->output);
+    unset($this->form);
+
+    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());
+  }
+
+  /**
+   * Assign cookies.
+   *
+   * @param c_base_cookie
+   *   The cookie to assign.
+   * @param bool $append
+   *   (optional) When TRUE the $cookie is appended.
+   *   When FALSE, the array is reset with $cookie as the only value in the array.
    *
-   * @see: $this->get_processed();
+   * @return c_base_return_status
+   *   TRUE is returned on success.
+   *   FALSE with error bit set is returned on error.
    */
-  public function do_execute($http) {
-    if (!($http instanceof c_base_http)) {
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'c_base_http', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
-      return c_base_return_error::s_value(0, 'c_base_return_null', $error);
+  public function set_cookies($cookie, $append = TRUE) {
+    if (!($cookie instanceof c_base_cookie)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'cookie', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
     }
 
-    if (!is_null($this->processed)) {
-      $this->processed = NULL;
+    if (!is_bool($append)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'append', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
     }
 
-    return new c_base_return_null();
+    if (!$append || !is_array($this->cookies)) {
+      $this->cookies = array();
+    }
+
+    $this->cookies[] = $cookie;
+    return new c_base_return_true();
   }
 
   /**
-   * Returns a sanitized string for use as the url path string.
+   * Assign output.
    *
-   * This removes multiple consecutive '/'.
-   * This removes any '/' prefix.
-   * This removes any '/' suffix.
-   * This limits the string size to 256 characters.
+   * @param c_base_value
+   *   The output to assign.
    *
-   * @return string
-   *   The sanitized string.
+   * @return c_base_return_status
+   *   TRUE is returned on success.
+   *   FALSE with error bit set is returned on error.
+   */
+  public function set_output($output) {
+    if (!($output instanceof c_base_value)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'output', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    $this->output = $output;
+    return new c_base_return_true();
+  }
+
+  /**
+   * Gets the assigned output
+   *
+   * @return c_base_return_value|c_base_return_null
+   *   The assigned output is returned.
+   *   If there is no assigned output (generally when execution is not performed) NULL is returned.
    */
-  private function p_sanitize_value($value) {
-    $value = preg_replace('@/+@i', '/', $value);
-    $value = preg_replace('@(^/|/$)@', '', $value);
-    $value = c_base_utf8::s_substring($value, 0, 256);
+  public function get_output() {
+    if (is_null($this->output)) {
+      return new c_base_return_null();
+    }
 
-    return $value->get_value_exact();
+    return $this->output;
   }
 }
 
@@ -940,8 +1180,8 @@ abstract class c_base_path extends c_base_return_string {
  * Third, the paths are exploded and searched based on all their sub-parts.
  */
 class c_base_paths extends c_base_return {
-  private $root  = NULL;
   private $paths = NULL;
+  private $root  = NULL;
 
 
   /**
@@ -950,16 +1190,16 @@ class c_base_paths extends c_base_return {
   public function __construct() {
     parent::__construct();
 
-    $this->root  = NULL;
     $this->paths = array();
+    $this->root  = NULL;
   }
 
   /**
    * Class destructor.
    */
   public function __destruct() {
-    unset($this->root);
     unset($this->paths);
+    unset($this->root);
 
     parent::__destruct();
   }
@@ -986,57 +1226,110 @@ class c_base_paths extends c_base_return {
   }
 
   /**
-   * Assign the path object string to this class.
+   * Assign a specific path handler.
    *
    * Duplicate paths overwrite previous paths.
    *
-   * @param c_base_path $path
-   *   An implentation of c_base_path to be executed when the path is requested.
-   *   If path value is an empty string, then this is treated as the root path.
+   * @todo: should redirect and alias booleans be added as parameters?
+   *
+   * @pram string $path
+   *   The url path in which the handler applies to.
+   * @param string $handler
+   *   The name of an implementation of c_base_path.
+   * @param string|null $include
+   *   (optional) The file path (relative to the PHP includes) to include that contains the requested path.
    *
    * @return c_base_return_status
    *   TRUE is returned on success.
    *   FALSE with error bit set is returned on error.
    */
-  public function set_path($path) {
-    if (!($path instanceof c_base_path)) {
+  public function set_path($path, $handler, $include = NULL) {
+    if (!is_string($path)) {
       $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
       return c_base_return_error::s_false($error);
     }
 
-    $path_string = $path->get_value();
+    if (!is_string($handler)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'handler', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
 
-    // No default will be specified, so return error if the value is not properly defined.
-    if (!is_string($path_string)) {
-      return new c_base_return_false();
+    if (!is_null($include) && !is_string($include)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'include', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
     }
 
-    if (mb_strlen($path_string) == 0) {
-      $this->root = $path;
+    if (mb_strlen($path) == 0) {
+      $this->root = array('handler' => $handler, 'include' => $include);
       return new c_base_return_true();
     }
 
-    // array is optimized based on the path group id and then the first character of a given path.
-    $ordinal = $this->get_id_path()->get_value_exact();
-    $sort = $this->get_id_sort()->get_value_exact();
+    $path_object = new c_base_path();
+    $valid_path = $path_object->set_value($path);
+
+    if (!$valid_path) {
+      unset($path_object);
+      unset($valid_path);
+
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+    unset($valid_path);
+
+
+    $path_string = $path_object->get_value_exact();
+    unset($path_object);
+
+    // assign each path part to the path
+    $path_parts = explode('/', $path_string);
+    unset($path_string);
+
+
+    // load the path group, if available.
+    $id_group = 0;
+    if (mb_strlen($path_parts[0]) == 1) {
+      $ordinal = ord($path_parts[0]);
+      if (in_array($ordinal, c_base_defaults_global::RESERVED_PATH_GROUP)) {
+        $id_group = $ordinal;
+      }
+      unset($ordinal);
+      unset($path_pats[0]);
+    }
 
     if (!is_array($this->paths)) {
       $this->paths = array();
     }
 
-    if (!array_key_exists($ordinal, $this->paths)) {
-      $this->paths[$ordinal] = array();
+    if (!array_key_exists($id_group, $this->paths)) {
+      $this->paths[$id_group] = array();
     }
 
-    if (!array_key_exists($sort, $this->paths[$ordinal])) {
-      $this->paths[$ordinal][$sort] = array();
-    }
+    $path_tree = &$this->paths[$id_group];
 
-    $path_parts = explode('/', $path);
+    $depth_current = 1;
+    $depth_total = count($path_parts);
+    foreach ($path_parts as $path_part) {
+      if ($depth_current == $depth_total) {
+        $path_tree['include'] = $include;
+        $path_tree['handler'] = $handler;
+        break;
+      }
 
-    // @todo: explode this into parts and place them into array.
-    //        the line below is incorrect, but is used as a notation until I finish writing this.
-    //$this->paths[$ordinal][$sort][$path_string] = $path;
+      if (!isset($path_tree['paths'][$path_part])) {
+        $path_tree['paths'][$path_part] = array(
+          'paths' => array(),
+          'include' => NULL,
+          'handler' => NULL,
+        );
+      }
+
+      $path_tree = &$path_tree['paths'][$path_part];
+      $depth_current++;
+    }
+    unset($path_part);
+    unset($path_parts);
+    unset($depth_current);
+    unset($depth_total);
 
     return new c_base_return_true();
   }
@@ -1044,17 +1337,115 @@ class c_base_paths extends c_base_return {
   /**
    * Gets a path object for the specified path.
    *
-   * @param string $path_string
+   * @param string|null $path_string
    *   The URL path without any path arguments.
    *   This does not accept wildcards.
-   *
-   * @return c_base_path|c_base_status
-   *   A path object is returned if the path matches, with wildcards.
+   *   Set to NULL or an empty string for the root path.
+   *
+   * @return c_base_return_array|c_base_return_int|c_base_return_null
+   *   An array containing:
+   *   - 'include': the file to include that contains the handler class implementation.
+   *   - 'handler': the name of the handler class.
+   *   - 'redirect': if specified, then a redirect path (instead of include/handler).
+   *   - 'code': if redirect is specified, then the http response code associated with the redirect.
    *   Wildcards are matched after all non-wildcards.
-   *   FALSE without error bit set is return if path was not found.
+   *   NULL is returned if not found.
    *   FALSE with error bit set is returned on error.
+   *
+   * @see: self::set_login()
+   * @see: self::process_path()
    */
-  public function get_path($path_string) {
-    // @todo
+  public function find_path($path_string) {
+    if (!is_null($path_string) && !is_string($path_string)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'path_string', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (is_null($path_string) || mb_strlen($path_string) == 0) {
+      if (is_array($this->root)) {
+        return $this->root;
+      }
+
+      return new c_base_return_null();
+    }
+
+
+    // sanitize the url path.
+    $path = new c_base_path();
+    if (!$path->set_value($path_string)) {
+      unset($path);
+
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':format_name' => 'path_string', ':expected_format' => 'Valid URL path', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_FORMAT);
+      return c_base_return_error::s_false($error);
+    }
+
+    $sanitized = $path->get_value_exact();
+    unset($path);
+
+    // if the sanitized path is different from the original, then send a url redirect.
+    if (strcmp($path_string, $sanitized) != 0) {
+      return c_base_return_array::s_new(array('redirect' => $sanitized, 'code' => c_base_http_status::MOVED_PERMANENTLY));
+    }
+
+    $path_parts = explode('/', $sanitized);
+    unset($sanitized);
+
+
+    // load the path group, if available.
+    $id_group = 0;
+    if (mb_strlen($path_parts[0]) == 1) {
+      $ordinal = ord($path_parts[0]);
+      if (in_array($ordinal, c_base_defaults_global::RESERVED_PATH_GROUP)) {
+        $id_group = $ordinal;
+      }
+      unset($ordinal);
+      unset($path_pats[0]);
+    }
+
+
+    $depth_current = 1;
+    $depth_total = count($path_parts);
+    $found = NULL;
+    $path_tree = &$this->paths[$sort];
+    foreach ($path_parts as $path_part) {
+      if ($depth_current == $depth_total) {
+        if (isset($path_tree['handler'])) {
+          $found = array('include' => $path_tree['include'], 'handler' => $path_tree['handler']);
+          break;
+        }
+      }
+
+      if (!isset($path_tree['paths'][$path_part])) {
+        if ($depth_current == $depth_total) {
+          if (isset($path_tree['handler'])) {
+            $found = array('include' => $path_tree['include'], 'handler' => $path_tree['handler']);
+            break;
+          }
+        }
+
+        if (isset($path_tree['paths']['%'])) {
+          $path_tree = &$path_tree['paths']['%'];
+          $depth_current++;
+          continue;
+        }
+
+        break;
+      }
+
+      $path_tree = &$path_tree['paths'][$path_part];
+      $depth_current++;
+    }
+    unset($path_part);
+    unset($path_parts);
+    unset($depth_current);
+    unset($depth_total);
+    unset($path_tree);
+
+    if (is_array($found)) {
+      return c_base_return_array::s_new($found);
+    }
+    unset($found);
+
+    return new c_base_return_null();
   }
 }
index 69ce1479da83b76c9400da95b78e0f283373c1cf..fc36562f1bb0baad2cee3b64b70ddb4234ec75e9 100644 (file)
@@ -405,10 +405,23 @@ class c_base_return_status extends c_base_return {
 class c_base_return_true extends c_base_return_status {
 
   /**
+   * Assign the value.
+   *
+   * @param $value
+   *   This is ignored.
+   *
+   * @return bool
+   *   Always returns TRUE.
+   */
+  public function set_value($value) {
+    return TRUE;
+  }
+
+  /**
    * Return the value.
    *
    * @return bool $value
-   *   The value within this class.
+   *   Always returns TRUE.
    */
   public function get_value() {
     return TRUE;
@@ -418,7 +431,7 @@ class c_base_return_true extends c_base_return_status {
    * Return the value of the expected type.
    *
    * @return bool $value
-   *   The value c_base_markup_tag stored within this class.
+   *   Always returns TRUE.
    */
   public function get_value_exact() {
     return TRUE;
@@ -433,10 +446,23 @@ class c_base_return_true extends c_base_return_status {
 class c_base_return_false extends c_base_return_status {
 
   /**
+   * Assign the value.
+   *
+   * @param $value
+   *   This is ignored.
+   *
+   * @return bool
+   *   Always returns TRUE.
+   */
+  public function set_value($value) {
+    return TRUE;
+  }
+
+  /**
    * Return the value.
    *
    * @return bool $value
-   *   The value within this class.
+   *   Always returns FALSE.
    */
   public function get_value() {
     return FALSE;
@@ -446,7 +472,7 @@ class c_base_return_false extends c_base_return_status {
    * Return the value of the expected type.
    *
    * @return bool $value
-   *   The value c_base_markup_tag stored within this class.
+   *   Always returns FALSE.
    */
   public function get_value_exact() {
     return FALSE;
@@ -459,6 +485,39 @@ class c_base_return_false extends c_base_return_status {
  * This class will not have any values.
  */
 class c_base_return_null extends c_base_return_status {
+
+  /**
+   * Assign the value.
+   *
+   * @param $value
+   *   This is ignored.
+   *
+   * @return bool
+   *   Always returns TRUE.
+   */
+  public function set_value($value) {
+    return TRUE;
+  }
+
+  /**
+   * Return the value.
+   *
+   * @return $value
+   *   Always returns NULL.
+   */
+  public function get_value() {
+    return NULL;
+  }
+
+  /**
+   * Return the value of the expected type.
+   *
+   * @return $value
+   *   Always returns NULL.
+   */
+  public function get_value_exact() {
+    return NULL;
+  }
 }
 
 /**
@@ -504,7 +563,7 @@ class c_base_return_value extends c_base_return {
    */
   public function get_value() {
     if (!isset($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -518,7 +577,7 @@ class c_base_return_value extends c_base_return {
    */
   public function get_value_exact() {
     if (!isset($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -582,7 +641,7 @@ class c_base_return_bool extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !is_bool($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -596,7 +655,7 @@ class c_base_return_bool extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!is_bool($this->value)) {
-      $this->value = FALSE;
+      return FALSE;
     }
 
     return $this->value;
@@ -659,7 +718,7 @@ class c_base_return_string extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !is_string($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -673,7 +732,7 @@ class c_base_return_string extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!is_string($this->value)) {
-      $this->value = '';
+      return '';
     }
 
     return $this->value;
@@ -743,7 +802,7 @@ class c_base_return_int extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !is_int($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -757,7 +816,7 @@ class c_base_return_int extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!is_int($this->value)) {
-      $this->value = 0;
+      return 0;
     }
 
     return $this->value;
@@ -827,7 +886,7 @@ class c_base_return_float extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !is_float($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -841,7 +900,7 @@ class c_base_return_float extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!is_float($this->value)) {
-      $this->value = 0.0;
+      return 0.0;
     }
 
     return $this->value;
@@ -985,6 +1044,87 @@ class c_base_return_array extends c_base_return_value {
   }
 
   /**
+   * Append the value at the end of the array.
+   *
+   * @param array $value
+   *   Any value so long as it is an array.
+   *   NULL is not allowed.
+   * @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_append($value, $type = NULL) {
+    // when type is not supplied, return a generic type.
+    if (is_null($type)) {
+      $this->value[] = $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[] = $value;
+    return TRUE;
+  }
+
+  /**
    * Return the value.
    *
    * @return array|null $value
@@ -993,7 +1133,7 @@ class c_base_return_array extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !is_array($this->value)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -1007,7 +1147,7 @@ class c_base_return_array extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!is_array($this->value)) {
-      $this->value = array();
+      return array();
     }
 
     return $this->value;
@@ -1126,6 +1266,34 @@ class c_base_return_array extends c_base_return_value {
 
     return $this->value[$key];
   }
+
+  /**
+   * Return the total number of values in the array.
+   *
+   * @return int
+   *   A positive integer.
+   */
+  public function get_value_count() {
+    if (empty($this->value)) {
+      return 0;
+    }
+
+    return count($this->value);
+  }
+
+  /**
+   * Return the array keys assigned to the value.
+   *
+   * @return array
+   *   An array of array keys.
+   */
+  public function get_value_keys() {
+    if (empty($this->value)) {
+      return array();
+    }
+
+    return array_keys($this->value);
+  }
 }
 
 /**
index 40542c598892ea1f7f081943cb498e85ff379ad8..be02c3a8a0af79866a7bbcd24dbf54804793c241 100644 (file)
@@ -2502,7 +2502,7 @@ abstract class c_base_rfc_string extends c_base_rfc_char {
       elseif (self::pr_rfc_char_is_pchar($code)) {
         // do nothing, valid.
       }
-      elseif ($code == c_base_asccii::SLASH_FORWARD) {
+      elseif ($code == c_base_ascii::SLASH_FORWARD) {
         // do nothing, valid.
       }
       else {
@@ -2592,7 +2592,7 @@ abstract class c_base_rfc_string extends c_base_rfc_char {
       elseif (self::pr_rfc_char_is_pchar($code)) {
         // do nothing, valid.
       }
-      elseif ($code == c_base_asccii::SLASH_FORWARD || $code == c_base_asccii::QUESTION_MARK) {
+      elseif ($code == c_base_ascii::SLASH_FORWARD || $code == c_base_ascii::QUESTION_MARK) {
         // do nothing, valid.
       }
       else {
@@ -2818,6 +2818,267 @@ abstract class c_base_rfc_string extends c_base_rfc_char {
   }
 
   /**
+   * Processes a string based on the syntax: numeric.
+   *
+   * This is not part of any specific rfc.
+   *
+   * A string that has the following syntax:
+   * - *(wsp) [('-' | '+')] 1*(DIGIT) ['.' . 1*(DIGIT)] *(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.
+   *
+   * @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_digit()
+   */
+  protected function pr_rfc_string_is_numeric($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;
+    }
+
+
+    // ignore leading whitespaces
+    $result = $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+    if ($result['invalid']) {
+      return $result;
+    }
+
+    // no numbers found.
+    if ($result['current'] >= $stop) {
+      $result['invalid'] = TRUE;
+      return $result;
+    }
+
+
+    // first look for a leading positive or negative sign.
+    if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+      // @fixme: should error be reported? do some debugging with this.
+      $result['invalid'] = TRUE;
+      return $result;
+    }
+
+    $code = $ordinals[$result['current']];
+    if ($code === c_base_ascii::MINUS && $code === c_base_ascii::PLUS) {
+      $result['text'] .= $code;
+      $result['current']++;
+
+      // must have a digit in addition to the leading +/-.
+      if ($result['current'] >= $stop) {
+        unset($code);
+
+        $result['invalid'] = TRUE;
+        return $result;
+      }
+    }
+    unset($code);
+
+
+    // look for digit, but only allow a single period.
+    $found_period = FALSE;
+    for (; $result['current'] < $stop; $result['current']++) {
+      if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+        // @fixme: should error be reported? do some debugging with this.
+        $result['invalid'] = TRUE;
+        break;
+      }
+
+      $code = $ordinals[$result['current']];
+
+      if ($code === c_base_ascii::PERIOD) {
+        if ($found_period) {
+          $result['invalid'] = TRUE;
+          break;
+        }
+
+        $found_period = TRUE;
+      }
+      elseif (!$this->pr_rfc_char_is_digit($ordinals[$result['current']])) {
+        // ignore trailing whitespaces
+        if ($this->pr_rfc_char_is_wsp($ordinals[$result['current']])) {
+          $result = $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+          if ($result['invalid']) {
+            unset($found_period);
+            return $result;
+          }
+
+          if ($result['current'] >= $stop) {
+            // this is not an error, because only whitespace was found at the end of the number.
+            break;
+          }
+        }
+
+        $result['invalid'] = TRUE;
+        break;
+      }
+
+      $result['text'] .= $characters[$result['current']];
+    }
+    unset($code);
+    unset($found_period);
+
+    // if there exists only a single period, then this is not a valid number.
+    if ($result['text'] == '.') {
+      $result['text'] = '';
+      $result['invalid'] = TRUE;
+    }
+
+    return $result;
+  }
+
+  /**
+   * Processes a string based on the syntax: hexadecimal numeric.
+   *
+   * This is not part of any specific rfc.
+   *
+   * A string that has the following syntax:
+   * - *(wsp) [('-' | '+')] 1*(HEXDIGIT) ['.' . 1*(HEXDIGIT)] *(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.
+   *
+   * @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_hexdigit()
+   */
+  protected function pr_rfc_string_is_hexanumeric($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;
+    }
+
+
+    // ignore leading whitespaces
+    $result = $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+    if ($result['invalid']) {
+      return $result;
+    }
+    unset($result);
+
+    // no numbers found.
+    if ($result['current'] >= $stop) {
+      $result['invalid'] = TRUE;
+      return $result;
+    }
+
+
+    // first look for a leading positive or negative sign.
+    if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+      // @fixme: should error be reported? do some debugging with this.
+      $result['invalid'] = TRUE;
+      return $result;
+    }
+
+    $code = $ordinals[$result['current']];
+    if ($code === c_base_ascii::MINUS && $code === c_base_ascii::PLUS) {
+      $result['text'] .= $code;
+      $result['current']++;
+
+      // must have a digit in addition to the leading +/-.
+      if ($result['current'] >= $stop) {
+        unset($code);
+
+        $result['invalid'] = TRUE;
+        return $result;
+      }
+    }
+    unset($code);
+
+
+    // look for digit, but only allow a single period.
+    $found_period = FALSE;
+    for (; $result['current'] < $stop; $result['current']++) {
+      if (!array_key_exists($result['current'], $ordinals) || !array_key_exists($result['current'], $characters)) {
+        // @fixme: should error be reported? do some debugging with this.
+        $result['invalid'] = TRUE;
+        break;
+      }
+
+      $code = $ordinals[$result['current']];
+
+      if ($code === c_base_ascii::PERIOD) {
+        if ($found_period) {
+          $result['invalid'] = TRUE;
+          break;
+        }
+
+        $found_period = TRUE;
+      }
+      elseif (!$this->pr_rfc_char_is_hexdigit($ordinals[$result['current']])) {
+        // ignore trailing whitespaces
+        if ($this->pr_rfc_char_is_wsp($ordinals[$result['current']])) {
+          $result = $this->p_rfc_string_skip_past_whitespace($ordinals, $characters, $stop, $result);
+          if ($result['invalid']) {
+            unset($found_period);
+            return $result;
+          }
+
+          if ($result['current'] >= $stop) {
+            // this is not an error, because only whitespace was found at the end of the number.
+            break;
+          }
+        }
+
+        $result['invalid'] = TRUE;
+        break;
+      }
+
+      $result['text'] .= $characters[$result['current']];
+    }
+    unset($code);
+    unset($found_period);
+
+    // if there exists only a single period, then this is not a valid number.
+    if ($result['text'] == '.') {
+      $result['text'] = '';
+      $result['invalid'] = TRUE;
+    }
+
+    return $result;
+  }
+
+  /**
    * Processes a string based on the rfc syntax: vchar except for semi-colon and comma.
    *
    * A string that has the following syntax:
index 39065d1bb99cba6fd400e2ca92914acadfc5a746..79a76cd51999d5e523b4cca5396e24cc708cbd17 100644 (file)
@@ -207,8 +207,8 @@ class c_theme_dom extends DOMDocument {
     }
 
     $markup = '';
-    if ($parent->hasChildNodes() > 0) {
-      foreach ($parent->childNodes as $child) {
+    if (parent::hasChildNodes() > 0) {
+      foreach (parent::childNodes as $child) {
         $markup .= $this->saveHTML($child);
       }
     }
@@ -253,7 +253,7 @@ class c_theme_dom extends DOMDocument {
     }
 
     if ($parent instanceOf DOMNode) {
-      $child = $parent->replaceChild($new, $element);
+      $child = parent::replaceChild($new, $element);
     }
     else {
       $this->appendChild($element);
@@ -298,7 +298,7 @@ class c_theme_dom extends DOMDocument {
   protected function pr_change_elements($type, $parent) {
     $result = TRUE;
 
-    $elements = $parent->getElementsByTagName($type);
+    $elements = parent::getElementsByTagName($type);
     foreach ($elements as $element) {
       if ($element instanceof DOMNode) {
         $result = $this->pr_change_element($element, $type);
@@ -354,7 +354,7 @@ class c_theme_dom extends DOMDocument {
         $removed_child = $element->removeChild($child);
 
         if (is_object($removed_child)) {
-          $parent->insertBefore($removed_child, $element);
+          parent::insertBefore($removed_child, $element);
         }
       }
       unset($child);
@@ -362,7 +362,7 @@ class c_theme_dom extends DOMDocument {
       unset($children);
     }
 
-    $child = $parent->removeChild($element);
+    $child = parent::removeChild($element);
     unset($parent);
 
     if ($child instanceof DOMNode) {
@@ -397,7 +397,7 @@ class c_theme_dom extends DOMDocument {
   protected function pr_remove_elements($type, $parent, $preserve_children = TRUE) {
     $result = TRUE;
 
-    $elements = $parent->getElementsByTagName($type);
+    $elements = parent::getElementsByTagName($type);
     foreach ($elements as $element) {
       $result = $this->pr_remove_element($element, $preserve_children);
 
@@ -466,7 +466,7 @@ class c_theme_return_c_theme_dom extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !($this->value instanceof c_theme_dom)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -480,7 +480,7 @@ class c_theme_return_c_theme_dom extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!($this->value instanceof c_theme_dom)) {
-      $this->value = new c_theme_dom();
+      return new c_theme_dom();
     }
 
     return $this->value;
@@ -544,7 +544,7 @@ class c_theme_return_dom_node extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !($this->value instanceof DOMNode)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -558,7 +558,7 @@ class c_theme_return_dom_node extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!($this->value instanceof DOMNode)) {
-      $this->value = new DOMNode();
+      return new DOMNode();
     }
 
     return $this->value;
@@ -622,7 +622,7 @@ class c_theme_return_dom_comment extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !($this->value instanceof DOMComment)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -636,7 +636,7 @@ class c_theme_return_dom_comment extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!($this->value instanceof DOMComment)) {
-      $this->value = new DOMComment();
+      return new DOMComment();
     }
 
     return $this->value;
@@ -700,7 +700,7 @@ class c_theme_return_dom_element extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !($this->value instanceof DOMElement)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -714,7 +714,7 @@ class c_theme_return_dom_element extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!($this->value instanceof DOMElement)) {
-      $this->value = new DOMElement();
+      return new DOMElement();
     }
 
     return $this->value;
@@ -778,7 +778,7 @@ class c_theme_return_dom_text extends c_base_return_value {
    */
   public function get_value() {
     if (!is_null($this->value) && !($this->value instanceof DOMText)) {
-      $this->value = NULL;
+      return NULL;
     }
 
     return $this->value;
@@ -792,7 +792,7 @@ class c_theme_return_dom_text extends c_base_return_value {
    */
   public function get_value_exact() {
     if (!($this->value instanceof DOMText)) {
-      $this->value = new DOMText();
+      return new DOMText();
     }
 
     return $this->value;
index b4751744ef403dcbf99c39f583ae8e8e91983357..1aee047202c2ab98926037e01fe7db5e5afc62a7 100644 (file)
@@ -5,6 +5,7 @@ Paths that begin with the following:
  /c/ = Cache Paths.
  /d/ = Data Paths, such as RSS Feeds.
  /f/ = File Paths.
+ /s/ = Form Submit Paths.
  /t/ = Theme Paths.
  /u/ = User Paths (similar to /a/, but focused on users or custom user content).
  /x/ = Ajax Paths.
@@ -29,6 +30,11 @@ Example /f/ paths:
 - /f/c/% - Load file '%' by file checksum.
 - /f/s/% - Load checksum for file '%'.
 
+Example /s/ paths:
+- /s/login - Login form
+- /s/logout - Logout form
+- /s/my_form_id/% - "My_form_id" form with parameter '%'.
+
 Example /t/ paths:
 - /t/main/css/all.css - All css file from main theme.
 - /t/alternate/js/slider.js - Slider javascript file from alternate.
index 33b690456579388033aff5f712f00d7268184b91..89e21bfd0fef9b0c8bd6493da477ba5d6c0310d0 100644 (file)
@@ -7,6 +7,7 @@
   require_once('common/base/classes/base_defaults_global.php');
 
   require_once('common/base/classes/base_http.php');
+  require_once('common/base/classes/base_http_status.php');
   require_once('common/base/classes/base_cookie.php');
   require_once('common/base/classes/base_ldap.php');
   require_once('common/base/classes/base_markup.php');
@@ -20,6 +21,7 @@
   require_once('program/reservation/reservation_database.php');
   require_once('program/reservation/reservation_session.php');
   require_once('program/reservation/reservation_paths.php');
+  require_once('program/reservation/reservation_redirects.php');
 
   /**
    * Load all custom settings.
     $settings['ldap_base_dn'] = 'ou=users,ou=People';
     $settings['ldap_fields'] = array('mail', 'gecos', 'givenname', 'cn', 'sn', 'employeenumber');
 
+    // base settings
+    $settings['base_scheme'] = 'https';
+    $settings['base_host'] = 'localhost';
+    $settings['base_path'] = $settings['cookie_path'];
+
     // default supported languages.
     c_base_defaults_global::s_set_languages(new c_base_language_limited());
 
     $http = new c_base_http();
     $http->do_load_request();
 
+    // Assign a default response protocol.
+    $http->set_response_protocol('HTTP/1.1');
+
+    // Assign a default response status (expected to be overridden by path handlers).
+    $http->set_response_status(c_base_http_status::OK);
+
     return $http;
   }
 
 
     // when the headers are sent, checksums are created, so at this point all error output should be stored and not sent.
     $http->send_response_headers(TRUE);
-    flush();
 
 
     // once the header are sent, send the content.
     $http->send_response_content();
-    flush();
 
 
     ini_set('output_buffering', $old_output_buffering);
    *   System settings
    * @param c_base_session &$session
    *   Session information.
+   *
+   * @return c_base_html|c_base_return_array
+   *   The generated html is returned on success.
+   *   In certain cases, an array is returned for special handling, such as redirects.
    */
   function reservation_process_request(&$http, &$database, &$settings, &$session) {
     $html = new c_base_html();
     $html->set_header($tag);
     unset($tag);
 
-    // finish building pages.
     if (!isset($_SERVER["HTTPS"])) {
-      reservation_build_page_require_https($html, $settings, $session);
+      //reservation_login_page_require_https($settings, $session, $html);
+      // @todo: provide custom https required page.
+      return $html;
     }
-    elseif ($settings['database_user'] == 'u_reservation_public') {
-      // if the session cookie exists, but the user is still u_reservation_public, then the cookie is no longer valid.
-      if (empty($session->get_session_id()->get_value_exact())) {
-        // check to see if user has filled out the login form.
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['form_id']) && $_POST['form_id'] == 'login_form') {
-          $logged_in = reservation_attempt_login($database, $settings, $session);
-
-          if ($logged_in instanceof c_base_return_true) {
-            reservation_build_page_dashboard($html, $settings, $session);
-            // @todo: process and handle different paths here and load page as requested.
-          }
-          else {
-            // store the problems in the session object (because session as a subclass of c_base_return).
-            $session->set_problems($logged_in->get_value_exact());
-
-            // @todo: render login failure.
-            reservation_process_path_public($html, $settings, $session);
-          }
-          unset($logged_in);
-        }
-        else {
-          reservation_process_path_public($html, $settings, $session);
-        }
-      }
-      else {
-        $cookie_login = $session->get_cookie();
 
-        // delete the cookie.
+    $session_user = $session->get_name()->get_value_exact();
+    if (is_null($session_user)) {
+      $logged_in = FALSE;
+
+      // @todo: delete old cookies, if they expire.
+      $cookie_login = $session->get_cookie();
+
+      // @fixme: shouldn't this check be in the session management code?
+      // the session should already be logged into at this point.
+      // if the session id exists, but no user id is defined, then the session is no longer valid, so delete the invalid cookie.
+      if (!empty($session->get_session_id()->get_value_exact())) {
         $cookie_login->set_expires(-1);
         $cookie_login->set_max_age(-1);
         $session->set_cookie($cookie_login);
         unset($cookie_login);
 
-        reservation_process_path_public($html, $settings, $session);
+        $session->set_session_id(NULL);
       }
     }
     else {
-      // load current database settings.
-      reservation_database_string($database, $settings);
-
-      // load current user roles
-      reservation_get_current_roles($database, $settings, $session); // @todo: handle returnr result errors.
-
-      if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['form_id'])) {
-        reservation_process_forms($html, $settings, $session);
-      }
-      else {
-        reservation_process_path($html, $settings, $session);
-      }
+      $logged_in = TRUE;
     }
 
+    $paths = new c_reservation_paths();
+    $paths->reservation_process_path($http, $database, $session, $html, $settings, $logged_in);
+    unset($logged_in);
+    unset($paths);
+
     return $html;
   }
 
 
   /**
    * Main Program Function
+   *
+   * note: Future designs will likely include content caching.
+   *       There are different designs based on the type of content that can be used for caching.
+   *       The following are some common generic areas to cache:
+   *       - design 1 (public content): 3, 4. 5 (cache handling happens between 2 and 3).
+   *       - design 2 (database bypass): 4 (4 is to be replaced with cache handling).
+   *       - design 3 (theme bypass): 4. 5 (4 is to be replaced with cache handling).
+   *       - design 4 (full private cache): 4. 5, 6* (should still handling login access, only (vary) headers are to be changed in 6).
+   *       - design 5 (full public cache): 3, 4. 5, 6* (should still handling login access, only (vary) headers are to be changed in 6).
+   *
+   *       It is also recommended that some placeholders are added to the css body to provide dynamic css class names, even on cached content.
    */
   function reservation_main() {
     // 1: local settings:
diff --git a/program/reservation/internal/access_denied.php b/program/reservation/internal/access_denied.php
new file mode 100644 (file)
index 0000000..703d22c
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the access denied pages.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_html.php');
+require_once('common/base/classes/base_cookie.php');
+require_once('common/base/classes/base_session.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+final class c_reservation_path_access_denied extends c_base_path {
+
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'error-path', 'error-path-access_denied', 'error-path-access_denied'));
+
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('Access Denied');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    // Content
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('You are not authorized to access this resource.');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+
+    // assign HTTP response status.
+    $http->set_response_status(c_base_http_status::FORBIDDEN);
+
+
+    return $executed;
+  }
+}
diff --git a/program/reservation/internal/bad_method.php b/program/reservation/internal/bad_method.php
new file mode 100644 (file)
index 0000000..7a22ca0
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the not found pages.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_html.php');
+require_once('common/base/classes/base_cookie.php');
+require_once('common/base/classes/base_session.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+final class c_reservation_path_not_found extends c_base_path {
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // @todo: This needs to return the HTTP invalid method response status.
+
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'error-path', 'error-path', 'error-path-bad_method'));
+
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('Bad Method');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    // Content
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('The provided HTTP request method is either unsupported or invalid for the request path.');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+
+    // assign HTTP response status.
+    $http->set_response_status(c_base_http_status::METHOD_NOT_ALLOWED);
+
+
+    return $executed;
+  }
+}
diff --git a/program/reservation/internal/not_found.php b/program/reservation/internal/not_found.php
new file mode 100644 (file)
index 0000000..3ae847d
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the not found pages.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_html.php');
+require_once('common/base/classes/base_cookie.php');
+require_once('common/base/classes/base_session.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+final class c_reservation_path_not_found extends c_base_path {
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'error-path', 'error-path', 'error-path-not_found'));
+
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('Page Not Found');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    // Content
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('The page you requested is not available.');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+
+    // assign HTTP response status.
+    $http->set_response_status(c_base_http_status::NOT_FOUND);
+
+    return $executed;
+  }
+}
diff --git a/program/reservation/paths/a/reservation_dashboard.php b/program/reservation/paths/a/reservation_dashboard.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/program/reservation/paths/u/dashboard.php b/program/reservation/paths/u/dashboard.php
new file mode 100644 (file)
index 0000000..75139ab
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the user dashboard.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_html.php');
+require_once('common/base/classes/base_cookie.php');
+require_once('common/base/classes/base_session.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+final class c_reservation_path_user_dashboard extends c_base_path {
+
+  /**
+   * Class constructor.
+   */
+  public function __construct() {
+    parent::__construct();
+
+    $this->is_root = TRUE;
+  }
+
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'dashboard-user', 'dashboard-user'));
+
+
+    // Dashboard Content
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('Dashboard');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('All links will go here.');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    $roles = array();
+    $roles_object = $session->get_setting('roles');
+    if ($roles_object instanceof c_base_roles) {
+      $roles = $roles_object->get_roles()->get_value_exact();
+    }
+    unset($roles_object);
+
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('You are currently logged in as: ' . $settings['database_user']);
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('You are currently assigned the following roles:');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    $tag_ul = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_UNORDERED_LIST);
+
+    foreach ($roles as $role) {
+      $tag_li = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LIST_ITEM);
+
+      switch ($role) {
+        case c_base_roles::PUBLIC:
+          $tag_li->set_text('Public');
+          break;
+        case c_base_roles::USER:
+          $tag_li->set_text('User');
+          break;
+        case c_base_roles::REQUESTER:
+          $tag_li->set_text('Requester');
+          break;
+        case c_base_roles::DRAFTER:
+          $tag_li->set_text('Drafter');
+          break;
+        case c_base_roles::EDITOR:
+          $tag_li->set_text('Editor');
+          break;
+        case c_base_roles::REVIEWER:
+          $tag_li->set_text('Reviewer');
+          break;
+        case c_base_roles::FINANCER:
+          $tag_li->set_text('Financer');
+          break;
+        case c_base_roles::INSURER:
+          $tag_li->set_text('Insurer');
+          break;
+        case c_base_roles::PUBLISHER:
+          $tag_li->set_text('Publisher');
+          break;
+        case c_base_roles::AUDITOR:
+          $tag_li->set_text('Auditor');
+          break;
+        case c_base_roles::MANAGER:
+          $tag_li->set_text('Manager');
+          break;
+        case c_base_roles::ADMINISTER:
+          $tag_li->set_text('Administer');
+          break;
+      }
+
+      $tag_ul->set_tag($tag_li);
+      unset($tag_li);
+    }
+    unset($role);
+
+    $wrapper->set_tag($tag_ul);
+
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+    return $executed;
+  }
+}
diff --git a/program/reservation/paths/u/login.php b/program/reservation/paths/u/login.php
new file mode 100644 (file)
index 0000000..bc4ee30
--- /dev/null
@@ -0,0 +1,462 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the login process.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_http_status.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+
+/**
+ * Provides a form for the user login.
+ *
+ * This listens on: /u/login
+ */
+final class c_reservation_path_user_login extends c_base_path {
+
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // handle any resulting errors.
+    $problem_fields = array();
+    $problem_messages = array();
+
+    // perform login if session information is provided.
+    if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
+      $login_result = $this->p_do_login($database, $session, $settings);
+
+      if ($login_result instanceof c_base_return_array) {
+        $problems = $login_result->get_value_exact();
+
+        foreach ($problems as $problem) {
+          $fields = $problem->get_fields();
+          if ($fields instanceof c_base_return_array) {
+            foreach ($fields->get_value_exact() as $field) {
+              $problem_fields[] = $field;
+            }
+            unset($field);
+          }
+          unset($fields);
+
+          $problem_messages[] = $problem->get_value_exact();
+        }
+        unset($problem);
+        unset($problems);
+      }
+      elseif ($login_result instanceof c_base_return_true) {
+        // successfully logged in.
+        // note: by using a SEE OTHER redirect, the client knows to make a GET request and that the redirect is temporary.
+        $redirect = c_reservation_path_redirect::s_create_redirect('/u/dashboard', c_base_http_status::SEE_OTHER, FALSE);
+        return $redirect->do_execute($http, $database, $session, $html, $settings);
+      }
+
+      unset($login_result);
+
+      if (!empty($problem_messages)) {
+        $messages = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER, 'form_problems', array('form_problems'));
+        foreach ($problem_messages as $problem_delta => $problem_message) {
+          $class = array(
+            'form_problems-problem',
+            'form_problems-problem-' . $problem_delta,
+          );
+
+          $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER, 'form_problems-problem-' . $problem_delta, $class);
+          $tag->set_text($problem_message);
+          unset($class);
+
+          $messages->set_tag($tag);
+          unset($tag);
+        }
+        unset($problem_message);
+        unset($problem_delta);
+
+        $html->set_tag($messages);
+        unset($messages);
+      }
+      unset($problem_messages);
+    }
+    else {
+      $form_defaults = array();
+    }
+
+    // login form
+    $form = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_FORM, 'login_form', array('login_form'));
+    $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_METHOD, 'post');
+    $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_ROLE, 'form');
+    $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_ACCEPT_CHARACTER_SET, c_base_charset::UTF_8);
+
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('Login to System');
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // form id: represents the form.
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_HIDDEN, 'form_id', array('form-id', 'login_form-id'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'login_form');
+    $form->set_tag($tag);
+    unset($tag);
+
+    // form unique id: uniquely identifies the form.
+    $unique_id = mt_rand(1, 16);
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_HIDDEN, 'form_id-unique', array('form-id_unique', 'login_form-id_unique'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, '' . $unique_id);
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // @todo: The $unique_id should be stored in the database for the duration of the session and the submit process.
+    // data should include form id, unique id, form source, and form destination and then these values will not need to be stored on the form itself where they can be modified.
+    // the data should also include the user id and optionally the session id (in cases where forms are session-sensitive or if they should span across sessions).
+    // revisions to each form, for dynamic changes, should also be supported.
+    unset($unique_id);
+
+
+    // label: username
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LABEL, NULL, array('login_form-label-username'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_FOR, 'login_form-username');
+    $tag->set_text('Username');
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // field: username
+    $class = array(
+      'login_form-input-username',
+    );
+    if (array_key_exists('login_form-username', $problem_fields)) {
+      $class[] = 'field_has_problem';
+    }
+
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_TEXT, 'login_form-username', $class);
+    unset($class);
+
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_REQUIRED, TRUE);
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, $this->pr_sanitize('login_form-username', 0)->get_value());
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // label: password
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LABEL, NULL, array('login_form-label-password'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_FOR, 'login_form-password');
+    $tag->set_text('Password');
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // field: password
+    $class = array(
+      'login_form-input-password',
+    );
+    if (array_key_exists('login_form-password', $problem_fields)) {
+      $class[] = 'field_has_problem';
+    }
+
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_PASSWORD, 'login_form-password', $class);
+    unset($class);
+
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_REQUIRED, TRUE);
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, $this->pr_sanitize('login_form-password', 0)->get_value());
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // button: reset
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_RESET, 'login_form-reset', array('login_form-button-reset'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'Reset');
+    $form->set_tag($tag);
+    unset($tag);
+
+
+    // button: submit
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SUBMIT, 'login_form-login', array('login_form-button-login'));
+    $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'Login');
+    #$tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_ACTION, $settings['base_path'] . '/s/u/login'); // custom submit destination, but would require /s/u/login to redirect back to here.
+    $form->set_tag($tag);
+    unset($tag);
+    unset($problem_fields);
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'content-wrapper', 'content-wrapper'));
+    $wrapper->set_tag($form);
+    unset($form);
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+    return $executed;
+  }
+
+  /**
+   * Validate and Perform the login.
+   *
+   * @param c_base_database &$database
+   *   The database object.
+   * @param c_base_session &$session
+   *   The current session.
+   * @param array $settings
+   *   The system settings array.
+   *
+   * @return c_base_return_array|c_base_return_status
+   *   TRUE on success.
+   *   An array of problems on failure.
+   */
+  private function p_do_login(&$database, &$session, $settings) {
+    $problems = array();
+    if (empty($_POST['login_form-username'])) {
+      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'No valid username has been supplied.');
+    }
+
+    if (empty($_POST['login_form-password'])) {
+      $problems[] = c_base_form_problem::s_create_error('login_form-password', 'No valid password has been supplied.');
+    }
+
+    // explicitly deny access to internal user accounts
+    if ($_POST['login_form-username'] == 'u_reservation_public') {
+      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user name or password has been specified.');
+    }
+
+    // return current list of problems before continuing to login attempt with credentials.
+    if (!empty($problems)) {
+      return c_base_return_array::s_new($problems);
+    }
+
+    $session->set_name($_POST['login_form-username']);
+    $session->set_password($_POST['login_form-password']);
+
+    // the database string must be rebuilt using the new username and password.
+    reservation_database_string($database, $settings, $_POST['login_form-username'], $_POST['login_form-password']);
+
+    $access_denied = FALSE;
+    $error_messages = array();
+    $connected = reservation_database_connect($database);
+    if (c_base_return::s_has_error($connected)) {
+      // try to determine what the warning is.
+      // this is not very accurate/efficient, but scanning the string appears to be the only way to identify the error.
+      $errors = $connected->get_error();
+
+      // @todo: walk through all errors instead of just checking the first.
+      $error = reset($errors);
+      unset($errors);
+
+      $details = $error->get_details();
+      unset($error);
+
+      if (isset($details['arguments'][':failure_reasons'][0]['message'])) {
+        // in the case where the database cannot be connected to, do not attempt to ensure user account.
+        if (preg_match('/could not connect to server: connection refused/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
+          // @todo: separate messages for admin users and public users.
+          #foreach ($details['arguments'][':failure_reasons'] as $error_message) {
+          #  $error_messages[] = $error_message;
+          #}
+          #unset($error_message);
+          unset($details);
+
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, cannot connect to the database.');
+          return c_base_return_array::s_new($problems);
+        }
+        elseif (preg_match('/no pg_hba\.conf entry for host/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
+          // the account either does note exist or is not authorized.
+          // it is a pity that postgresql doesn't differentiate the two.
+          $access_denied = TRUE;
+        }
+        else {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, reason: ' . $details['arguments'][':failure_reasons'][0]['message'] . '.');
+          unset($details);
+
+          return c_base_return_array::s_new($problems);
+        }
+      }
+      unset($details);
+
+      if ($access_denied) {
+        // it is possible the user name might not exist, so try to auto-create the username if the username does not exist.
+        $ensure_result = reservation_ensure_user_account($settings, $_POST['login_form-username']);
+        if ($ensure_result instanceof c_base_return_int) {
+          $ensure_result = $ensure_result->get_value_exact();
+
+          $connected = new c_base_return_false();
+          if ($ensure_result === 0) {
+            // try again now that the system has attempted to ensure the user account exists.
+            $connected = reservation_database_connect($database);
+            if ($connected instanceof c_base_return_true) {
+              // @todo: add log entry.
+              #set_log_user($database, 'create_user');
+            }
+          }
+          elseif ($ensure_result === 1) {
+            // invalid user name, bad characters, or name too long.
+          }
+          elseif ($ensure_result === 2) {
+            // failed to connect to the ldap server and could not query the ldap name.
+          }
+          elseif ($ensure_result === 3) {
+            // user name not found in ldap database.
+          }
+          elseif ($ensure_result === 4) {
+            //    4 = failed to connect to the database.
+          }
+          elseif ($ensure_result === 5) {
+            //    5 = error returned while executing the SQL command.
+          }
+          elseif ($ensure_result === 6) {
+            //    6 = error occured while reading input from the user (such as via recv()).
+          }
+          elseif ($ensure_result === 7) {
+            //    7 = error occured while writing input from the user (such as via send()).
+          }
+          elseif ($ensure_result === 8) {
+            //    8 = the received packet is invalid, such as wrong length.
+          }
+          elseif ($ensure_result === 9) {
+            //   10 = connection timed out when reading or writing.
+          }
+          elseif ($ensure_result === 10) {
+            //   10 = the connection is being forced closed.
+          }
+          elseif ($ensure_result === 11) {
+            //   11 = the connection is closing because the service is quitting.
+          }
+        }
+        unset($ensure_result);
+      }
+    }
+
+    if (c_base_return::s_has_error($connected) || $connected instanceof c_base_return_false) {
+      // @todo: rewrite this function to handle multiple errors.
+      if ($access_denied) {
+        $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user or password has been specified.');
+      }
+      else {
+        $errors = $connected->get_error();
+
+        $error = reset($errors);
+        unset($errors);
+
+        $details = $error->get_details();
+        unset($error);
+
+        // @todo: not just database errors, but also session create errors need to be checked.
+        if (isset($details['arguments'][':failure_reasons'][0]['message']) && is_string($details['arguments'][':failure_reasons'][0]['message'])) {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, ' . $details['arguments'][':failure_reasons'][0]['message']);
+        }
+        else {
+          // here the reason for failure is unknown.
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login,');
+        }
+        unset($details);
+      }
+
+      unset($access_denied);
+      unset($connected);
+
+      if (empty($problems)) {
+        unset($problems);
+        return new c_base_return_false();
+      }
+
+      return c_base_return_array::s_new($problems);
+    }
+    unset($access_denied);
+
+    // @todo: add log entry.
+    #set_log_user($database, 'login');
+
+    // @todo: load and store custom settings (loaded from the database and/or ldap).
+    #$session->set_settings($user_data);
+
+    // the session needs to be opened and the data needs to be saved on successful login.
+    $result = $session->do_connect();
+    if (c_base_return::s_has_error($result)) {
+      $socket_error = $session->get_error_socket();
+      if ($socket_error instanceof c_base_return_int) {
+        $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
+      }
+      else {
+        $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session.');
+      }
+      unset($socket_error);
+    }
+    else {
+      $ldap = reservation_database_load_ldap_data($settings, $_POST['login_form-username'])->get_value();
+      if ($ldap instanceof c_base_return_false || !is_array($ldap)) {
+        $ldap = array(
+          'data' => NULL,
+        );
+      }
+
+      if (isset($ldap['status']) && $ldap['status'] instanceof c_base_return_false) {
+        $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Failed to retrieve ldap information for specified user.');
+
+        // @todo: handle error situation.
+      }
+
+      $user_data = reservation_database_get_user_data($database, $_POST['login_form-username'], $ldap['data'])->get_value();
+
+      // @todo: get and use user id from $user_data.
+
+      $pushed = $session->do_push($settings['session_expire'], $settings['session_max']);
+      $session->do_disconnect();
+
+      $cookie_login = NULL;
+      if (c_base_return::s_has_error($pushed)) {
+        $socket_error = $session->get_error_socket();
+        if ($socket_error instanceof c_base_return_int) {
+          $problems = c_base_form_problem::s_create_error(NULL, 'Failed to push session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
+        }
+        else {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to push session.');
+        }
+        unset($socket_error);
+      }
+      else {
+        $session_expire = $session->get_timeout_expire()->get_value_exact();
+        $cookie_login = $session->get_cookie();
+      }
+
+      if ($cookie_login instanceof c_base_cookie) {
+        $cookie_login->set_expires($session_expire);
+        $cookie_login->set_max_age(NULL);
+
+        if ($pushed instanceof c_base_return_true) {
+          $data = array(
+            'session_id' => $session->get_session_id()->get_value_exact(),
+            'expire' => gmdate("D, d-M-Y H:i:s T", $session_expire), // unnecessary, but provided for debug purposes.
+          );
+
+          $cookie_login->set_value($data);
+          $session->set_cookie($cookie_login);
+        }
+      }
+      unset($cookie_login);
+      unset($session_expire);
+      unset($pushed);
+    }
+    unset($result);
+    unset($connected);
+
+    if (empty($problems)) {
+      unset($problems);
+      return new c_base_return_true();
+    }
+
+    return c_base_return_array::s_new($problems);
+  }
+}
diff --git a/program/reservation/paths/u/logout.php b/program/reservation/paths/u/logout.php
new file mode 100644 (file)
index 0000000..7301bb6
--- /dev/null
@@ -0,0 +1,316 @@
+<?php
+/**
+ * @file
+ * Provides path handler for the user logout process.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_http_status.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+/**
+ * Provides a form for the user logout.
+ *
+ * This listens on: /s/u/logout
+ */
+final class c_reservation_path_form_user_logout extends c_base_path {
+
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+
+    // Wrapper
+    $wrapper = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SECTION, c_base_defaults_global::CSS_BASE . c_base_defaults_global::CSS_BASE . 'content-wrapper', array(c_base_defaults_global::CSS_BASE . 'content-wrapper', 'content-wrapper'));
+
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
+    $tag->set_text('You Have Logged Out');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    // H1
+    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
+    $tag->set_text('You have been logged out of the system.');
+    $wrapper->set_tag($tag);
+    unset($tag);
+
+    $html->set_tag($wrapper);
+    unset($wrapper);
+
+
+    reservation_session_logout($database, $session, $settings);
+
+    return $executed;
+  }
+
+
+  /**
+   * Logout of the session.
+   *
+   * @fixme: much of this is just a carbon copy of the login form and needs to be rewritten accordingly.
+   *
+   * @param c_base_database &$database
+   *   The database object.
+   * @param c_base_session &$session
+   *   The current session.
+   * @param array $settings
+   *   The system settings array.
+   *
+   * @return c_base_return_array|c_base_return_status
+   *   TRUE on success.
+   *   An array of problems on failure.
+   */
+  private function p_do_logout(&$database, &$session, $settings) {
+    // @fixme: below is a copy and paste of the login form, it needs to be replaced with the logout code!
+    $problems = array();
+    if (empty($_POST['login_form-username'])) {
+      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'No valid username has been supplied.');
+    }
+
+    if (empty($_POST['login_form-password'])) {
+      $problems[] = c_base_form_problem::s_create_error('login_form-password', 'No valid password has been supplied.');
+    }
+
+    // explicitly deny access to internal user accounts
+    if ($_POST['login_form-username'] == 'u_reservation_public') {
+      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user name or password has been specified.');
+    }
+
+    // return current list of problems before continuing to login attempt with credentials.
+    if (!empty($problems)) {
+      return c_base_return_array::s_new($problems);
+    }
+
+    $session->set_name($_POST['login_form-username']);
+    $session->set_password($_POST['login_form-password']);
+
+    // the database string must be rebuilt using the new username and password.
+    reservation_database_string($database, $settings, $_POST['login_form-username'], $_POST['login_form-password']);
+
+    $access_denied = FALSE;
+    $error_messages = array();
+    $connected = reservation_database_connect($database);
+    if (c_base_return::s_has_error($connected)) {
+      // try to determine what the warning is.
+      // this is not very accurate/efficient, but scanning the string appears to be the only way to identify the error.
+      $errors = $connected->get_error();
+
+      // @todo: walk through all errors instead of just checking the first.
+      $error = reset($errors);
+      unset($errors);
+
+      $details = $error->get_details();
+      unset($error);
+
+      if (isset($details['arguments'][':failure_reasons'][0]['message'])) {
+        // in the case where the database cannot be connected to, do not attempt to ensure user account.
+        if (preg_match('/could not connect to server: connection refused/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
+          // @todo: separate messages for admin users and public users.
+          #foreach ($details['arguments'][':failure_reasons'] as $error_message) {
+          #  $error_messages[] = $error_message;
+          #}
+          #unset($error_message);
+          unset($details);
+
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, cannot connect to the database.');
+          return c_base_return_array::s_new($problems);
+        }
+        elseif (preg_match('/no pg_hba\.conf entry for host/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
+          // the account either does note exist or is not authorized.
+          // it is a pity that postgresql doesn't differentiate the two.
+          $access_denied = TRUE;
+        }
+        else {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, reason: ' . $details['arguments'][':failure_reasons'][0]['message'] . '.');
+          unset($details);
+
+          return c_base_return_array::s_new($problems);
+        }
+      }
+      unset($details);
+
+      if ($access_denied) {
+        // it is possible the user name might not exist, so try to auto-create the username if the username does not exist.
+        $ensure_result = reservation_ensure_user_account($settings, $_POST['login_form-username']);
+        if ($ensure_result instanceof c_base_return_int) {
+          $ensure_result = $ensure_result->get_value_exact();
+
+          $connected = new c_base_return_false();
+          if ($ensure_result === 0) {
+            // try again now that the system has attempted to ensure the user account exists.
+            $connected = reservation_database_connect($database);
+            if ($connected instanceof c_base_return_true) {
+              // @todo: add log entry.
+              #set_log_user($database, 'create_user');
+            }
+          }
+          elseif ($ensure_result === 1) {
+            // invalid user name, bad characters, or name too long.
+          }
+          elseif ($ensure_result === 2) {
+            // failed to connect to the ldap server and could not query the ldap name.
+          }
+          elseif ($ensure_result === 3) {
+            // user name not found in ldap database.
+          }
+          elseif ($ensure_result === 4) {
+            //    4 = failed to connect to the database.
+          }
+          elseif ($ensure_result === 5) {
+            //    5 = error returned while executing the SQL command.
+          }
+          elseif ($ensure_result === 6) {
+            //    6 = error occured while reading input from the user (such as via recv()).
+          }
+          elseif ($ensure_result === 7) {
+            //    7 = error occured while writing input from the user (such as via send()).
+          }
+          elseif ($ensure_result === 8) {
+            //    8 = the received packet is invalid, such as wrong length.
+          }
+          elseif ($ensure_result === 9) {
+            //   10 = connection timed out when reading or writing.
+          }
+          elseif ($ensure_result === 10) {
+            //   10 = the connection is being forced closed.
+          }
+          elseif ($ensure_result === 11) {
+            //   11 = the connection is closing because the service is quitting.
+          }
+        }
+        unset($ensure_result);
+      }
+    }
+
+    if (c_base_return::s_has_error($connected) || $connected instanceof c_base_return_false) {
+      // @todo: rewrite this function to handle multiple errors.
+      if ($access_denied) {
+        $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user or password has been specified.');
+      }
+      else {
+        $errors = $connected->get_error();
+
+        $error = reset($errors);
+        unset($errors);
+
+        $details = $error->get_details();
+        unset($error);
+
+        // @todo: not just database errors, but also session create errors need to be checked.
+        if (isset($details['arguments'][':failure_reasons'][0]['message']) && is_string($details['arguments'][':failure_reasons'][0]['message'])) {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, ' . $details['arguments'][':failure_reasons'][0]['message']);
+        }
+        else {
+          // here the reason for failure is unknown.
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login,');
+        }
+        unset($details);
+      }
+
+      unset($access_denied);
+      unset($connected);
+
+      if (empty($problems)) {
+        unset($problems);
+        return new c_base_return_false();
+      }
+
+      return c_base_return_array::s_new($problems);
+    }
+    unset($access_denied);
+
+    // @todo: add log entry.
+    #set_log_user($database, 'login');
+
+    // @todo: load and store custom settings (loaded from the database and/or ldap).
+    #$session->set_settings($user_data);
+
+    // the session needs to be opened and the data needs to be saved on successful login.
+    $result = $session->do_connect();
+    if (c_base_return::s_has_error($result)) {
+      $socket_error = $session->get_error_socket();
+      if ($socket_error instanceof c_base_return_int) {
+        $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
+      }
+      else {
+        $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session.');
+      }
+      unset($socket_error);
+    }
+    else {
+      $ldap = reservation_database_load_ldap_data($settings, $_POST['login_form-username'])->get_value();
+      if ($ldap instanceof c_base_return_false || !is_array($ldap)) {
+        $ldap = array(
+          'data' => NULL,
+        );
+      }
+
+      if (isset($ldap['status']) && $ldap['status'] instanceof c_base_return_false) {
+        $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Failed to retrieve ldap information for specified user.');
+
+        // @todo: handle error situation.
+      }
+
+      $user_data = reservation_database_get_user_data($database, $_POST['login_form-username'], $ldap['data'])->get_value();
+
+      // @todo: get and use user id from $user_data.
+
+      $pushed = $session->do_push($settings['session_expire'], $settings['session_max']);
+      $session->do_disconnect();
+
+      $cookie_login = NULL;
+      if (c_base_return::s_has_error($pushed)) {
+        $socket_error = $session->get_error_socket();
+        if ($socket_error instanceof c_base_return_int) {
+          $problems = c_base_form_problem::s_create_error(NULL, 'Failed to push session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
+        }
+        else {
+          $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to push session.');
+        }
+        unset($socket_error);
+      }
+      else {
+        $session_expire = $session->get_timeout_expire()->get_value_exact();
+        $cookie_login = $session->get_cookie();
+      }
+
+      if ($cookie_login instanceof c_base_cookie) {
+        $cookie_login->set_expires($session_expire);
+        $cookie_login->set_max_age(NULL);
+
+        if ($pushed instanceof c_base_return_true) {
+          $data = array(
+            'session_id' => $session->get_session_id()->get_value_exact(),
+            'expire' => gmdate("D, d-M-Y H:i:s T", $session_expire), // unnecessary, but provided for debug purposes.
+          );
+
+          $cookie_login->set_value($data);
+          $session->set_cookie($cookie_login);
+        }
+      }
+      unset($cookie_login);
+      unset($session_expire);
+      unset($pushed);
+    }
+    unset($result);
+    unset($connected);
+
+    if (empty($problems)) {
+      unset($problems);
+      return new c_base_return_true();
+    }
+
+    return c_base_return_array::s_new($problems);
+  }
+}
index 10bcfad67db27446f0c112db165aac486bf5977f..98464430479563898cbb9372a7c201f4a5a635ea 100644 (file)
    *   The database to connect to.
    * @param array $settings
    *   Custom settings.
+   * @param string|null $username
+   *   The username string.
+   *   If NULL, then the global username is used.
+   * @param string|null $password
+   *   The password string.
+   *   If NULL, then the global password is used.
    *
    * @return c_base_return_status
    *   TRUE on success, FALSE otherwise.
    *   FALSE with error bit set on error.
    */
-  function reservation_database_string(&$database, $settings) {
+  function reservation_database_string(&$database, $settings, $user_name = NULL, $password = NULL) {
     if (!($database instanceof c_base_database)) {
       $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'database', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
       return c_base_return_error::s_false($error);
       return c_base_return_error::s_false($error);
     }
 
+    if (!is_null($user_name) && !is_string($user_name)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'user_name', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
+    if (!is_null($password) && !is_string($password)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'password', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_false($error);
+    }
+
     $connection_string = new c_base_connection_string();
     $connection_string->set_host($settings['database_host']);
     $connection_string->set_port($settings['database_port']);
     $connection_string->set_database($settings['database_name']);
-    $connection_string->set_user($settings['database_user']);
 
-    if (!is_null($settings['database_password'])) {
-      $connection_string->set_password($settings['database_password']);
+    if (is_null($user_name)) {
+      $connection_string->set_user($settings['database_user']);
+
+      if (!is_null($settings['database_password'])) {
+        $connection_string->set_password($settings['database_password']);
+      }
+    }
+    else {
+      $connection_string->set_user($user_name);
+
+      if (!is_null($password)) {
+        $connection_string->set_password($password);
+      }
     }
 
     $connection_string->set_ssl_mode($settings['database_ssl_mode']);
index 6ffcf5384d5445b3f43ef96f94553129dbc58740..c9d8021c0b15e8ecdea838c155cfe752507aef95 100644 (file)
   require_once('common/base/classes/base_markup.php');
   require_once('common/base/classes/base_html.php');
   require_once('common/base/classes/base_charset.php');
+  require_once('common/base/classes/base_ascii.php');
   require_once('common/base/classes/base_form.php');
+  require_once('common/base/classes/base_path.php');
 
   require_once('program/reservation/reservation_database.php');
   require_once('program/reservation/reservation_session.php');
 
-/**
- * Build the login page.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- */
-function reservation_build_login_page(&$html, $settings, $session) {
-  $problem_fields = array();
-  $problem_messages = array();
-
-  // @fixme: create a form problems array in session and use that.
-  $problems = $session->get_problems();
-  if ($problems instanceof c_base_return_array) {
-    $problems = $problems->get_value_exact();
-
-    foreach ($problems as $problem) {
-      $fields = $problem->get_fields();
-      if ($fields instanceof c_base_return_array) {
-        foreach ($fields->get_value_exact() as $field) {
-          $problem_fields[] = $field;
-        }
-        unset($field);
-      }
-      unset($fields);
-
-      $problem_messages[] = $problem->get_value_exact();
-    }
-    unset($problem);
+class c_reservation_paths {
+  // paths to common files (not url paths).
+  private const PATH_LOGIN         = 'program/reservation/paths/u/login.php';
+  private const PATH_LOGOUT        = 'program/reservation/paths/u/logout.php';
+  private const PATH_ACCESS_DENIED = 'program/reservation/internal/access_denied.php';
+  private const PATH_NOT_FOUND     = 'program/reservation/internal/not_found.php';
+
+  private $http      = NULL;
+  private $database  = NULL;
+  private $settings  = NULL;
+  private $session   = NULL;
+  private $markup    = NULL;
+  private $logged_in = NULL;
+  private $paths     = NULL;
+
+  /**
+   * Class constructor.
+   */
+  public function __construct() {
+    $this->uri       = NULL;
+    $this->http      = NULL;
+    $this->database  = NULL;
+    $this->settings  = NULL;
+    $this->session   = NULL;
+    $this->markup    = NULL;
+    $this->logged_in = NULL;
+    $this->paths     = NULL;
+    $this->path      = NULL;
   }
-  unset($problems);
-
-  if (!empty($problem_messages)) {
-    $messages = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER, 'form_problems', array('form_problems'));
-    foreach ($problem_messages as $problem_delta => $problem_message) {
-      $class = array(
-        'form_problems-problem',
-        'form_problems-problem-' . $problem_delta,
-      );
-
-      $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER, 'form_problems-problem-' . $problem_delta, $class);
-      $tag->set_text($problem_message);
-      unset($class);
-
-      $messages->set_tag($tag);
-      unset($tag);
-    }
-    unset($problem_message);
-    unset($problem_delta);
 
-    $html->set_tag($messages);
-    unset($messages);
+  /**
+   * Class destructor.
+   */
+  public function __destruct() {
+    unset($this->uri);
+    unset($this->http);
+    unset($this->settings);
+    unset($this->session);
+    unset($this->markup);
+    unset($this->logged_in);
+    unset($this->paths);
+    unset($this->path);
   }
-  unset($problem_messages);
-
-  // login form
-  $form = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_FORM, 'login_form', array('login_form'));
-  $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_METHOD, 'post');
-  $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_ROLE, 'form');
-  $form->set_attribute(c_base_markup_attributes::ATTRIBUTE_ACCEPT_CHARACTER_SET, c_base_charset::UTF_8);
-
-
-  // H1
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
-  $tag->set_text('Login to System');
-  $form->set_tag($tag);
-  unset($tag);
 
+  /**
+   * Process request path and determine what to do.
+   *
+   * @param c_base_http &$http
+   *   The HTTP settings.
+   * @param c_base_database &database
+   *   The current database.
+   * @param c_base_session &$session
+   *   The current session.
+   * @param c_base_html &$html
+   *   The html page object.
+   * @param array $settings
+   *   The system settings array.
+   * @param bool $logged_in
+   *   (optional) TRUE of logged in, FALSE otherwise.
+   *
+   * @return c_base_path_executed
+   *   The execution results.
+   *   The execution results with the error bit set on error.
+   */
+  public function reservation_process_path(&$http, &$database, &$session, &$html, $settings, $logged_in = TRUE) {
+    // @todo: these parameter errors might need a custom service unavailable and system log support.
+    if (!($http instanceof c_base_http)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'http', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
-  // hidden form data
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_HIDDEN, 'form_id', array('login_form-id'));
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'login_form');
-  $form->set_tag($tag);
-  unset($tag);
+    if (!($database instanceof c_base_database)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'database', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
+    if (!($session instanceof c_base_session)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'session', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
-  // label: username
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LABEL, NULL, array('login_form-label-username'));
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_FOR, 'login_form-username');
-  $tag->set_text('Username');
-  $form->set_tag($tag);
-  unset($tag);
+    if (!($html instanceof c_base_html)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'html', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
+    if (!is_array($settings)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'settings', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
-  // field: username
-  $class = array(
-    'login_form-input-username',
-  );
-  if (array_key_exists('login_form-username', $problem_fields)) {
-    $class[] = 'field_has_problem';
-  }
+    if (!is_bool($logged_in)) {
+      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'logged_in', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+      return c_base_return_error::s_value(array(), 'c_base_path_executed', $error);
+    }
 
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_TEXT, 'login_form-username', $class);
-  unset($class);
+    $this->http = &$http;
+    $this->database = &$database;
+    $this->settings = $settings;
+    $this->session = &$session;
+    $this->markup = &$html;
+    $this->logged_in = $logged_in;
 
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_REQUIRED, TRUE);
-  $form->set_tag($tag);
-  unset($tag);
+    // require HTTPS for access to any part of this website.
+    if (!isset($_SERVER["HTTPS"])) {
+      // @todo: redirect to https version of requested uri.
+      $failure_path = $this->p_get_path_not_found();
 
+      return $failure_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
+    }
 
-  // label: password
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LABEL, NULL, array('login_form-label-password'));
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_FOR, 'login_form-password');
-  $tag->set_text('Password');
-  $form->set_tag($tag);
-  unset($tag);
 
+    $request_uri = $http->get_request(c_base_http::REQUEST_URI)->get_value_exact();
 
-  // field: password
-  $class = array(
-    'login_form-input-password',
-  );
-  if (array_key_exists('login_form-password', $problem_fields)) {
-    $class[] = 'field_has_problem';
-  }
+    $this->uri = array(
+      'scheme' => '',
+      'authority' => '',
+      'path' => '',
+      'query' => array(),
+      'fragment' => '',
+      'url' => TRUE,
+    );
 
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_PASSWORD, 'login_form-password', $class);
-  unset($class);
+    if (isset($request_uri['data'])) {
+      $this->uri = $request_uri['data'];
+    }
+    unset($request_uri);
 
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_REQUIRED, TRUE);
-  $form->set_tag($tag);
-  unset($tag);
+    // strip the base path from the requested uri.
+    if (!empty($settings['base_path'])) {
+      $this->uri['path'] = preg_replace('@^' . preg_quote($settings['base_path'], '@') . '@i', '', $this->uri['path']);
+      $this->uri['path'] = preg_replace('@/$@', '', $this->uri['path']);
+    }
 
 
-  // button: reset
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_RESET, 'login_form-reset', array('login_form-button-reset'));
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'Reset');
-  $form->set_tag($tag);
-  unset($tag);
+    // load all available paths.
+    $this->p_paths_create();
 
 
-  // button: submit
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_SUBMIT, 'login_form-login', array('login_form-button-login'));
-  $tag->set_attribute(c_base_markup_attributes::ATTRIBUTE_VALUE, 'Login');
-  $form->set_tag($tag);
-  unset($tag);
-  unset($problem_fields);
+    // find the path
+    $handler_settings = $this->paths->find_path($this->uri['path']);
 
-  $html->set_tag($form);
-  unset($form);
-}
+    if (!is_array($handler_settings)) {
+      unset($handler_settings);
 
-/**
- * Validate the login form.
- *
- * @param c_base_database &$database
- *   The database object.
- * @param array &$settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- *
- * @return c_base_return_array|c_base_return_status
- *   TRUE on success.
- *   An array of problems on failure.
- */
-function reservation_attempt_login(&$database, &$settings, &$session) {
-  $problems = array();
-  if (empty($_POST['login_form-username'])) {
-    $problems[] = c_base_form_problem::s_create_error('login_form-username', 'No valid username has been supplied.');
-  }
-
-  if (empty($_POST['login_form-password'])) {
-    $problems[] = c_base_form_problem::s_create_error('login_form-password', 'No valid password has been supplied.');
-  }
-
-  // explicitly deny access to internal user accounts
-  if ($_POST['login_form-username'] == 'u_reservation_public') {
-    $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user name or password has been specified.');
-  }
-
-  // return current list of problems before continuing to login attempt with credentials.
-  if (!empty($problems)) {
-    return c_base_return_array::s_new($problems);
-  }
+      // @todo: handle error case and failsafe (404)?.
+      return new c_base_return_false();
+    }
 
-  $session->set_name($_POST['login_form-username']);
-  $session->set_password($_POST['login_form-password']);
-  $settings['database_user'] = $_POST['login_form-username'];
-  $settings['database_password'] = $_POST['login_form-password'];
-
-  // the database string must be rebuilt using the new username and password.
-  reservation_database_string($database, $settings);
-
-  $access_denied = FALSE;
-  $error_messages = array();
-  $connected = reservation_database_connect($database);
-  if (c_base_return::s_has_error($connected)) {
-    // try to determine what the warning is.
-    // this is not very accurate/efficient, but scanning the string appears to be the only way to identify the error.
-    $errors = $connected->get_error();
-
-    // @todo: walk through all errors instead of just checking the first.
-    $error = reset($errors);
-    unset($errors);
-
-    $details = $error->get_details();
-    unset($error);
-
-    if (isset($details['arguments'][':failure_reasons'][0]['message'])) {
-      // in the case where the database cannot be connected to, do not attempt to ensure user account.
-      if (preg_match('/could not connect to server: connection refused/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
-        // @todo: separate messages for admin users and public users.
-        #foreach ($details['arguments'][':failure_reasons'] as $error_message) {
-        #  $error_messages[] = $error_message;
-        #}
-        #unset($error_message);
-        unset($details);
-
-        $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, cannot connect to the database.');
-        return c_base_return_array::s_new($problems);
-      }
-      elseif (preg_match('/no pg_hba\.conf entry for host/i', $details['arguments'][':failure_reasons'][0]['message']) > 0) {
-        // the account either does note exist or is not authorized.
-        // it is a pity that postgresql doesn't differentiate the two.
-        $access_denied = TRUE;
+    if (array_key_exists('redirect', $handler_settings)) {
+      // @todo: handle redirect.
+    }
+    else {
+      if (!empty($handler_settings['include']) && is_string($handler_settings['include'])) {
+        require_once($handler_settings['include']);
       }
-      else {
-        $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, reason: ' . $details['arguments'][':failure_reasons'][0]['message'] . '.');
-        unset($details);
 
-        return c_base_return_array::s_new($problems);
+      if (empty($handler_settings['handler']) || !class_exists($handler_settings['handler'])) {
+        // @todo: handle error case.
       }
-    }
-    unset($details);
-
-    if ($access_denied) {
-      // it is possible the user name might not exist, so try to auto-create the username if the username does not exist.
-      $ensure_result = reservation_ensure_user_account($settings, $_POST['login_form-username']);
-      if ($ensure_result instanceof c_base_return_int) {
-        $ensure_result = $ensure_result->get_value_exact();
-
-        $connected = new c_base_return_false();
-        if ($ensure_result === 0) {
-          // try again now that the system has attempted to ensure the user account exists.
-          $connected = reservation_database_connect($database);
-          if ($connected instanceof c_base_return_true) {
-            // @todo: add log entry.
-            #set_log_user($database, 'create_user');
-          }
-        }
-        elseif ($ensure_result === 1) {
-          // invalid user name, bad characters, or name too long.
-        }
-        elseif ($ensure_result === 2) {
-          // failed to connect to the ldap server and could not query the ldap name.
-        }
-        elseif ($ensure_result === 3) {
-          // user name not found in ldap database.
-        }
-        elseif ($ensure_result === 4) {
-          //    4 = failed to connect to the database.
-        }
-        elseif ($ensure_result === 5) {
-          //    5 = error returned while executing the SQL command.
-        }
-        elseif ($ensure_result === 6) {
-          //    6 = error occured while reading input from the user (such as via recv()).
-        }
-        elseif ($ensure_result === 7) {
-          //    7 = error occured while writing input from the user (such as via send()).
-        }
-        elseif ($ensure_result === 8) {
-          //    8 = the received packet is invalid, such as wrong length.
-        }
-        elseif ($ensure_result === 9) {
-          //   10 = connection timed out when reading or writing.
-        }
-        elseif ($ensure_result === 10) {
-          //   10 = the connection is being forced closed.
-        }
-        elseif ($ensure_result === 11) {
-          //   11 = the connection is closing because the service is quitting.
-        }
+      else {
+        $this->path = new $handler_settings['handler']();
       }
-      unset($ensure_result);
     }
-  }
+    unset($handler_settings);
 
-  if (c_base_return::s_has_error($connected) || $connected instanceof c_base_return_false) {
-    // @todo: rewrite this function to handle multiple errors.
-    if ($access_denied) {
-      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Unable to login, an incorrect user or password has been specified.');
+    // handle request method validation.
+    if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
+      // @todo: considering limiting _POST to different path groups here.
     }
-    else {
-      $errors = $connected->get_error();
+    elseif (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') {
+      $id_group = $this->path->get_id_group()->get_value_exact();
 
-      $error = reset($errors);
-      unset($errors);
+      // all paths except /s/ and /x/ may use GET.
+      if ($id_group === c_base_ascii::LOWER_S || $id_group === c_base_ascii::LOWER_X) {
+        $failure_path = $this->p_get_path_bad_method();
 
-      $details = $error->get_details();
-      unset($error);
-
-      // @todo: not just database errors, but also session create errors need to be checked.
-      if (isset($details['arguments'][':failure_reasons'][0]['message']) && is_string($details['arguments'][':failure_reasons'][0]['message'])) {
-        $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login, ' . $details['arguments'][':failure_reasons'][0]['message']);
+        return $failure_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
       }
-      else {
-        // here the reason for failure is unknown.
-        $problems[] = c_base_form_problem::s_create_error(NULL, 'Unable to login,');
-      }
-      unset($details);
+      unset($id_group);
     }
+    else {
+      $failure_path = $this->p_get_path_bad_method();
 
-    unset($access_denied);
-    unset($connected);
-
-    if (empty($problems)) {
-      unset($problems);
-      return new c_base_return_false();
+      return $failure_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
     }
 
-    return c_base_return_array::s_new($problems);
+
+    return $this->p_paths_normal();
   }
-  unset($access_denied);
 
-  // @todo: add log entry.
-  #set_log_user($database, 'login');
+  /**
+   * Creates and returns a list of all available paths.
+   *
+   * Add/modify paths here as desired.
+   *
+   * @return c_base_paths
+   *   The generated paths object.
+   */
+  private function p_paths_create() {
+    $this->paths = new c_base_paths();
 
-  // @todo: load and store custom settings (loaded from the database and/or ldap).
-  #$session->set_settings($user_data);
 
-  // the session needs to be opened and the data needs to be saved on successful login.
-  $result = $session->do_connect();
-  if (c_base_return::s_has_error($result)) {
-    $socket_error = $session->get_error_socket();
-    if ($socket_error instanceof c_base_return_int) {
-      $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
-    }
-    else {
-      $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to load session.');
-    }
-    unset($socket_error);
+    // set root path to be the user dashboard.
+    $this->paths->set_path('', 'c_reservation_path_user_dashboard', 'program/reservation/paths/u/dashboard.php');
+
+
+    // create login/logout paths
+    $path = c_base_path::s_create_content(c_base_ascii::LOWER_U, 'login', FALSE);
+    $this->paths->set_path($path, 'c_reservation_path_user_login', 'program/reservation/login.php');
+    unset($path);
+
+    $path = c_base_path::s_create_content(c_base_ascii::LOWER_U, 'logout', FALSE);
+    $this->paths->set_path($path, 'c_reservation_path_user_logout', 'program/reservation/logout.php');
+    unset($path);
+
+
+    // user dashboard
+    $path = c_base_path::s_create_content(c_base_ascii::LOWER_U, 'dashboard', FALSE);
+    $this->paths->set_path($path, 'c_reservation_path_user_dashboard', 'program/reservation/paths/u/dashboard.php');
+    unset($path);
   }
-  else {
-    $ldap = reservation_database_load_ldap_data($settings, $_POST['login_form-username'])->get_value();
-    if ($ldap instanceof c_base_return_false || !is_array($ldap)) {
-      $ldap = array(
-        'data' => NULL,
-      );
-    }
 
-    if (isset($ldap['status']) && $ldap['status'] instanceof c_base_return_false) {
-      $problems[] = c_base_form_problem::s_create_error('login_form-username', 'Failed to retrieve ldap information for specified user.');
+  /**
+   * Process request paths and determine what to do.
+   *
+   * @return c_base_path_executed
+   *   The execution results.
+   *   The execution results with the error bit set on error.
+   */
+  private function p_paths_normal() {
+    $id_group = $this->path->get_id_group()->get_value_exact();
+
+    // regardless of path-specific settings, the following paths always require login credentials to access.
+    if ($id_group === c_base_ascii::LOWER_A || $id_group === c_base_ascii::LOWER_U) {
+      $this->path->set_is_private(TRUE);
+    }
 
-      // @todo: handle error situation.
+    if ($this->path instanceof c_reservation_path_user_login) {
+      unset($id_group);
+      return $this->path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
     }
 
-    $user_data = reservation_database_get_user_data($database, $_POST['login_form-username'], $ldap['data'])->get_value();
 
-    // @todo: get and use user id from $user_data.
+    // if the request is private, make sure the user is logged in.
+    if ($id_group === c_base_ascii::LOWER_A || $id_group === c_base_ascii::LOWER_U || $this->path->get_is_private()->get_value_exact()) {
+      if ($this->logged_in) {
+        unset($id_group);
+        return $this->path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
+      }
+      elseif ($this->path->get_is_root()->get_value_exact()) {
+        unset($id_group);
 
-    $pushed = $session->do_push($settings['session_expire'], $settings['session_max']);
-    $session->do_disconnect();
+        $this->http->set_response_status(c_base_http_status::FORBIDDEN);
 
-    $cookie_login = NULL;
-    if (c_base_return::s_has_error($pushed)) {
-      $socket_error = $session->get_error_socket();
-      if ($socket_error instanceof c_base_return_int) {
-        $problems = c_base_form_problem::s_create_error(NULL, 'Failed to push session, due to socket error (' . $socket_error->get_value_exact() . '): ' . @socket_strerror($socket_error->get_value_exact()) . '.');
+        $login_path = $this->p_get_path_login();
+        return $login_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
       }
       else {
-        $problems[] = c_base_form_problem::s_create_error(NULL, 'Failed to push session.');
+        // some special case paths always provide login prompt along with access denied.
+        if ($id_group === c_base_ascii::LOWER_A || $id_group === c_base_ascii::LOWER_U) {
+          unset($id_group);
+
+          $this->http->set_response_status(c_base_http_status::FORBIDDEN);
+
+          $login_path = $this->p_get_path_login();
+          return $login_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
+        }
       }
-      unset($socket_error);
     }
     else {
-      $session_expire = $session->get_timeout_expire()->get_value_exact();
-      $cookie_login = $session->get_cookie();
+      unset($id_group);
+      return $this->path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
     }
 
-    if ($cookie_login instanceof c_base_cookie) {
-      $cookie_login->set_expires($session_expire);
-      $cookie_login->set_max_age(NULL);
-
-      if ($pushed instanceof c_base_return_true) {
-        $data = array(
-          'session_id' => $session->get_session_id()->get_value_exact(),
-          'expire' => gmdate("D, d-M-Y H:i:s T", $session_expire), // unnecessary, but provided for debug purposes.
-        );
-
-        $cookie_login->set_value($data);
-        $session->set_cookie($cookie_login);
-      }
+    // return access denied or page not found depending on path and privacy settings.
+    if ($id_group === c_base_ascii::LOWER_C || $id_group === c_base_ascii::LOWER_D || $id_group === c_base_ascii::LOWER_T || $id_group === c_base_ascii::LOWER_X || $id_group === c_base_ascii::LOWER_F) {
+      // these always return not found for these paths.
+      $failsafe_path = $this->p_get_path_not_found();
+    }
+    elseif ($this->path->get_is_private()->get_value_exact() && $id_group !== c_base_ascii::NULL) {
+      // non private, and non-special case paths should return access denied as per normal behavior.
+      $failsafe_path = $this->p_get_path_access_denied();
     }
-    unset($cookie_login);
-    unset($session_expire);
-    unset($pushed);
+    else {
+      // all other case, return not found.
+      $failsafe_path = $this->p_get_path_not_found();
+    }
+    unset($id_group);
+
+    return $failsafe_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
   }
-  unset($result);
-  unset($connected);
 
-  if (empty($problems)) {
-    unset($problems);
-    return new c_base_return_true();
+  /**
+   * Process request path form paths and determine what to do.
+   *
+   * @return c_base_path_executed
+   *   The execution results.
+   *   The execution results with the error bit set on error.
+   */
+  private function p_paths_forms() {
+    // @todo
+
+    // always return not found, do not inform user if the access is denied.
+    $failsafe_path = $this->p_get_path_not_found();
+
+    return $failsafe_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
   }
 
-  return c_base_return_array::s_new($problems);
-}
+  /**
+   * Process request path ajax paths and determine what to do.
+   *
+   * @return c_base_path_executed
+   *   The execution results.
+   *   The execution results with the error bit set on error.
+   */
+  private function p_paths_ajax() {
+    // @todo
 
-/**
- * Build the HTTPS requirement page.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
-   * @param c_base_session &$session
-   *   The current session.
- */
-function reservation_build_page_require_https(&$html, $settings, &$session) {
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
-  $tag->set_text('HTTPS Connection is Required');
-  $html->set_tag($tag);
-  unset($tag);
-
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-  $tag->set_text('Please use a secure connection to access this website.');
-  $html->set_tag($tag);
-  unset($tag);
-}
+    // always return not found, do not inform user if the access is denied.
+    $failsafe_path = $this->p_get_path_not_found();
 
-/**
- * Build the dashboard page.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- */
-function reservation_build_page_dashboard(&$html, $settings, &$session) {
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
-  $tag->set_text('Dashboard');
-  $html->set_tag($tag);
-  unset($tag);
-
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-  $tag->set_text('All links will go here.');
-  $html->set_tag($tag);
-  unset($tag);
-
-  $roles = array();
-  $roles_object = $session->get_setting('roles');
-  if ($roles_object instanceof c_base_roles) {
-    $roles = $roles_object->get_roles()->get_value_exact();
+    return $failsafe_path->do_execute($this->http, $this->database, $this->session, $this->markup, $this->settings);
   }
-  unset($roles_object);
-
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-  $tag->set_text('You are currently logged in as: ' . $settings['database_user']);
-  $html->set_tag($tag);
-  unset($tag);
-
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-  $tag->set_text('You are currently assigned the following roles:');
-  $html->set_tag($tag);
-  unset($tag);
-
-  $tag_ul = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_UNORDERED_LIST);
-
-  foreach ($roles as $role) {
-    $tag_li = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_LIST_ITEM);
-
-    switch ($role) {
-      case c_base_roles::PUBLIC:
-        $tag_li->set_text('Public');
-        break;
-      case c_base_roles::USER:
-        $tag_li->set_text('User');
-        break;
-      case c_base_roles::REQUESTER:
-        $tag_li->set_text('Requester');
-        break;
-      case c_base_roles::DRAFTER:
-        $tag_li->set_text('Drafter');
-        break;
-      case c_base_roles::EDITOR:
-        $tag_li->set_text('Editor');
-        break;
-      case c_base_roles::REVIEWER:
-        $tag_li->set_text('Reviewer');
-        break;
-      case c_base_roles::FINANCER:
-        $tag_li->set_text('Financer');
-        break;
-      case c_base_roles::INSURER:
-        $tag_li->set_text('Insurer');
-        break;
-      case c_base_roles::PUBLISHER:
-        $tag_li->set_text('Publisher');
-        break;
-      case c_base_roles::AUDITOR:
-        $tag_li->set_text('Auditor');
-        break;
-      case c_base_roles::MANAGER:
-        $tag_li->set_text('Manager');
-        break;
-      case c_base_roles::ADMINISTER:
-        $tag_li->set_text('Administer');
-        break;
-    }
 
-    $tag_ul->set_tag($tag_li);
-    unset($tag_li);
+  /**
+   * Load and return the login path.
+   */
+  private function p_get_path_login() {
+    require_once(self::PATH_LOGIN);
+    return new c_reservation_path_user_login();
   }
-  unset($role);
 
-  $html->set_tag($tag_ul);
-}
+  /**
+   * Load and return the logout path.
+   */
+  private function p_get_path_logout() {
+    require_once(self::PATH_LOGOUT);
+    return new c_reservation_path_user_logout();
+  }
 
-/**
- * Process and build requested forms.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- */
-function reservation_process_forms(&$html, $settings, &$session) {
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_H1);
-  $tag->set_text('Form Processing');
-  $html->set_tag($tag);
-  unset($tag);
-
-  $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-  $tag->set_text('This function is called to process specific forms.');
-  $html->set_tag($tag);
-  unset($tag);
-
-  if (!empty($_POST['form_id'])) {
-    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_BREAK);
-    $html->set_tag($tag);
-    unset($tag);
-
-    $tag = c_theme_html::s_create_tag(c_base_markup_tag::TYPE_DIVIDER);
-    $tag->set_text('The form has the id: ' . $_POST['form_id'] . '.');
-    $html->set_tag($tag);
-    unset($tag);
+  /**
+   * Load and return the access denied path.
+   */
+  private function p_get_path_access_denied() {
+    require_once(self::PATH_ACCESS_DENIED);
+    return new c_reservation_path_access_denied();
   }
-}
 
-/**
- * Process request path and determine what to do.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- */
-function reservation_process_path(&$html, $settings, &$session) {
-  reservation_build_page_dashboard($html, $settings, $session);
-}
+  /**
+   * Load and return the not found path.
+   */
+  private function p_get_path_not_found() {
+    require_once(self::PATH_NOT_FOUND);
+    return new c_reservation_path_not_found();
+  }
 
-/**
- * Process request path for public users and determine what to do.
- *
- * @param c_base_html &$html
- *   The html page object.
- * @param array $settings
- *   The system settings array.
- * @param c_base_session &$session
- *   The current session.
- */
-function reservation_process_path_public(&$html, $settings, &$session) {
-  reservation_build_login_page($html, $settings, $session);
+  /**
+   * Load and return the not found path.
+   */
+  private function p_get_path_bad_method() {
+    require_once(self::PATH_BAD_METHOD);
+    return new c_reservation_path_bad_method();
+  }
 }
diff --git a/program/reservation/reservation_redirects.php b/program/reservation/reservation_redirects.php
new file mode 100644 (file)
index 0000000..14283e4
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @file
+ * Provides reservation redirect classes.
+ */
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+require_once('common/base/classes/base_path.php');
+require_once('common/base/classes/base_http.php');
+require_once('common/base/classes/base_http_status.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+/**
+ * Handles redirects to a specified destination.
+ */
+final class c_reservation_path_redirect extends c_base_path {
+  const PREFIX_ID    = '';
+  const PREFIX_CLASS = 'reservation-';
+
+  /**
+   * Implements do_execute().
+   */
+  public function do_execute(&$http, &$database, &$session, &$html, $settings = array()) {
+    // the parent function performs validation on the parameters.
+    $executed = parent::do_execute($http, $database, $session, $html, $settings);
+    if (c_base_return::s_has_error($executed)) {
+      return $executed;
+    }
+
+    $http->set_response_location($this->field_destination);
+    $http->set_response_status($this->field_response_code);
+
+    return $executed;
+  }
+}
index 2c5fd90f4f0eabe5c6689506888ffc3aa95e8298..14af8f01c725c3829a4413d13b3395f8a7e1b381 100644 (file)
   require_once('program/reservation/reservation_database.php');
 
 
-  /**
-   * Process session information.
-   *
-   * @param c_base_http &$http
-   *   Http object.
-   * @param array &$settings
-   *   System settings.
-   *
-   * @param c_base_session
-   *   Session information is returned on success.
-   *   Session information with error bit set is returned on error.
-   */
-  function reservation_process_sessions(&$http, &$settings) {
-    $cookie_login = $http->get_request(c_base_http::REQUEST_COOKIE, $settings['cookie_name']);
-
-    $no_session = FALSE;
-    if (!($cookie_login instanceof c_base_cookie)) {
-      $cookie_login = new c_base_cookie();
-
-      $no_session = TRUE;
-    }
+/**
+ * Process session information.
+ *
+ * @param c_base_http &$http
+ *   Http object.
+ * @param array &$settings
+ *   System settings.
+ *
+ * @param c_base_session
+ *   Session information is returned on success.
+ *   Session information with error bit set is returned on error.
+ */
+function reservation_process_sessions(&$http, &$settings) {
+  $cookie_login = $http->get_request(c_base_http::REQUEST_COOKIE, $settings['cookie_name']);
 
-    // create a session object regardless of login session cookie.
-    $session = new c_base_session();
-    $session->set_socket_directory($settings['session_socket']);
-    $session->set_system_name($settings['session_system']);
-
-    // the requester should not have any control over specifying/changing these settings, so overwrite whatever is defined by the request cookie.
-    $cookie_login->set_name($settings['cookie_name']);
-    $cookie_login->set_path($settings['cookie_path']);
-    $cookie_login->set_domain($settings['cookie_domain']);
-    $cookie_login->set_http_only($settings['cookie_http_only']);
-    $cookie_login->set_host_only($settings['cookie_host_only']);
-    $cookie_login->set_same_site($settings['cookie_same_site']);
-    $cookie_login->set_secure(TRUE);
-
-    if (empty($_SERVER['REMOTE_ADDR'])) {
-      $session->set_host('0.0.0.0');
-    }
-    else {
-      $session->set_host($_SERVER['REMOTE_ADDR']);
-    }
+  $no_session = FALSE;
+  if (!($cookie_login instanceof c_base_cookie)) {
+    $cookie_login = new c_base_cookie();
 
-    // no session cookie has been defined, so there is no existing session to load.
-    if ($no_session) {
-      $session->set_cookie($cookie_login);
-      unset($cookie_login);
-      unset($no_session);
+    $no_session = TRUE;
+  }
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':session_name' => $settings['cookie_name'], ':function_name' => __FUNCTION__)), i_base_error_messages::NO_SESSION);
-      $session->set_error($error);
-      unset($error);
+  // create a session object regardless of login session cookie.
+  $session = new c_base_session();
+  $session->set_socket_directory($settings['session_socket']);
+  $session->set_system_name($settings['session_system']);
+
+  // the requester should not have any control over specifying/changing these settings, so overwrite whatever is defined by the request cookie.
+  $cookie_login->set_name($settings['cookie_name']);
+  $cookie_login->set_path($settings['cookie_path']);
+  $cookie_login->set_domain($settings['cookie_domain']);
+  $cookie_login->set_http_only($settings['cookie_http_only']);
+  $cookie_login->set_host_only($settings['cookie_host_only']);
+  $cookie_login->set_same_site($settings['cookie_same_site']);
+  $cookie_login->set_secure(TRUE);
+
+  if (empty($_SERVER['REMOTE_ADDR'])) {
+    $session->set_host('0.0.0.0');
+  }
+  else {
+    $session->set_host($_SERVER['REMOTE_ADDR']);
+  }
 
-      return $session;
-    }
+  // no session cookie has been defined, so there is no existing session to load.
+  if ($no_session) {
+    $session->set_cookie($cookie_login);
+    unset($cookie_login);
     unset($no_session);
 
-    $cookie_data = $cookie_login->get_value_exact();
-    if (!($cookie_login->validate() instanceof c_base_return_true) || empty($cookie_data['session_id'])) {
-      $session->set_cookie($cookie_login);
-      unset($cookie_login);
-
-      // cookie_login failed validation or the cookie contains no session id.
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':session_name' => $settings['cookie_name'], ':function_name' => __FUNCTION__)), i_base_error_messages::SESSION_INVALID);
-      $session->set_error($error);
-      unset($error);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':session_name' => $settings['cookie_name'], ':function_name' => __FUNCTION__)), i_base_error_messages::NO_SESSION);
+    $session->set_error($error);
+    unset($error);
 
-      return $session;
-    }
+    return $session;
+  }
+  unset($no_session);
 
-    $session->set_session_id($cookie_data['session_id']);
+  $cookie_data = $cookie_login->get_value_exact();
+  if (!($cookie_login->validate() instanceof c_base_return_true) || empty($cookie_data['session_id'])) {
+    $session->set_cookie($cookie_login);
+    unset($cookie_login);
 
+    // cookie_login failed validation or the cookie contains no session id.
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':session_name' => $settings['cookie_name'], ':function_name' => __FUNCTION__)), i_base_error_messages::SESSION_INVALID);
+    $session->set_error($error);
+    unset($error);
 
-    // connect to the session using the given session id.
-    $session_connection = $session->do_connect();
-    if (c_base_return::s_has_error($session_connection)) {
-      $session->set_cookie($cookie_login);
-      unset($cookie_login);
+    return $session;
+  }
 
-      $session->set_error($session_connection->get_error());
-      unset($error);
+  $session->set_session_id($cookie_data['session_id']);
 
-      return $session;
-    }
 
-    $result = $session->do_pull();
-    $session->do_disconnect();
+  // connect to the session using the given session id.
+  $session_connection = $session->do_connect();
+  if (c_base_return::s_has_error($session_connection)) {
+    $session->set_cookie($cookie_login);
+    unset($cookie_login);
 
-    if ($result instanceof c_base_return_true) {
-      $user_name = $session->get_name()->get_value();
-      $password = $session->get_password()->get_value();
+    $session->set_error($session_connection->get_error());
+    unset($error);
 
-      if (is_string($user_name) && is_string($password)) {
-        $settings['database_user'] = $user_name;
-        $settings['database_password'] = $password;
-      }
-    }
+    return $session;
+  }
 
+  $result = $session->do_pull();
+  $session->do_disconnect();
 
-    // check to see if the session timeout has been extended and if so, then update the cookie.
-    $session_expire = $session->get_timeout_expire()->get_value_exact();
-    $session_seconds = $session_expire - time();
-    if ($session_seconds == 0) {
-      $session_seconds = -1;
-    }
+  if ($result instanceof c_base_return_true) {
+    $user_name = $session->get_name()->get_value();
+    $password = $session->get_password()->get_value();
 
-    if ($session_expire > $cookie_data['expire']) {
-      $cookie_data['expire'] = gmdate("D, d-M-Y H:i:s T", $session_expire);
-      $cookie_login->set_value($cookie_data);
-      $cookie_login->set_expires($session_expire);
+    if (is_string($user_name) && is_string($password)) {
+      $settings['database_user'] = $user_name;
+      $settings['database_password'] = $password;
     }
-
-    $session->set_cookie($cookie_login);
-    unset($cookie_login);
-
-    return $session;
   }
 
-  /**
-   * Attempt to auto-create a postgresql account.
-   *
-   * @param array $settings
-   *   System settings.
-   * @param string $username
-   *   The name of the postgresql account to auto-create.
-   *
-   * @return c_base_return_int|c_base_return_status
-   *   An integer is returned, whose codes represent the transaction result.
-   *   FALSE with error bit set is returned on error.
-   */
-  function reservation_ensure_user_account($settings, $user_name) {
-    if (!is_array($settings)) {
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'settings', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
-      return c_base_return_error::s_false($error);
-    }
 
-    if (!is_string($user_name)) {
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'user_name', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
-      return c_base_return_error::s_false($error);
-    }
+  // check to see if the session timeout has been extended and if so, then update the cookie.
+  $session_expire = $session->get_timeout_expire()->get_value_exact();
+  $session_seconds = $session_expire - time();
+  if ($session_seconds == 0) {
+    $session_seconds = -1;
+  }
 
-    $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
-    if (!is_resource($socket)) {
-      unset($socket);
+  if ($session_expire > $cookie_data['expire']) {
+    $cookie_data['expire'] = gmdate("D, d-M-Y H:i:s T", $session_expire);
+    $cookie_login->set_value($cookie_data);
+    $cookie_login->set_expires($session_expire);
+  }
 
-      $socket_error = @socket_last_error();
-      @socket_clear_error();
+  $session->set_cookie($cookie_login);
+  unset($cookie_login);
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_create', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
-      unset($socket_error);
+  return $session;
+}
 
-      return c_base_return_error::s_false($error);
-    }
+/**
+ * Attempt to auto-create a postgresql account.
+ *
+ * @param array $settings
+ *   System settings.
+ * @param string $username
+ *   The name of the postgresql account to auto-create.
+ *
+ * @return c_base_return_int|c_base_return_status
+ *   An integer is returned, whose codes represent the transaction result.
+ *   FALSE with error bit set is returned on error.
+ */
+function reservation_ensure_user_account($settings, $user_name) {
+  if (!is_array($settings)) {
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'settings', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+    return c_base_return_error::s_false($error);
+  }
 
-    $connected = @socket_connect($socket, $settings['database_create_account_host'], $settings['database_create_account_port']);
-    if ($connected === FALSE) {
-      $socket_error = @socket_last_error($socket);
+  if (!is_string($user_name)) {
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':argument_name' => 'user_name', ':function_name' => __FUNCTION__)), i_base_error_messages::INVALID_ARGUMENT);
+    return c_base_return_error::s_false($error);
+  }
 
-      @socket_close($socket);
-      @socket_clear_error();
+  $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+  if (!is_resource($socket)) {
+    unset($socket);
 
-      unset($socket);
-      unset($connected);
+    $socket_error = @socket_last_error();
+    @socket_clear_error();
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_connect', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
-      unset($socket_error);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_create', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
+    unset($socket_error);
 
-      return c_base_return_error::s_false($error);
-    }
+    return c_base_return_error::s_false($error);
+  }
 
-    $packet_size_target = 63;
-    $packet_size_client = 1;
+  $connected = @socket_connect($socket, $settings['database_create_account_host'], $settings['database_create_account_port']);
+  if ($connected === FALSE) {
+    $socket_error = @socket_last_error($socket);
 
-    $name_length = strlen(trim($user_name));
-    $difference = $packet_size_target - $name_length;
+    @socket_close($socket);
+    @socket_clear_error();
 
-    if ($difference > 0) {
-      // the packet expects a packet to be NULL terminated or at most $packet_size_target.
-      $packet = pack('a' . $name_length . 'x' . $difference, trim($user_name));
-    }
-    else {
-      $packet = pack('a' . $name_length, $user_name);
-    }
+    unset($socket);
+    unset($connected);
 
-    $written = @socket_write($socket, $packet, $packet_size_target);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_connect', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
+    unset($socket_error);
 
-    unset($packet);
-    unset($packet_size_target);
-    unset($name_length);
-    unset($difference);
+    return c_base_return_error::s_false($error);
+  }
 
-    if ($written === FALSE) {
-      unset($written);
-      unset($packet_size_client);
+  $packet_size_target = 63;
+  $packet_size_client = 1;
 
-      $socket_error = @socket_last_error($socket);
+  $name_length = strlen(trim($user_name));
+  $difference = $packet_size_target - $name_length;
 
-      @socket_close($socket);
-      @socket_clear_error();
+  if ($difference > 0) {
+    // the packet expects a packet to be NULL terminated or at most $packet_size_target.
+    $packet = pack('a' . $name_length . 'x' . $difference, trim($user_name));
+  }
+  else {
+    $packet = pack('a' . $name_length, $user_name);
+  }
 
-      unset($socket);
-      unset($connected);
+  $written = @socket_write($socket, $packet, $packet_size_target);
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_write', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($this->socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
-      unset($socket_error);
+  unset($packet);
+  unset($packet_size_target);
+  unset($name_length);
+  unset($difference);
 
-      return c_base_return_error::s_false($error);
-    }
+  if ($written === FALSE) {
     unset($written);
+    unset($packet_size_client);
 
-    $response = @socket_read($socket, $packet_size_client);
-    if ($response === FALSE) {
-      unset($response);
-      unset($packet_size_client);
+    $socket_error = @socket_last_error($socket);
 
-      $socket_error = @socket_last_error($socket);
+    @socket_close($socket);
+    @socket_clear_error();
 
-      @socket_close($socket);
-      @socket_clear_error();
+    unset($socket);
+    unset($connected);
 
-      unset($socket);
-      unset($connected);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_write', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($this->socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
+    unset($socket_error);
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_read', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($this->socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
-      unset($socket_error);
+    return c_base_return_error::s_false($error);
+  }
+  unset($written);
 
-      return c_base_return_error::s_false($error);
-    }
+  $response = @socket_read($socket, $packet_size_client);
+  if ($response === FALSE) {
+    unset($response);
+    unset($packet_size_client);
+
+    $socket_error = @socket_last_error($socket);
 
     @socket_close($socket);
+    @socket_clear_error();
+
     unset($socket);
-    unset($packet_size_client);
+    unset($connected);
 
-    if (!is_string($response) || strlen($response) == 0) {
-      unset($response);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_read', ':socket_error' => $socket_error, ':socket_error_message' => @socket_strerror($this->socket_error), ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::SOCKET_FAILURE);
+    unset($socket_error);
 
-      $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_read', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
-      return c_base_return_error::s_false($error);
-    }
+    return c_base_return_error::s_false($error);
+  }
 
-    // an integer is expected to be returned by the socket.
-    $response_packet = unpack('C', $response);
-    $response_value = (int) $response_packet[1];
+  @socket_close($socket);
+  unset($socket);
+  unset($packet_size_client);
 
+  if (!is_string($response) || strlen($response) == 0) {
     unset($response);
 
-    // response codes as defined in the c source file:
-    //    0 = no problems detected.
-    //    1 = invalid user name, bad characters, or name too long.
-    //    2 = failed to connect to the ldap server and could not query the ldap name.
-    //    3 = user name not found in ldap database.
-    //    4 = failed to connect to the database.
-    //    5 = error returned while executing the SQL command.
-    //    6 = error occured while reading input from the user (such as via recv()).
-    //    7 = error occured while writing input from the user (such as via send()).
-    //    8 = the received packet is invalid, such as wrong length.
-    //    9 = connection timed out when reading or writing.
-    //   10 = the connection is being forced closed.
-    //   11 = the connection is closing because the service is quitting.
-    return c_base_return_int::s_new($response_value);
+    $error = c_base_error::s_log(NULL, array('arguments' => array(':operation_name' => 'socket_read', ':function_name' => __CLASS__ . '->' . __FUNCTION__)), i_base_error_messages::OPERATION_FAILURE);
+    return c_base_return_error::s_false($error);
   }
+
+  // an integer is expected to be returned by the socket.
+  $response_packet = unpack('C', $response);
+  $response_value = (int) $response_packet[1];
+
+  unset($response);
+
+  // response codes as defined in the c source file:
+  //    0 = no problems detected.
+  //    1 = invalid user name, bad characters, or name too long.
+  //    2 = failed to connect to the ldap server and could not query the ldap name.
+  //    3 = user name not found in ldap database.
+  //    4 = failed to connect to the database.
+  //    5 = error returned while executing the SQL command.
+  //    6 = error occured while reading input from the user (such as via recv()).
+  //    7 = error occured while writing input from the user (such as via send()).
+  //    8 = the received packet is invalid, such as wrong length.
+  //    9 = connection timed out when reading or writing.
+  //   10 = the connection is being forced closed.
+  //   11 = the connection is closing because the service is quitting.
+  return c_base_return_int::s_new($response_value);
+}
diff --git a/program/reservation/reservation_theme.php b/program/reservation/reservation_theme.php
new file mode 100644 (file)
index 0000000..27fb327
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @file
+ * Provides handler for common theme functionality.
+ */
+
+require_once('common/base/classes/base_error.php');
+require_once('common/base/classes/base_return.php');
+
+require_once('common/theme/classes/theme_html.php');
+
+
+class c_reservation_theme_html extends c_theme_html {
+}