diff --git a/web_client/@install.php b/web_client/@install.php
new file mode 100644
index 00000000..f9ee54ec
--- /dev/null
+++ b/web_client/@install.php
@@ -0,0 +1,320 @@
+
+
+
+
+
+ Kohana Installation
+
+
+
+ Environment Tests
+
+ The following tests have been run to determine if Kohana will work in your environment. If any of the tests have failed, consult the documentation
+ for more information on how to correct the problem.
+
+
+
+
+
+ PHP Version
+ =')): ?>
+
+
+
+
+
+ Kohana requires PHP 5.2 or newer, this version is .
+
+
+
+
+ System Directory
+
+
+
+
+
+
+ The configured
+
+ system
+
+ directory does not exist or does not contain required files.
+
+
+
+
+ Application Directory
+
+
+
+
+
+
+ The configured
+
+ application
+
+ directory does not exist or does not contain required files.
+
+
+
+
+ Modules Directory
+
+
+
+
+
+
+ The configured
+
+ modules
+
+ directory does not exist or does not contain required files.
+
+
+
+
+ Logs Directory
+
+
+ Pass
+
+
+
+ The default
+
+ logs
+
+ directory does not exist or is not writable. Depending on your log driver and config settings, this may not be a problem.
+
+
+
+
+ Cache Directory
+
+
+ Pass
+
+
+
+ The default
+
+ cache
+
+ directory does not exist or is not writable. Depending on your cache driver and config settings, this may not be a problem.
+
+
+
+
+ PCRE UTF-8
+
+
+ PCRE
+ support is missing.
+
+
+
+ PCRE
+ has not been compiled with UTF-8 support.
+
+
+
+ PCRE
+ has not been compiled with Unicode property support.
+
+
+
+ Pass
+
+
+
+
+ Reflection Enabled
+
+
+ Pass
+
+
+
+ PHP reflection
+ is either not loaded or not compiled in.
+
+
+
+
+ Filters Enabled
+
+
+ Pass
+
+
+
+ The filter
+ extension is either not loaded or not compiled in.
+
+
+
+
+ Iconv Extension Loaded
+
+
+ Pass
+
+
+
+ The iconv
+ extension is not loaded.
+
+
+
+
+ SPL Enabled
+
+
+ Pass
+
+
+
+ SPL
+ is not enabled.
+
+
+
+
+ Multibyte String Enabled
+
+ Pass
+
+ The mbstring
+ extension is not loaded.
+
+
+
+
+ Mbstring Not Overloaded
+
+
+ The mbstring
+ extension is overloading PHP's native string functions.
+
+
+
+ Pass
+
+
+
+
+
+ XML support
+
+
+ PHP is compiled without XML
+ support, thus lacking support for
+
+ utf8_encode()
+
/
+
+ utf8_decode()
+
.
+
+
+
+ Pass
+
+
+
+ Timezone
+
+ Pass
+
+
+ The current timezone, ''
, is not valid.
+ You must configure it in php.ini
or config/locale.php
.
+
+
+
+
+ URI Determination
+
+
+ Pass
+
+
+
+ At least one of $_SERVER['PATH_INFO']
, $_SERVER['ORIG_PATH_INFO']
, or $_SERVER['PHP_SELF']
must be available.
+
+
+
+
+
+
+
+ Kohana may not work correctly with your environment.
+
+
+
+ Your environment passed all requirements. Remove or rename the
+
+ install
+
+ file now.
+
+
+
+
+
+
diff --git a/web_client/Kohana License.html b/web_client/Kohana License.html
new file mode 100644
index 00000000..bc4bce28
--- /dev/null
+++ b/web_client/Kohana License.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+Kohana License
+
+
+
+
+Kohana License Agreement
+
+This license is a legal agreement between you and the Kohana Software Foundation for the use of Kohana Framework (the "Software"). By obtaining the Software you agree to comply with the terms and conditions of this license.
+
+Copyright (c) 2007-2009 Kohana Team All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+Neither the name of the Kohana nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+NOTE: This license is modeled after the BSD software license.
+
+
+
\ No newline at end of file
diff --git a/web_client/application/Bootstrap.php b/web_client/application/Bootstrap.php
new file mode 100644
index 00000000..b15cbfce
--- /dev/null
+++ b/web_client/application/Bootstrap.php
@@ -0,0 +1,59 @@
+template->title = 'G3 Web Client';
+
+ if (Session::instance()->get("g3_client_access_token")) {
+ $resource = G3Remote::instance()->get_resource("gallery");
+ $this->template->album_tree = $this->_get_album_tree($resource);
+ $this->template->detail = $this->_get_detail($resource);
+ } else {
+ $this->template->content = new View('login.html');
+ $this->template->content->errors = $this->template->content->form =
+ array("user" => "", "password" => "");
+ }
+ }
+
+ public function login() {
+ $form = $errors = array("user" => "", "password" => "");
+ $post = new Validation($_POST);
+ $post->add_rules("user", "required");
+ $post->add_rules("password", "required");
+ if ($valid =$post->validate()) {
+ try {
+ $token = G3Remote::instance()->get_access_token($post["user"], $post["password"]);
+ Session::instance()->set("g3_client_access_token", $token);
+ $resource = G3Remote::instance()->get_resource("gallery");
+ $content = array(
+ "album_tree" => $this->_get_album_tree($resource),
+ "detail" => $this->_get_detail($resource));
+ } catch (Exception $e) {
+ Kohana_Log::add("error", Kohana_Exception::text($e));
+ $valid = false;
+ }
+ }
+
+ if (!$valid) {
+ $content = new View('login.html');
+ $content->form = arr::overwrite($form, $post->as_array());
+ $content->errors = arr::overwrite($errors, $post->errors());
+ }
+
+ $this->auto_render = false;
+ print json_encode(array("status" => $valid ? "ok" : "error", "content" => $content));
+ }
+
+ public function albums() {
+ $path = $this->input->get("path");
+ $resource = G3Remote::instance()->get_resource("gallery/$path", "album");
+ $this->auto_render = false;
+ print $this->_get_album_tree($resource);
+ }
+
+ public function detail() {
+ $path = $this->input->get("path");
+ $resource = G3Remote::instance()->get_resource("gallery/$path");
+ $this->auto_render = false;
+ print $this->_get_detail($resource);
+ }
+
+ private function _get_album_tree($resource) {
+ $v = new View("tree_part.html");
+ $v->element = (object)array("title" => $resource->title, "path" => $resource->path);
+ $v->element->children = array();
+ foreach ($resource->children as $child) {
+ if ($child->type != "album") {
+ continue;
+ }
+ $v->element->children[] = $child;
+ }
+ return $v;
+ }
+
+ private function _get_detail($resource) {
+ $v = new View("{$resource->type}_detail.html");
+ $v->resource = $resource;
+ return $v;
+ }
+} // End G3 Client Controller
diff --git a/web_client/application/libraries/G3Remote.php b/web_client/application/libraries/G3Remote.php
new file mode 100644
index 00000000..83ed5a00
--- /dev/null
+++ b/web_client/application/libraries/G3Remote.php
@@ -0,0 +1,252 @@
+ $value) {
+ if (!empty($_data_raw)) {
+ $_data_raw .= '&';
+ }
+ $_data_raw .= urlencode($key) . '=' . urlencode($value);
+ }
+
+ return $_data_raw;
+ }
+
+ /**
+ * A single request, without following redirects
+ *
+ * @todo: Handle redirects? If so, only for GET (i.e. not for POST), and use G2's
+ * WebHelper_simple::_parseLocation logic.
+ */
+ static function do_request($url, $method='GET', $headers=array(), $body='') {
+ /* Convert illegal characters */
+ $url = str_replace(' ', '%20', $url);
+
+ $url_components = self::_parse_url_for_fsockopen($url);
+ $handle = fsockopen(
+ $url_components['fsockhost'], $url_components['port'], $errno, $errstr, 5);
+ if (empty($handle)) {
+ // log "Error $errno: '$errstr' requesting $url";
+ return array(null, null, null);
+ }
+
+ $header_lines = array('Host: ' . $url_components['host']);
+ foreach ($headers as $key => $value) {
+ $header_lines[] = $key . ': ' . $value;
+ }
+
+ $success = fwrite($handle, sprintf("%s %s HTTP/1.0\r\n%s\r\n\r\n%s",
+ $method,
+ $url_components['uri'],
+ implode("\r\n", $header_lines),
+ $body));
+ if (!$success) {
+ // Zero bytes written or false was returned
+ // log "fwrite failed in requestWebPage($url)" . ($success === false ? ' - false' : ''
+ return array(null, null, null);
+ }
+ fflush($handle);
+
+ /*
+ * Read the status line. fgets stops after newlines. The first line is the protocol
+ * version followed by a numeric status code and its associated textual phrase.
+ */
+ $response_status = trim(fgets($handle, 4096));
+ if (empty($response_status)) {
+ // 'Empty http response code, maybe timeout'
+ return array(null, null, null);
+ }
+
+ /* Read the headers */
+ $response_headers = array();
+ while (!feof($handle)) {
+ $line = trim(fgets($handle, 4096));
+ if (empty($line)) {
+ break;
+ }
+
+ /* Normalize the line endings */
+ $line = str_replace("\r", '', $line);
+
+ list ($key, $value) = explode(':', $line, 2);
+ if (isset($response_headers[$key])) {
+ if (!is_array($response_headers[$key])) {
+ $response_headers[$key] = array($response_headers[$key]);
+ }
+ $response_headers[$key][] = trim($value);
+ } else {
+ $response_headers[$key] = trim($value);
+ }
+ }
+
+ /* Read the body */
+ $response_body = '';
+ while (!feof($handle)) {
+ $response_body .= fread($handle, 4096);
+ }
+ fclose($handle);
+
+ return array($response_status, $response_headers, $response_body);
+ }
+
+ /**
+ * Prepare for fsockopen call.
+ * @param string $url
+ * @return array url components
+ * @access private
+ */
+ private static function _parse_url_for_fsockopen($url) {
+ $url_components = parse_url($url);
+ if (strtolower($url_components['scheme']) == 'https') {
+ $url_components['fsockhost'] = 'ssl://' . $url_components['host'];
+ $default_port = 443;
+ } else {
+ $url_components['fsockhost'] = $url_components['host'];
+ $default_port = 80;
+ }
+ if (empty($url_components['port'])) {
+ $url_components['port'] = $default_port;
+ }
+ if (empty($url_components['path'])) {
+ $url_components['path'] = '/';
+ }
+ $uri = $url_components['path']
+ . (empty($url_components['query']) ? '' : '?' . $url_components['query']);
+ /* Unescape ampersands, since if the url comes from form input it will be escaped */
+ $url_components['uri'] = str_replace('&', '&', $uri);
+
+ return $url_components;
+ }
+}
+
+// This class does not depend on any Kohana services so that it can be used in non-Kohana
+// applications.
+class G3Remote {
+ protected static $_instance;
+
+ protected $_config;
+
+ private $_resources;
+ private $_access_token;
+ private $_identity;
+
+ public static function instance($access_token=null) {
+ if (!isset(G3Remote::$_instance)) {
+ G3Remote::$_instance = new G3Remote($access_token);
+ }
+
+ return G3Remote::$_instance;
+ }
+
+ /**
+ * Constructs a new G3Remote object
+ *
+ * @param array Database config array
+ * @return G3Remote
+ */
+ protected function __construct($access_token) {
+ // Store the config locally
+ $this->_config = Kohana::config("g3_remote");
+ $this->_access_token = $access_token;
+ }
+
+ public function get_access_token($user, $password) {
+ $identity = md5("$user/$password");
+ if (empty($this->_identity) || $this->_identity != $identity) {
+ $request = "{$this->_config["gallery3_site"]}/access_key";
+ list ($response_body, $response_status, $response_headers) =
+ url_connection::get($request, array("user" => $user, "password" => $password));
+ if (url_connection::success($response_status)) {
+ $response = json_decode($response_body);
+ if ($response->status == "OK") {
+ $this->_access_token = $response->token;
+ $this->_identity = $identity;
+ } else {
+ throw new Exception("Remote host failure: {$response->message}");
+ }
+ } else {
+ throw new Exception("Remote host failure: $response_status");
+ }
+ }
+ return $this->_access_token;
+ }
+
+ public function get_resource($path, $filter=false, $offset=false, $limit=false) {
+ $request = "{$this->_config["gallery3_site"]}/$path";
+ $params = array();
+ if ($filter) {
+ $param["filter"] = $filter;
+ }
+ if ($offset) {
+ $param["offset"] = $offset;
+ }
+ if ($limit) {
+ $param["limit"] = $limit;
+ }
+
+ list ($response_body, $response_status, $response_headers) =
+ url_connection::get($request, $params);
+ if (url_connection::success($response_status)) {
+ $response = json_decode($response_body);
+ if ($response->status != "OK") {
+ throw new Exception("Remote host failure: {$response->message}");
+ }
+ } else {
+ throw new Exception("Remote host failure: $response_status");
+ }
+ return $response->resource;
+ }
+}
\ No newline at end of file
diff --git a/web_client/application/logs/.gitignore b/web_client/application/logs/.gitignore
new file mode 100644
index 00000000..72e8ffc0
--- /dev/null
+++ b/web_client/application/logs/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/web_client/application/views/album_detail.html.php b/web_client/application/views/album_detail.html.php
new file mode 100644
index 00000000..c9e2ff6e
--- /dev/null
+++ b/web_client/application/views/album_detail.html.php
@@ -0,0 +1,15 @@
+
+
+
diff --git a/web_client/application/views/g3_template.html.php b/web_client/application/views/g3_template.html.php
new file mode 100644
index 00000000..81b0a326
--- /dev/null
+++ b/web_client/application/views/g3_template.html.php
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ = html::chars(__($title)) ?>
+ = html::stylesheet("css/reset-fonts-grids.css") ?>
+ = html::stylesheet("css/g3_client.css") ?>
+ = html::stylesheet("css/jquery-ui.css") ?>
+
+
+ = html::script("js/jquery.js") ?>
+ = html::script("js/jquery.form.js") ?>
+ = html::script("js/jquery-ui.js") ?>
+ = html::script("js/g3_client.js") ?>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web_client/application/views/login.html.php b/web_client/application/views/login.html.php
new file mode 100644
index 00000000..6e5799ac
--- /dev/null
+++ b/web_client/application/views/login.html.php
@@ -0,0 +1,27 @@
+
+
+
+ = form::open("g3_client/login") ?>
+
+ Please provide your userid and password for the remote system
+
+
+ = form::label("user", "User Id:") ?>
+ = form::input("user", $form["user"]) ?>
+ = empty($errors["user"]) ? "" : "{$errors["user"]} " ?>
+
+
+ = form::label("password", "Password") ?>
+ = form::password("password", $form["password"]) ?>
+ = empty($errors["password"]) ? "" : "{$errors["password"]} " ?>
+
+
+ = form::submit("submit", "Login") ?>
+ = form::input(array('type'=>'reset','name'=>'reset'), "Reset") ?>
+
+
+
+
+
+
+
diff --git a/web_client/application/views/tree_part.html.php b/web_client/application/views/tree_part.html.php
new file mode 100644
index 00000000..4c671b32
--- /dev/null
+++ b/web_client/application/views/tree_part.html.php
@@ -0,0 +1,14 @@
+
+
+ " >
+ = $element->title ?>
+
+ foreach ($element->children as $child): ?>
+
+ ">
+ = $child->title ?>
+
+ endforeach ?>
+
+
+
diff --git a/web_client/application/views/welcome_content.php b/web_client/application/views/welcome_content.php
new file mode 100644
index 00000000..625c0ba3
--- /dev/null
+++ b/web_client/application/views/welcome_content.php
@@ -0,0 +1,15 @@
+
+
+
.
+
+
+ application/controllers/welcome.php
.
+ application/views/welcome_content.php
.
+
+
+
+
\ No newline at end of file
diff --git a/web_client/css/fix-ie.css b/web_client/css/fix-ie.css
new file mode 100644
index 00000000..376dac74
--- /dev/null
+++ b/web_client/css/fix-ie.css
@@ -0,0 +1,29 @@
+/**
+ * Fix display in IE 6, 7
+ */
+
+#g-banner {
+ z-index: 2;
+}
+
+input.submit {
+ clear: none !important;
+ display: inline !important;
+}
+
+#g-add-tag-form input.textbox {
+ width: 110px;
+}
+
+#g-dialog .g-cancel {
+ display: inline-block !important;
+ float: none !important;
+}
+
+.g-paginator .g-text-right {
+ width: 29%;
+}
+
+.g-paginator .ui-icon-right {
+ width: 60px;
+}
diff --git a/web_client/css/g3_client.css b/web_client/css/g3_client.css
new file mode 100644
index 00000000..f8850e65
--- /dev/null
+++ b/web_client/css/g3_client.css
@@ -0,0 +1,118 @@
+#header {
+ background-color: #E8E8E8;
+ left: 0px;
+ min-height: 90px;
+ padding: 0 20px;
+ position: fixed;
+ right: 0px;
+ height: 90px;
+}
+
+#header div {
+ background-image: url(g3_web.png);
+ background-repeat: no-repeat;
+ height: 90px;
+}
+
+#body {
+ background-color: #E8E8E8;
+ bottom: 40px;
+ left: 0px;
+ position: fixed;
+ right: 0px;
+ top: 90px;
+}
+
+#body #content {
+ background-color: #FFFFFF;
+ border: 1px solid #CCCCCC;
+ padding: 10px 20px;
+ height: 100%;
+}
+
+form li {
+ padding-top: .4em;
+}
+
+form legend {
+ font-size: 1.2em;
+ font-weight: bold;
+}
+
+span.error {
+ color: #FF0000;
+}
+
+#footer {
+ background-color: #E8E8E8;
+ bottom: 0px;
+ left: 0px;
+ min-height: 40px;
+ padding: 10px 20px;
+ position: fixed;
+ right: 0px;
+}
+
+.copyright {
+ font-size: 0.9em;
+ text-transform: uppercase;
+ color: #557d10;
+}
+
+#body #left {
+ background-color: #FFFFFF;
+ border: 2px inset;
+ bottom: 20px;
+ left: 20px;
+ margin: 0;
+ padding: 5px;
+ position: absolute;
+ top: 11px;
+ width: 191px;
+}
+
+#body #left #album_tree {
+ height: 100%;
+ overflow: auto;
+ font-size: 1.1em;
+}
+
+#body #left #album_tree ul li {
+ padding: 0 0 .2em 1.2em;
+}
+
+#body #center {
+ background-color: #FFFFFF;
+ border: 2px inset;
+ bottom: 20px;
+ left: 225px;
+ overflow: auto;
+ padding: 5px;
+ position: absolute;
+ right: 20px;
+ top: 11px;
+}
+
+.tree-title {
+ cursor: pointer;
+}
+
+#body #center .thumb-grid-cell {
+ float: left;
+ margin-right: 10px;
+}
+
+.ui-icon-left .ui-icon {
+ float: left;
+ margin-right: 0.2em;
+}
+
+.ui-icon-left .ui-icon-none {
+ float: left;
+ padding-left: 15px;
+}
+
+.ui-selected {
+ background-color: #E8E8E8;
+ color: #FF9933;
+}
diff --git a/web_client/css/g3_web.pdn b/web_client/css/g3_web.pdn
new file mode 100644
index 00000000..add8b1e2
Binary files /dev/null and b/web_client/css/g3_web.pdn differ
diff --git a/web_client/css/g3_web.png b/web_client/css/g3_web.png
new file mode 100644
index 00000000..27519ec8
Binary files /dev/null and b/web_client/css/g3_web.png differ
diff --git a/web_client/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/web_client/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png
new file mode 100644
index 00000000..954e22db
Binary files /dev/null and b/web_client/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png differ
diff --git a/web_client/css/images/ui-bg_diagonals-thick_20_666666_40x40.png b/web_client/css/images/ui-bg_diagonals-thick_20_666666_40x40.png
new file mode 100644
index 00000000..64ece570
Binary files /dev/null and b/web_client/css/images/ui-bg_diagonals-thick_20_666666_40x40.png differ
diff --git a/web_client/css/images/ui-bg_flat_10_000000_40x100.png b/web_client/css/images/ui-bg_flat_10_000000_40x100.png
new file mode 100644
index 00000000..abdc0108
Binary files /dev/null and b/web_client/css/images/ui-bg_flat_10_000000_40x100.png differ
diff --git a/web_client/css/images/ui-bg_glass_100_f6f6f6_1x400.png b/web_client/css/images/ui-bg_glass_100_f6f6f6_1x400.png
new file mode 100644
index 00000000..9b383f4d
Binary files /dev/null and b/web_client/css/images/ui-bg_glass_100_f6f6f6_1x400.png differ
diff --git a/web_client/css/images/ui-bg_glass_100_fdf5ce_1x400.png b/web_client/css/images/ui-bg_glass_100_fdf5ce_1x400.png
new file mode 100644
index 00000000..859c264b
Binary files /dev/null and b/web_client/css/images/ui-bg_glass_100_fdf5ce_1x400.png differ
diff --git a/web_client/css/images/ui-bg_glass_65_ffffff_1x400.png b/web_client/css/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 00000000..42ccba26
Binary files /dev/null and b/web_client/css/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/web_client/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/web_client/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png
new file mode 100644
index 00000000..39d5824d
Binary files /dev/null and b/web_client/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ
diff --git a/web_client/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/web_client/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
new file mode 100644
index 00000000..f1273672
Binary files /dev/null and b/web_client/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ
diff --git a/web_client/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png b/web_client/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
new file mode 100644
index 00000000..359397ac
Binary files /dev/null and b/web_client/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png differ
diff --git a/web_client/css/images/ui-icons_222222_256x240.png b/web_client/css/images/ui-icons_222222_256x240.png
new file mode 100644
index 00000000..ee039dc0
Binary files /dev/null and b/web_client/css/images/ui-icons_222222_256x240.png differ
diff --git a/web_client/css/images/ui-icons_228ef1_256x240.png b/web_client/css/images/ui-icons_228ef1_256x240.png
new file mode 100644
index 00000000..10e3631d
Binary files /dev/null and b/web_client/css/images/ui-icons_228ef1_256x240.png differ
diff --git a/web_client/css/images/ui-icons_ef8c08_256x240.png b/web_client/css/images/ui-icons_ef8c08_256x240.png
new file mode 100644
index 00000000..35bb8efa
Binary files /dev/null and b/web_client/css/images/ui-icons_ef8c08_256x240.png differ
diff --git a/web_client/css/images/ui-icons_ffd27a_256x240.png b/web_client/css/images/ui-icons_ffd27a_256x240.png
new file mode 100644
index 00000000..baebb63e
Binary files /dev/null and b/web_client/css/images/ui-icons_ffd27a_256x240.png differ
diff --git a/web_client/css/images/ui-icons_ffffff_256x240.png b/web_client/css/images/ui-icons_ffffff_256x240.png
new file mode 100644
index 00000000..bef5178a
Binary files /dev/null and b/web_client/css/images/ui-icons_ffffff_256x240.png differ
diff --git a/web_client/css/jquery-ui.css b/web_client/css/jquery-ui.css
new file mode 100644
index 00000000..9d7aa08b
--- /dev/null
+++ b/web_client/css/jquery-ui.css
@@ -0,0 +1,406 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; outline: none; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; outline: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; outline: none; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; outline: none; }
+.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; outline: none; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; outline: none; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #ffffff; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; }/* Accordion
+----------------------------------*/
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
+.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker
+----------------------------------*/
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ display: none; /*sorry for IE5*/
+ display/**/: block; /*sorry for IE5*/
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}/* Dialog
+----------------------------------*/
+.ui-dialog { position: relative; padding: .2em; width: 300px; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/* Progressbar
+----------------------------------*/
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
+----------------------------------*/
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
+----------------------------------*/
+.ui-tabs { padding: .2em; zoom: 1; }
+.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
+.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
diff --git a/web_client/css/kohana-2.4rc2.zip b/web_client/css/kohana-2.4rc2.zip
new file mode 100644
index 00000000..90db5e36
Binary files /dev/null and b/web_client/css/kohana-2.4rc2.zip differ
diff --git a/web_client/css/reset-fonts-grids.css b/web_client/css/reset-fonts-grids.css
new file mode 100644
index 00000000..b3e042c9
--- /dev/null
+++ b/web_client/css/reset-fonts-grids.css
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.6.0
+*/
+html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}del,ins{text-decoration:none;}body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main,.yui-g .yui-u .yui-g{width:100%;}{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-g .yui-u{width:48.1%;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;} .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}
\ No newline at end of file
diff --git a/web_client/example.htaccess b/web_client/example.htaccess
new file mode 100644
index 00000000..cd7a9d3f
--- /dev/null
+++ b/web_client/example.htaccess
@@ -0,0 +1,19 @@
+# Turn on URL rewriting
+RewriteEngine On
+
+# Installation directory
+RewriteBase /kohana/
+
+# Protect application and system files from being viewed
+# This is only necessary when these files are inside the webserver document root
+RewriteRule ^(application|modules|system) - [R=404,L]
+
+# Allow any files or directories that exist to be displayed directly
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+
+# Rewrite all other URLs to index.php/URL
+RewriteRule .* index.php/$0 [PT,L]
+
+# Alternativly, if the rewrite rule above does not work try this instead:
+#RewriteRule .* index.php?kohana_uri=$0 [PT,QSA,L]
diff --git a/web_client/index.php b/web_client/index.php
new file mode 100644
index 00000000..43aefef2
--- /dev/null
+++ b/web_client/index.php
@@ -0,0 +1,107 @@
+ 0) {
+ $(login).ajaxForm({
+ dataType: "json",
+ beforeSubmit: function(formData, form, options) {
+ form.find(":submit")
+ .addClass("ui-state-disabled")
+ .attr("disabled", "disabled");
+ return true;
+ },
+ success: function(data) {
+ $(obj).html(data.content);
+ if (data.status == "ok") {
+ initializeDetail(obj);
+ } else {
+ ajaxifyLoginForm(obj);
+ }
+ }
+ });
+ } else {
+ initializeDetail(obj);
+ }
+ };
+
+ function initializeDetail(obj) {
+ $(".ui-icon-plus", obj).live("click", function (event) {
+ var siblings = $("~ ul", this);
+ if (siblings.length > 0) {
+ siblings.show();
+ $(this).removeClass("ui-icon-plus");
+ $(this).addClass("ui-icon-minus");
+ } else {
+ var parent = $(this).parent("li");
+ $.get("/g3_client/index.php/g3_client/albums",
+ {path: $(parent).attr("ref")},
+ function(data, textStatus) {
+ $(parent).replaceWith(data);
+ });
+ }
+ });
+ $(".ui-icon-minus", obj).live("click", function (event) {
+ $("~ ul", this).hide();
+ $(this).removeClass("ui-icon-minus");
+ $(this).addClass("ui-icon-plus");
+ });
+ $(".tree-title", obj).click(function (event) {
+ $.get("/g3_client/index.php/g3_client/detail",
+ {path: $(this).parent("li").attr("ref")},
+ function(data, textStatus) {
+ $("#center").html(data);
+ });
+ $(".ui-selected").removeClass("ui-selected");
+ $(this).addClass("ui-selected");
+ });
+ $("#album_tree [ref=''] .tree-title:first").addClass("ui-selected");
+ };
+ })(jQuery);
diff --git a/web_client/js/jquery-ui.js b/web_client/js/jquery-ui.js
new file mode 100644
index 00000000..3a69887e
--- /dev/null
+++ b/web_client/js/jquery-ui.js
@@ -0,0 +1,45 @@
+/*
+ * jQuery UI 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
+ */
+jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.2",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
+ * jQuery UI Dialog 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ * ui.core.js
+ * ui.draggable.js
+ * ui.resizable.js
+ */
+(function(c){var b={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},a="ui-dialog ui-widget ui-widget-content ui-corner-all ";c.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr("title");var l=this,m=this.options,j=m.title||this.originalTitle||" ",e=c.ui.dialog.getTitleId(this.element),k=(this.uiDialog=c("
")).appendTo(document.body).hide().addClass(a+m.dialogClass).css({position:"absolute",overflow:"hidden",zIndex:m.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){(m.closeOnEscape&&n.keyCode&&n.keyCode==c.ui.keyCode.ESCAPE&&l.close(n))}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(n){l.moveToTop(false,n)}),g=this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(k),f=(this.uiDialogTitlebar=c("
")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(k),i=c(' ').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){i.addClass("ui-state-hover")},function(){i.removeClass("ui-state-hover")}).focus(function(){i.addClass("ui-state-focus")}).blur(function(){i.removeClass("ui-state-focus")}).mousedown(function(n){n.stopPropagation()}).click(function(n){l.close(n);return false}).appendTo(f),h=(this.uiDialogTitlebarCloseText=c(" ")).addClass("ui-icon ui-icon-closethick").text(m.closeText).appendTo(i),d=c(" ").addClass("ui-dialog-title").attr("id",e).html(j).prependTo(f);f.find("*").add(f).disableSelection();(m.draggable&&c.fn.draggable&&this._makeDraggable());(m.resizable&&c.fn.resizable&&this._makeResizable());this._createButtons(m.buttons);this._isOpen=false;(m.bgiframe&&c.fn.bgiframe&&k.bgiframe());(m.autoOpen&&this.open())},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");this.uiDialog.remove();(this.originalTitle&&this.element.attr("title",this.originalTitle))},close:function(f){var d=this;if(false===d._trigger("beforeclose",f)){return}(d.overlay&&d.overlay.destroy());d.uiDialog.unbind("keypress.ui-dialog");(d.options.hide?d.uiDialog.hide(d.options.hide,function(){d._trigger("close",f)}):d.uiDialog.hide()&&d._trigger("close",f));c.ui.dialog.overlay.resize();d._isOpen=false;if(d.options.modal){var e=0;c(".ui-dialog").each(function(){if(this!=d.uiDialog[0]){e=Math.max(e,c(this).css("z-index"))}});c.ui.dialog.maxZ=e}},isOpen:function(){return this._isOpen},moveToTop:function(f,e){if((this.options.modal&&!f)||(!this.options.stack&&!this.options.modal)){return this._trigger("focus",e)}if(this.options.zIndex>c.ui.dialog.maxZ){c.ui.dialog.maxZ=this.options.zIndex}(this.overlay&&this.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=++c.ui.dialog.maxZ));var d={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};this.uiDialog.css("z-index",++c.ui.dialog.maxZ);this.element.attr(d);this._trigger("focus",e)},open:function(){if(this._isOpen){return}var e=this.options,d=this.uiDialog;this.overlay=e.modal?new c.ui.dialog.overlay(this):null;(d.next().length&&d.appendTo("body"));this._size();this._position(e.position);d.show(e.show);this.moveToTop(true);(e.modal&&d.bind("keypress.ui-dialog",function(h){if(h.keyCode!=c.ui.keyCode.TAB){return}var g=c(":tabbable",this),i=g.filter(":first")[0],f=g.filter(":last")[0];if(h.target==f&&!h.shiftKey){setTimeout(function(){i.focus()},1)}else{if(h.target==i&&h.shiftKey){setTimeout(function(){f.focus()},1)}}}));c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();this._trigger("open");this._isOpen=true},_createButtons:function(g){var f=this,d=false,e=c("
").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiDialog.find(".ui-dialog-buttonpane").remove();(typeof g=="object"&&g!==null&&c.each(g,function(){return !(d=true)}));if(d){c.each(g,function(h,i){c(' ').addClass("ui-state-default ui-corner-all").text(h).click(function(){i.apply(f.element[0],arguments)}).hover(function(){c(this).addClass("ui-state-hover")},function(){c(this).removeClass("ui-state-hover")}).focus(function(){c(this).addClass("ui-state-focus")}).blur(function(){c(this).removeClass("ui-state-focus")}).appendTo(e)});e.appendTo(this.uiDialog)}},_makeDraggable:function(){var d=this,f=this.options,e;this.uiDialog.draggable({cancel:".ui-dialog-content",handle:".ui-dialog-titlebar",containment:"document",start:function(){e=f.height;c(this).height(c(this).height()).addClass("ui-dialog-dragging");(f.dragStart&&f.dragStart.apply(d.element[0],arguments))},drag:function(){(f.drag&&f.drag.apply(d.element[0],arguments))},stop:function(){c(this).removeClass("ui-dialog-dragging").height(e);(f.dragStop&&f.dragStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}})},_makeResizable:function(g){g=(g===undefined?this.options.resizable:g);var d=this,f=this.options,e=typeof g=="string"?g:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",alsoResize:this.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:f.minHeight,start:function(){c(this).addClass("ui-dialog-resizing");(f.resizeStart&&f.resizeStart.apply(d.element[0],arguments))},resize:function(){(f.resize&&f.resize.apply(d.element[0],arguments))},handles:e,stop:function(){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();(f.resizeStop&&f.resizeStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}}).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_position:function(i){var e=c(window),f=c(document),g=f.scrollTop(),d=f.scrollLeft(),h=g;if(c.inArray(i,["center","top","right","bottom","left"])>=0){i=[i=="right"||i=="left"?i:"center",i=="top"||i=="bottom"?i:"middle"]}if(i.constructor!=Array){i=["center","middle"]}if(i[0].constructor==Number){d+=i[0]}else{switch(i[0]){case"left":d+=0;break;case"right":d+=e.width()-this.uiDialog.outerWidth();break;default:case"center":d+=(e.width()-this.uiDialog.outerWidth())/2}}if(i[1].constructor==Number){g+=i[1]}else{switch(i[1]){case"top":g+=0;break;case"bottom":g+=e.height()-this.uiDialog.outerHeight();break;default:case"middle":g+=(e.height()-this.uiDialog.outerHeight())/2}}g=Math.max(g,h);this.uiDialog.css({top:g,left:d})},_setData:function(e,f){(b[e]&&this.uiDialog.data(b[e],f));switch(e){case"buttons":this._createButtons(f);break;case"closeText":this.uiDialogTitlebarCloseText.text(f);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(a+f);break;case"draggable":(f?this._makeDraggable():this.uiDialog.draggable("destroy"));break;case"height":this.uiDialog.height(f);break;case"position":this._position(f);break;case"resizable":var d=this.uiDialog,g=this.uiDialog.is(":data(resizable)");(g&&!f&&d.resizable("destroy"));(g&&typeof f=="string"&&d.resizable("option","handles",f));(g||this._makeResizable(f));break;case"title":c(".ui-dialog-title",this.uiDialogTitlebar).html(f||" ");break;case"width":this.uiDialog.width(f);break}c.widget.prototype._setData.apply(this,arguments)},_size:function(){var e=this.options;this.element.css({height:0,minHeight:0,width:"auto"});var d=this.uiDialog.css({height:"auto",width:e.width}).height();this.element.css({minHeight:Math.max(e.minHeight-d,0),height:e.height=="auto"?"auto":Math.max(e.height-d,0)})}});c.extend(c.ui.dialog,{version:"1.7.2",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1000},getter:"isOpen",uuid:0,maxZ:0,getTitleId:function(d){return"ui-dialog-title-"+(d.attr("id")||++this.uuid)},overlay:function(d){this.$el=c.ui.dialog.overlay.create(d)}});c.extend(c.ui.dialog.overlay,{instances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(d){return d+".dialog-overlay"}).join(" "),create:function(e){if(this.instances.length===0){setTimeout(function(){if(c.ui.dialog.overlay.instances.length){c(document).bind(c.ui.dialog.overlay.events,function(f){var g=c(f.target).parents(".ui-dialog").css("zIndex")||0;return(g>c.ui.dialog.overlay.maxZ)})}},1);c(document).bind("keydown.dialog-overlay",function(f){(e.options.closeOnEscape&&f.keyCode&&f.keyCode==c.ui.keyCode.ESCAPE&&e.close(f))});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var d=c("
").appendTo(document.body).addClass("ui-widget-overlay").css({width:this.width(),height:this.height()});(e.options.bgiframe&&c.fn.bgiframe&&d.bgiframe());this.instances.push(d);return d},destroy:function(d){this.instances.splice(c.inArray(this.instances,d),1);if(this.instances.length===0){c([document,window]).unbind(".dialog-overlay")}d.remove();var e=0;c.each(this.instances,function(){e=Math.max(e,this.css("z-index"))});this.maxZ=e},height:function(){if(c.browser.msie&&c.browser.version<7){var e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var d=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(e');var j=f.parent();if(f.css("position")=="static"){j.css({position:"relative"});f.css({position:"relative"})}else{var i=f.css("top");if(isNaN(parseInt(i,10))){i="auto"}var h=f.css("left");if(isNaN(parseInt(h,10))){h="auto"}j.css({position:f.css("position"),top:i,left:h,zIndex:f.css("z-index")}).show();f.css({position:"relative",top:0,left:0})}j.css(g);return j},removeWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent().replaceWith(f)}return f},setTransition:function(g,i,f,h){h=h||{};d.each(i,function(k,j){unit=g.cssUnit(j);if(unit[0]>0){h[j]=unit[0]*f+unit[1]}});return h},animateClass:function(h,i,k,j){var f=(typeof k=="function"?k:(j?j:null));var g=(typeof k=="string"?k:null);return this.each(function(){var q={};var o=d(this);var p=o.attr("style")||"";if(typeof p=="object"){p=p.cssText}if(h.toggle){o.hasClass(h.toggle)?h.remove=h.toggle:h.add=h.toggle}var l=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.addClass(h.add)}if(h.remove){o.removeClass(h.remove)}var m=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.removeClass(h.add)}if(h.remove){o.addClass(h.remove)}for(var r in m){if(typeof m[r]!="function"&&m[r]&&r.indexOf("Moz")==-1&&r.indexOf("length")==-1&&m[r]!=l[r]&&(r.match(/color/i)||(!r.match(/color/i)&&!isNaN(parseInt(m[r],10))))&&(l.position!="static"||(l.position=="static"&&!r.match(/left|top|bottom|right/)))){q[r]=m[r]}}o.animate(q,i,g,function(){if(typeof d(this).attr("style")=="object"){d(this).attr("style")["cssText"]="";d(this).attr("style")["cssText"]=p}else{d(this).attr("style",p)}if(h.add){d(this).addClass(h.add)}if(h.remove){d(this).removeClass(h.remove)}if(f){f.apply(this,arguments)}})})}};function c(g,f){var i=g[1]&&g[1].constructor==Object?g[1]:{};if(f){i.mode=f}var h=g[1]&&g[1].constructor!=Object?g[1]:(i.duration?i.duration:g[2]);h=d.fx.off?0:typeof h==="number"?h:d.fx.speeds[h]||d.fx.speeds._default;var j=i.callback||(d.isFunction(g[1])&&g[1])||(d.isFunction(g[2])&&g[2])||(d.isFunction(g[3])&&g[3]);return[g[0],i,h,j]}d.fn.extend({_show:d.fn.show,_hide:d.fn.hide,__toggle:d.fn.toggle,_addClass:d.fn.addClass,_removeClass:d.fn.removeClass,_toggleClass:d.fn.toggleClass,effect:function(g,f,h,i){return d.effects[g]?d.effects[g].call(this,{method:g,options:f||{},duration:h,callback:i}):null},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._show.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"show"))}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._hide.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"hide"))}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||(d.isFunction(arguments[0])||typeof arguments[0]=="boolean")){return this.__toggle.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"toggle"))}},addClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{add:g},f,i,h]):this._addClass(g)},removeClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{remove:g},f,i,h]):this._removeClass(g)},toggleClass:function(g,f,i,h){return((typeof f!=="boolean")&&f)?d.effects.animateClass.apply(this,[{toggle:g},f,i,h]):this._toggleClass(g,f)},morph:function(f,h,g,j,i){return d.effects.animateClass.apply(this,[{add:h,remove:f},g,j,i])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(f){var g=this.css(f),h=[];d.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}});d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(g,f){d.fx.step[f]=function(h){if(h.state==0){h.start=e(h.elem,f);h.end=b(h.end)}h.elem.style[f]="rgb("+[Math.max(Math.min(parseInt((h.pos*(h.end[0]-h.start[0]))+h.start[0],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[1]-h.start[1]))+h.start[1],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[2]-h.start[2]))+h.start[2],10),255),0)].join(",")+")"}});function b(g){var f;if(g&&g.constructor==Array&&g.length==3){return g}if(f=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(g)){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}if(f=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(g)){return[parseFloat(f[1])*2.55,parseFloat(f[2])*2.55,parseFloat(f[3])*2.55]}if(f=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(g)){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}if(f=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(g)){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}if(f=/rgba\(0, 0, 0, 0\)/.exec(g)){return a.transparent}return a[d.trim(g).toLowerCase()]}function e(h,f){var g;do{g=d.curCSS(h,f);if(g!=""&&g!="transparent"||d.nodeName(h,"body")){break}f="backgroundColor"}while(h=h.parentNode);return b(g)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};d.easing.jswing=d.easing.swing;d.extend(d.easing,{def:"easeOutQuad",swing:function(g,h,f,j,i){return d.easing[d.easing.def](g,h,f,j,i)},easeInQuad:function(g,h,f,j,i){return j*(h/=i)*h+f},easeOutQuad:function(g,h,f,j,i){return -j*(h/=i)*(h-2)+f},easeInOutQuad:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h+f}return -j/2*((--h)*(h-2)-1)+f},easeInCubic:function(g,h,f,j,i){return j*(h/=i)*h*h+f},easeOutCubic:function(g,h,f,j,i){return j*((h=h/i-1)*h*h+1)+f},easeInOutCubic:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h+f}return j/2*((h-=2)*h*h+2)+f},easeInQuart:function(g,h,f,j,i){return j*(h/=i)*h*h*h+f},easeOutQuart:function(g,h,f,j,i){return -j*((h=h/i-1)*h*h*h-1)+f},easeInOutQuart:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h+f}return -j/2*((h-=2)*h*h*h-2)+f},easeInQuint:function(g,h,f,j,i){return j*(h/=i)*h*h*h*h+f},easeOutQuint:function(g,h,f,j,i){return j*((h=h/i-1)*h*h*h*h+1)+f},easeInOutQuint:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h*h+f}return j/2*((h-=2)*h*h*h*h+2)+f},easeInSine:function(g,h,f,j,i){return -j*Math.cos(h/i*(Math.PI/2))+j+f},easeOutSine:function(g,h,f,j,i){return j*Math.sin(h/i*(Math.PI/2))+f},easeInOutSine:function(g,h,f,j,i){return -j/2*(Math.cos(Math.PI*h/i)-1)+f},easeInExpo:function(g,h,f,j,i){return(h==0)?f:j*Math.pow(2,10*(h/i-1))+f},easeOutExpo:function(g,h,f,j,i){return(h==i)?f+j:j*(-Math.pow(2,-10*h/i)+1)+f},easeInOutExpo:function(g,h,f,j,i){if(h==0){return f}if(h==i){return f+j}if((h/=i/2)<1){return j/2*Math.pow(2,10*(h-1))+f}return j/2*(-Math.pow(2,-10*--h)+2)+f},easeInCirc:function(g,h,f,j,i){return -j*(Math.sqrt(1-(h/=i)*h)-1)+f},easeOutCirc:function(g,h,f,j,i){return j*Math.sqrt(1-(h=h/i-1)*h)+f},easeInOutCirc:function(g,h,f,j,i){if((h/=i/2)<1){return -j/2*(Math.sqrt(1-h*h)-1)+f}return j/2*(Math.sqrt(1-(h-=2)*h)+1)+f},easeInElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else
+ options.data = q; // data is the query string for 'post'
+
+ var $form = this, callbacks = [];
+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ $(options.target).html(data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success)
+ callbacks.push(options.success);
+
+ options.success = function(data, status) {
+ for (var i=0, max=callbacks.length; i < max; i++)
+ callbacks[i].apply(options, [data, status, $form]);
+ };
+
+ // are there files to upload?
+ var files = $('input:file', this).fieldValue();
+ var found = false;
+ for (var j=0; j < files.length; j++)
+ if (files[j])
+ found = true;
+
+ var multipart = false;
+// var mp = 'multipart/form-data';
+// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ // options.iframe allows user to force iframe mode
+ if (options.iframe || found || multipart) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive)
+ $.get(options.closeKeepAlive, fileUpload);
+ else
+ fileUpload();
+ }
+ else
+ $.ajax(options);
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUpload() {
+ var form = $form[0];
+
+ /* (this breaks the watermark form uploader, turn it off for now)
+ if ($(':input[name=submit]', form).length) {
+ alert('Error: Form elements must not be named "submit".');
+ return;
+ }
+ */
+
+ var opts = $.extend({}, $.ajaxSettings, options);
+ var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
+
+ var id = 'jqFormIO' + (new Date().getTime());
+ var $io = $('');
+ var io = $io[0];
+
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+ var xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function() {
+ this.aborted = 1;
+ $io.attr('src','about:blank'); // abort op in progress
+ }
+ };
+
+ var g = opts.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && ! $.active++) $.event.trigger("ajaxStart");
+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+
+ if (s.beforeSend && s.beforeSend(xhr, s) === false) {
+ s.global && $.active--;
+ return;
+ }
+ if (xhr.aborted)
+ return;
+
+ var cbInvoked = 0;
+ var timedOut = 0;
+
+ // add submitting element to data if we know it
+ var sub = form.clk;
+ if (sub) {
+ var n = sub.name;
+ if (n && !sub.disabled) {
+ options.extraData = options.extraData || {};
+ options.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ options.extraData[name+'.x'] = form.clk_x;
+ options.extraData[name+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ setTimeout(function() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (form.getAttribute('method') != 'POST')
+ form.setAttribute('method', 'POST');
+ if (form.getAttribute('action') != opts.url)
+ form.setAttribute('action', opts.url);
+
+ // ie borks in some cases when setting encoding
+ if (! options.skipEncodingOverride) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (opts.timeout)
+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (options.extraData)
+ for (var n in options.extraData)
+ extraInputs.push(
+ $(' ')
+ .appendTo(form)[0]);
+
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ t ? form.setAttribute('target', t) : $form.removeAttr('target');
+ $(extraInputs).remove();
+ }
+ }, 10);
+
+ var nullCheckFlag = 0;
+
+ function cb() {
+ if (cbInvoked++) return;
+
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+ var ok = true;
+ try {
+ if (timedOut) throw 'timeout';
+ // extract the server response from the iframe
+ var data, doc;
+
+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+
+ if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
+ // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
+ // the onload callback fires, so we give them a 2nd chance
+ nullCheckFlag = 1;
+ cbInvoked--;
+ setTimeout(cb, 100);
+ return;
+ }
+
+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': opts.dataType};
+ return headers[header];
+ };
+
+ if (opts.dataType == 'json' || opts.dataType == 'script') {
+ var ta = doc.getElementsByTagName('textarea')[0];
+ xhr.responseText = ta ? ta.value : xhr.responseText;
+ }
+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+ data = $.httpData(xhr, opts.dataType);
+ }
+ catch(e){
+ ok = false;
+ $.handleError(opts, xhr, 'error', e);
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (ok) {
+ opts.success(data, 'success');
+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+ }
+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+ if (g && ! --$.active) $.event.trigger("ajaxStop");
+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+ // clean up
+ setTimeout(function() {
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ };
+
+ function toXml(s, doc) {
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
+ };
+ };
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
+ $(this).ajaxSubmit(options);
+ return false;
+ }).each(function() {
+ // store options in hash
+ $(":submit,input:image", this).bind('click.form-plugin',function(e) {
+ var form = this.form;
+ form.clk = this;
+ if (this.type == 'image') {
+ if (e.offsetX != undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+ var offset = $(this).offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - this.offsetLeft;
+ form.clk_y = e.pageY - this.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
+ });
+ });
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ this.unbind('submit.form-plugin');
+ return this.each(function() {
+ $(":submit,input:image", this).unbind('click.form-plugin');
+ });
+
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+ var a = [];
+ if (this.length == 0) return a;
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) return a;
+ for(var i=0, max=els.length; i < max; i++) {
+ var el = els[i];
+ var n = el.name;
+ if (!n) continue;
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el) {
+ a.push({name: n, value: $(el).val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ var v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ for(var j=0, jmax=v.length; j < jmax; j++)
+ a.push({name: n, value: v[j]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: n, value: v});
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0], n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) return;
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++)
+ a.push({name: n, value: v[i]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: this.name, value: v});
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ *
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
+ continue;
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (typeof successful == 'undefined') successful = true;
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1))
+ return null;
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) return null;
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) // extra pain for IE...
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+ if (one) return v;
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return el.value;
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields();
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (t == 'text' || t == 'password' || tag == 'textarea')
+ this.value = '';
+ else if (t == 'checkbox' || t == 'radio')
+ this.checked = false;
+ else if (tag == 'select')
+ this.selectedIndex = -1;
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
+ this.reset();
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b == undefined) b = true;
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select == undefined) select = true;
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio')
+ this.checked = select;
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// helper fn for console logging
+// set $.fn.ajaxSubmit.debug to true to enable debug logging
+function log() {
+ if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
+ window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
+};
+
+})(jQuery);
diff --git a/web_client/js/jquery.js b/web_client/js/jquery.js
new file mode 100644
index 00000000..b1ae21d8
--- /dev/null
+++ b/web_client/js/jquery.js
@@ -0,0 +1,19 @@
+/*
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,""]||!O.indexOf(""," "]||(!O.indexOf(""," "]||!O.indexOf(""," "]||!o.support.htmlSerialize&&[1,"div","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
+/*
+ * Sizzle CSS Selector Engine - v0.9.3
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V ";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML=" ";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="
";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(Fa text ';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(/';
+ }
+
+ return $compiled."\n";
+ }
+
+ /**
+ * Creates a image link.
+ *
+ * @param string image source, or an array of attributes
+ * @param string|array image alt attribute, or an array of attributes
+ * @param boolean include the index_page in the link
+ * @return string
+ */
+ public static function image($src = NULL, $alt = NULL, $index = FALSE)
+ {
+ // Create attribute list
+ $attributes = is_array($src) ? $src : array('src' => $src);
+
+ if (is_array($alt))
+ {
+ $attributes += $alt;
+ }
+ elseif ( ! empty($alt))
+ {
+ // Add alt to attributes
+ $attributes['alt'] = $alt;
+ }
+
+ if (strpos($attributes['src'], '://') === FALSE)
+ {
+ // Make the src attribute into an absolute URL
+ $attributes['src'] = url::base($index).$attributes['src'];
+ }
+
+ return ' ';
+ }
+
+ /**
+ * Compiles an array of HTML attributes into an attribute string.
+ *
+ * @param string|array array of attributes
+ * @return string
+ */
+ public static function attributes($attrs)
+ {
+ if (empty($attrs))
+ return '';
+
+ if (is_string($attrs))
+ return ' '.$attrs;
+
+ $compiled = '';
+ foreach ($attrs as $key => $val)
+ {
+ $compiled .= ' '.$key.'="'.htmlspecialchars($val, ENT_QUOTES, Kohana::CHARSET).'"';
+ }
+
+ return $compiled;
+ }
+
+} // End html
diff --git a/web_client/system/helpers/inflector.php b/web_client/system/helpers/inflector.php
new file mode 100644
index 00000000..9bd281db
--- /dev/null
+++ b/web_client/system/helpers/inflector.php
@@ -0,0 +1,254 @@
+ 1)
+ return $str;
+
+ // Cache key name
+ $key = 'singular_'.$str.$count;
+
+ if (isset(inflector::$cache[$key]))
+ return inflector::$cache[$key];
+
+ if (inflector::uncountable($str))
+ return inflector::$cache[$key] = $str;
+
+ if (empty(inflector::$irregular))
+ {
+ // Cache irregular words
+ inflector::$irregular = Kohana::config('inflector.irregular');
+ }
+
+ if ($irregular = array_search($str, inflector::$irregular))
+ {
+ $str = $irregular;
+ }
+ elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str))
+ {
+ // Remove "es"
+ $str = substr($str, 0, -2);
+ }
+ elseif (preg_match('/[^aeiou]ies$/', $str))
+ {
+ $str = substr($str, 0, -3).'y';
+ }
+ elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
+ {
+ $str = substr($str, 0, -1);
+ }
+
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ public static function plural($str, $count = NULL)
+ {
+ if ( ! $str)
+ return $str;
+
+ $parts = explode('_', $str);
+
+ $last = inflector::_plural(array_pop($parts), $count);
+
+ $pre = implode('_', $parts);
+ if (strlen($pre))
+ $pre .= '_';
+
+ return $pre.$last;
+ }
+
+
+ /**
+ * Makes a singular word plural.
+ *
+ * @param string word to pluralize
+ * @return string
+ */
+ public static function _plural($str, $count = NULL)
+ {
+ // Remove garbage
+ $str = strtolower(trim($str));
+
+ if (is_string($count))
+ {
+ // Convert to integer when using a digit string
+ $count = (int) $count;
+ }
+
+ // Do nothing with singular
+ if ($count === 1)
+ return $str;
+
+ // Cache key name
+ $key = 'plural_'.$str.$count;
+
+ if (isset(inflector::$cache[$key]))
+ return inflector::$cache[$key];
+
+ if (inflector::uncountable($str))
+ return inflector::$cache[$key] = $str;
+
+ if (empty(inflector::$irregular))
+ {
+ // Cache irregular words
+ inflector::$irregular = Kohana::config('inflector.irregular');
+ }
+
+ if (isset(inflector::$irregular[$str]))
+ {
+ $str = inflector::$irregular[$str];
+ }
+ elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str))
+ {
+ $str .= 'es';
+ }
+ elseif (preg_match('/[^aeiou]y$/', $str))
+ {
+ // Change "y" to "ies"
+ $str = substr_replace($str, 'ies', -1);
+ }
+ else
+ {
+ $str .= 's';
+ }
+
+ // Set the cache and return
+ return inflector::$cache[$key] = $str;
+ }
+
+ /**
+ * Makes a word possessive.
+ *
+ * @param string word to to make possessive
+ * @return string
+ */
+ public static function possessive($string)
+ {
+ $length = strlen($string);
+
+ if (substr($string, $length - 1, $length) == 's')
+ {
+ return $string.'\'';
+ }
+
+ return $string.'\'s';
+ }
+
+ /**
+ * Makes a phrase camel case.
+ *
+ * @param string phrase to camelize
+ * @return string
+ */
+ public static function camelize($str)
+ {
+ $str = 'x'.strtolower(trim($str));
+ $str = ucwords(preg_replace('/[\s_]+/', ' ', $str));
+
+ return substr(str_replace(' ', '', $str), 1);
+ }
+
+ /**
+ * Makes a phrase underscored instead of spaced.
+ *
+ * @param string phrase to underscore
+ * @return string
+ */
+ public static function underscore($str)
+ {
+ return trim(preg_replace('/[\s_]+/', '_', $str), '_');
+ }
+
+ /**
+ * Makes an underscored or dashed phrase human-reable.
+ *
+ * @param string phrase to make human-reable
+ * @return string
+ */
+ public static function humanize($str)
+ {
+ return trim(preg_replace('/[_-\s]+/', ' ', $str));
+ }
+
+} // End inflector
\ No newline at end of file
diff --git a/web_client/system/helpers/num.php b/web_client/system/helpers/num.php
new file mode 100644
index 00000000..42f13bec
--- /dev/null
+++ b/web_client/system/helpers/num.php
@@ -0,0 +1,26 @@
+ $method));
+
+ return $method;
+ }
+
+ /**
+ * Retrieves current user agent information
+ * keys: browser, version, platform, mobile, robot
+ *
+ * @param string key
+ * @return mixed NULL or the parsed value
+ */
+ public static function user_agent($key = 'agent')
+ {
+ // Retrieve raw user agent without parsing
+ if ($key === 'agent')
+ {
+ if (request::$user_agent === NULL)
+ return request::$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ if (is_array(request::$user_agent))
+ return request::$user_agent['agent'];
+
+ return request::$user_agent;
+ }
+
+ if ( ! is_array(request::$user_agent))
+ {
+ request::$user_agent['agent'] = isset($_SERVER['HTTP_USER_AGENT']) ? trim($_SERVER['HTTP_USER_AGENT']) : '';
+
+ // Parse the user agent and extract basic information
+ foreach (Kohana::config('user_agents') as $type => $data)
+ {
+ foreach ($data as $fragment => $name)
+ {
+ if (stripos(request::$user_agent['agent'], $fragment) !== FALSE)
+ {
+ if ($type === 'browser' AND preg_match('|'.preg_quote($fragment).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', request::$user_agent['agent'], $match))
+ {
+ // Set the browser version
+ request::$user_agent['version'] = $match[1];
+ }
+
+ // Set the agent name
+ request::$user_agent[$type] = $name;
+ break;
+ }
+ }
+ }
+ }
+
+ return isset(request::$user_agent[$key]) ? request::$user_agent[$key] : NULL;
+ }
+
+ /**
+ * Returns boolean of whether client accepts content type.
+ *
+ * @param string content type
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts($type = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ if ($type === NULL)
+ return request::$accept_types;
+
+ return (request::accepts_at_quality($type, $explicit_check) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts a charset
+ *
+ * @param string
+ * @return boolean
+ */
+ public static function accepts_charset($charset = NULL)
+ {
+ request::parse_accept_charset_header();
+
+ if ($charset === NULL)
+ return request::$accept_charsets;
+
+ return (request::accepts_charset_at_quality($charset) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts an encoding
+ *
+ * @param string
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return boolean
+ */
+ public static function accepts_encoding($encoding = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ if ($encoding === NULL)
+ return request::$accept_encodings;
+
+ return (request::accepts_encoding_at_quality($encoding, $explicit_check) > 0);
+ }
+
+ /**
+ * Returns boolean indicating if the client accepts a language tag
+ *
+ * @param string language tag
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return boolean
+ */
+ public static function accepts_language($tag = NULL, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ if ($tag === NULL)
+ return request::$accept_languages;
+
+ return (request::accepts_language_at_quality($tag, $explicit_check) > 0);
+ }
+
+ /**
+ * Compare the q values for given array of content types and return the one with the highest value.
+ * If items are found to have the same q value, the first one encountered in the given array wins.
+ * If all items in the given array have a q value of 0, FALSE is returned.
+ *
+ * @param array content types
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed string mime type with highest q value, FALSE if none of the given types are accepted
+ */
+ public static function preferred_accept($types, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($types as $type)
+ {
+ $q = request::accepts_at_quality($type, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $type;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of character sets and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array character sets
+ * @return mixed
+ */
+ public static function preferred_charset($charsets)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($charsets as $charset)
+ {
+ $q = request::accepts_charset_at_quality($charset);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $charset;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of encodings and return the one with
+ * the highest value. If items are found to have the same q value, the first
+ * one encountered takes precedence. If all items in the given array have
+ * a q value of 0, FALSE is returned.
+ *
+ * @param array encodings
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return mixed
+ */
+ public static function preferred_encoding($encodings, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($encodings as $encoding)
+ {
+ $q = request::accepts_encoding_at_quality($encoding, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $encoding;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Compare the q values for a given array of language tags and return the
+ * one with the highest value. If items are found to have the same q value,
+ * the first one encountered takes precedence. If all items in the given
+ * array have a q value of 0, FALSE is returned.
+ *
+ * @param array language tags
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return mixed
+ */
+ public static function preferred_language($tags, $explicit_check = FALSE)
+ {
+ $max_q = 0;
+ $preferred = FALSE;
+
+ foreach ($tags as $tag)
+ {
+ $q = request::accepts_language_at_quality($tag, $explicit_check);
+
+ if ($q > $max_q)
+ {
+ $max_q = $q;
+ $preferred = $tag;
+ }
+ }
+
+ return $preferred;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts content type
+ *
+ * @param string content type (e.g. "image/jpg", "jpg")
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_at_quality($type, $explicit_check = FALSE)
+ {
+ request::parse_accept_content_header();
+
+ // Normalize type
+ $type = strtolower($type);
+
+ // General content type (e.g. "jpg")
+ if (strpos($type, '/') === FALSE)
+ {
+ // Don't accept anything by default
+ $q = 0;
+
+ // Look up relevant mime types
+ foreach ((array) Kohana::config('mimes.'.$type) as $type)
+ {
+ $q2 = request::accepts_at_quality($type, $explicit_check);
+ $q = ($q2 > $q) ? $q2 : $q;
+ }
+
+ return $q;
+ }
+
+ // Content type with subtype given (e.g. "image/jpg")
+ $type = explode('/', $type, 2);
+
+ // Exact match
+ if (isset(request::$accept_types[$type[0]][$type[1]]))
+ return request::$accept_types[$type[0]][$type[1]];
+
+ if ($explicit_check === FALSE)
+ {
+ // Wildcard match
+ if (isset(request::$accept_types[$type[0]]['*']))
+ return request::$accept_types[$type[0]]['*'];
+
+ // Catch-all wildcard match
+ if (isset(request::$accept_types['*']['*']))
+ return request::$accept_types['*']['*'];
+ }
+
+ // Content type not accepted
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts a charset
+ *
+ * @param string charset (e.g., "ISO-8859-1", "utf-8")
+ * @return integer|float
+ */
+ public static function accepts_charset_at_quality($charset)
+ {
+ request::parse_accept_charset_header();
+
+ // Normalize charset
+ $charset = strtolower($charset);
+
+ // Exact match
+ if (isset(request::$accept_charsets[$charset]))
+ return request::$accept_charsets[$charset];
+
+ if (isset(request::$accept_charsets['*']))
+ return request::$accept_charsets['*'];
+
+ if ($charset === 'iso-8859-1')
+ return 1;
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts an encoding
+ *
+ * @param string encoding (e.g., "gzip", "deflate")
+ * @param boolean set to TRUE to disable wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_encoding_at_quality($encoding, $explicit_check = FALSE)
+ {
+ request::parse_accept_encoding_header();
+
+ // Normalize encoding
+ $encoding = strtolower($encoding);
+
+ // Exact match
+ if (isset(request::$accept_encodings[$encoding]))
+ return request::$accept_encodings[$encoding];
+
+ if ($explicit_check === FALSE)
+ {
+ if (isset(request::$accept_encodings['*']))
+ return request::$accept_encodings['*'];
+
+ if ($encoding === 'identity')
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns quality factor at which the client accepts a language
+ *
+ * @param string tag (e.g., "en", "en-us", "fr-ca")
+ * @param boolean set to TRUE to disable prefix and wildcard checking
+ * @return integer|float
+ */
+ public static function accepts_language_at_quality($tag, $explicit_check = FALSE)
+ {
+ request::parse_accept_language_header();
+
+ $tag = explode('-', strtolower($tag), 2);
+
+ if (isset(request::$accept_languages[$tag[0]]))
+ {
+ if (isset($tag[1]))
+ {
+ // Exact match
+ if (isset(request::$accept_languages[$tag[0]][$tag[1]]))
+ return request::$accept_languages[$tag[0]][$tag[1]];
+
+ // A prefix matches
+ if ($explicit_check === FALSE AND isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ else
+ {
+ // No subtags
+ if (isset(request::$accept_languages[$tag[0]]['*']))
+ return request::$accept_languages[$tag[0]]['*'];
+ }
+ }
+
+ if ($explicit_check === FALSE AND isset(request::$accept_languages['*']))
+ return request::$accept_languages['*'];
+
+ return 0;
+ }
+
+ /**
+ * Parses a HTTP Accept or Accept-* header for q values
+ *
+ * @param string header data
+ * @return array
+ */
+ protected static function parse_accept_header($header)
+ {
+ $result = array();
+
+ // Remove linebreaks and parse the HTTP Accept header
+ foreach (explode(',', str_replace(array("\r", "\n"), '', strtolower($header))) as $entry)
+ {
+ // Explode each entry in content type and possible quality factor
+ $entry = explode(';', trim($entry), 2);
+
+ $q = (isset($entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $entry[1], $match)) ? (float) $match[1] : 1;
+
+ // Overwrite entries with a smaller q value
+ if ( ! isset($result[$entry[0]]) OR $q > $result[$entry[0]])
+ {
+ $result[$entry[0]] = $q;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Charset header
+ */
+ protected static function parse_accept_charset_header()
+ {
+ // Run this function just once
+ if (request::$accept_charsets !== NULL)
+ return;
+
+ // No HTTP Accept-Charset header found
+ if (empty($_SERVER['HTTP_ACCEPT_CHARSET']))
+ {
+ // Accept everything
+ request::$accept_charsets['*'] = 1;
+ }
+ else
+ {
+ request::$accept_charsets = request::parse_accept_header($_SERVER['HTTP_ACCEPT_CHARSET']);
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept header
+ */
+ protected static function parse_accept_content_header()
+ {
+ // Run this function just once
+ if (request::$accept_types !== NULL)
+ return;
+
+ // No HTTP Accept header found
+ if (empty($_SERVER['HTTP_ACCEPT']))
+ {
+ // Accept everything
+ request::$accept_types['*']['*'] = 1;
+ }
+ else
+ {
+ request::$accept_types = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT']) as $type => $q)
+ {
+ // Explode each content type (e.g. "text/html")
+ $type = explode('/', $type, 2);
+
+ // Skip invalid content types
+ if ( ! isset($type[1]))
+ continue;
+
+ request::$accept_types[$type[0]][$type[1]] = $q;
+ }
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Encoding header
+ */
+ protected static function parse_accept_encoding_header()
+ {
+ // Run this function just once
+ if (request::$accept_encodings !== NULL)
+ return;
+
+ // No HTTP Accept-Encoding header found
+ if ( ! isset($_SERVER['HTTP_ACCEPT_ENCODING']))
+ {
+ // Accept everything
+ request::$accept_encodings['*'] = 1;
+ }
+ elseif ($_SERVER['HTTP_ACCEPT_ENCODING'] === '')
+ {
+ // Accept only identity
+ request::$accept_encodings['identity'] = 1;
+ }
+ else
+ {
+ request::$accept_encodings = request::parse_accept_header($_SERVER['HTTP_ACCEPT_ENCODING']);
+ }
+ }
+
+ /**
+ * Parses a client's HTTP Accept-Language header
+ */
+ protected static function parse_accept_language_header()
+ {
+ // Run this function just once
+ if (request::$accept_languages !== NULL)
+ return;
+
+ // No HTTP Accept-Language header found
+ if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+ {
+ // Accept everything
+ request::$accept_languages['*'] = 1;
+ }
+ else
+ {
+ request::$accept_languages = array();
+
+ foreach (request::parse_accept_header($_SERVER['HTTP_ACCEPT_LANGUAGE']) as $tag => $q)
+ {
+ // Explode each language (e.g. "en-us")
+ $tag = explode('-', $tag, 2);
+
+ request::$accept_languages[$tag[0]][isset($tag[1]) ? $tag[1] : '*'] = $q;
+ }
+ }
+ }
+
+} // End request
diff --git a/web_client/system/helpers/security.php b/web_client/system/helpers/security.php
new file mode 100644
index 00000000..33e5118e
--- /dev/null
+++ b/web_client/system/helpers/security.php
@@ -0,0 +1,37 @@
+xss_clean($str, $tool);
+ }
+
+ /**
+ * Remove image tags from a string.
+ *
+ * @param string string to sanitize
+ * @return string
+ */
+ public static function strip_image_tags($str)
+ {
+ return preg_replace('# \s]*)["\']?[^>]*)?>#is', '$1', $str);
+ }
+
+} // End security
\ No newline at end of file
diff --git a/web_client/system/helpers/text.php b/web_client/system/helpers/text.php
new file mode 100644
index 00000000..ed7f9cbf
--- /dev/null
+++ b/web_client/system/helpers/text.php
@@ -0,0 +1,598 @@
+ 1)
+ {
+ if (ctype_alpha($str))
+ {
+ // Add a random digit
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
+ }
+ elseif (ctype_digit($str))
+ {
+ // Add a random letter
+ $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Reduces multiple slashes in a string to single slashes.
+ *
+ * @param string string to reduce slashes of
+ * @return string
+ */
+ public static function reduce_slashes($str)
+ {
+ return preg_replace('#(? $badword)
+ {
+ $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
+ }
+
+ $regex = '('.implode('|', $badwords).')';
+
+ if ($replace_partial_words === FALSE)
+ {
+ // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
+ $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
+ }
+
+ $regex = '!'.$regex.'!ui';
+
+ if (mb_strlen($replacement) == 1)
+ {
+ $regex .= 'e';
+ return preg_replace($regex, 'str_repeat($replacement, mb_strlen(\'$1\'))', $str);
+ }
+
+ return preg_replace($regex, $replacement, $str);
+ }
+
+ /**
+ * Finds the text that is similar between a set of words.
+ *
+ * @param array words to find similar text of
+ * @return string
+ */
+ public static function similar(array $words)
+ {
+ // First word is the word to match against
+ $word = current($words);
+
+ for ($i = 0, $max = strlen($word); $i < $max; ++$i)
+ {
+ foreach ($words as $w)
+ {
+ // Once a difference is found, break out of the loops
+ if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
+ break 2;
+ }
+ }
+
+ // Return the similar text
+ return substr($word, 0, $i);
+ }
+
+ /**
+ * An alternative to the php levenshtein() function that work out the
+ * distance between 2 words using the Damerau–Levenshtein algorithm.
+ * Credit: http://forums.devnetwork.net/viewtopic.php?f=50&t=89094
+ *
+ * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
+ * @param string first word
+ * @param string second word
+ * @return int distance between words
+ */
+ public static function distance($string1, $string2)
+ {
+ $string1_length = strlen($string1);
+ $string2_length = strlen($string2);
+
+ // Here we start building the table of values
+ $matrix = array();
+
+ // String1 length + 1 = rows.
+ for ($i = 0; $i <= $string1_length; ++$i)
+ {
+ $matrix[$i][0] = $i;
+ }
+
+ // String2 length + 1 columns.
+ for ($j = 0; $j <= $string2_length; ++$j)
+ {
+ $matrix[0][$j] = $j;
+ }
+
+ for ($i = 1; $i <= $string1_length; ++$i)
+ {
+ for ($j = 1; $j <= $string2_length; ++$j)
+ {
+ $cost = substr($string1, $i - 1, 1) == substr($string2, $j - 1, 1) ? 0 : 1;
+
+ $matrix[$i][$j] = min(
+ $matrix[$i - 1][$j] + 1, // deletion
+ $matrix[$i][$j - 1] + 1, // insertion
+ $matrix[$i - 1][$j - 1] + $cost // substitution
+ );
+
+ if ($i > 1 && $j > 1 && (substr($string1, $i - 1, 1) == substr($string2, $j - 2, 1))
+ && (substr($string1, $i - 2, 1) == substr($string2, $j - 1, 1)))
+ {
+ $matrix[$i][$j] = min(
+ $matrix[$i][$j],
+ $matrix[$i - 2][$j - 2] + $cost // transposition
+ );
+ }
+ }
+ }
+
+ return $matrix[$string1_length][$string2_length];
+ }
+
+ /**
+ * Converts text anchors into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_urls($text)
+ {
+
+ $regex = '~\\b'
+ .'((?:ht|f)tps?://)?' // protocol
+ .'(?:[-a-zA-Z0-9]{1,63}\.)+' // host name
+ .'(?:[0-9]{1,3}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)' // tlds
+ .'(?:/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?' // path
+ .'(?:\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // query
+ .'(?:#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // fragment
+ .'(?=[?.!,;:"]?(?:\s|$))~'; // punctuation and url end
+
+ $result = "";
+ $position = 0;
+
+ while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $position))
+ {
+ list($url, $url_pos) = $match[0];
+
+ // Add the text before the url
+ $result .= substr($text, $position, $url_pos - $position);
+
+ // Default to http://
+ $full_url = empty($match[1][0]) ? 'http://'.$url : $url;
+
+ // Add the hyperlink.
+ $result .= html::anchor($full_url, $url);
+
+ // New position to start parsing
+ $position = $url_pos + strlen($url);
+ }
+
+ return $result.substr($text, $position);
+ }
+
+ /**
+ * Converts text email addresses into links.
+ *
+ * @param string text to auto link
+ * @return string
+ */
+ public static function auto_link_emails($text)
+ {
+ // Finds all email addresses that are not part of an existing html mailto anchor
+ // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
+ // The html entity for a colon (:) is : or : or : etc.
+ if (preg_match_all('~\b(?|58;)(?!\.)[-+_a-z0-9.]++(? and markup to text. Basically nl2br() on steroids.
+ *
+ * @param string subject
+ * @param boolean convert single linebreaks to
+ * @return string
+ */
+ public static function auto_p($str, $br = TRUE)
+ {
+ // Trim whitespace
+ if (($str = trim($str)) === '')
+ return '';
+
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+
+ // Trim whitespace on each line
+ $str = preg_replace('~^[ \t]+~m', '', $str);
+ $str = preg_replace('~[ \t]+$~m', '', $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found = (strpos($str, '<') !== FALSE))
+ {
+ // Elements that should not be surrounded by p tags
+ $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
+
+ // Put at least two linebreaks before and after $no_p elements
+ $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
+ $str = preg_replace('~'.$no_p.'\s*+>$~im', "$0\n", $str);
+ }
+
+ // Do the magic!
+ $str = '
'.trim($str).'
';
+ $str = preg_replace('~\n{2,}~', "\n\n", $str);
+
+ // The following regexes only need to be executed if the string contains html
+ if ($html_found !== FALSE)
+ {
+ // Remove p tags around $no_p elements
+ $str = preg_replace('~
(?=?'.$no_p.'[^>]*+>)~i', '', $str);
+ $str = preg_replace('~(?'.$no_p.'[^>]*+>)
~i', '$1', $str);
+ }
+
+ // Convert single linebreaks to
+ if ($br === TRUE)
+ {
+ $str = preg_replace('~(?\n", $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Returns human readable sizes.
+ * @see Based on original functions written by:
+ * @see Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php
+ * @see Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/
+ *
+ * @param integer size in bytes
+ * @param string a definitive unit
+ * @param string the return string format
+ * @param boolean whether to use SI prefixes or IEC
+ * @return string
+ */
+ public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
+ {
+ // Format string
+ $format = ($format === NULL) ? '%01.2f %s' : (string) $format;
+
+ // IEC prefixes (binary)
+ if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
+ {
+ $units = array(__('B'), __('KiB'), __('MiB'), __('GiB'), __('TiB'), __('PiB'));
+ $mod = 1024;
+ }
+ // SI prefixes (decimal)
+ else
+ {
+ $units = array(__('B'), __('kB'), __('MB'), __('GB'), __('TB'), __('PB'));
+ $mod = 1000;
+ }
+
+ // Determine unit to use
+ if (($power = array_search((string) $force_unit, $units)) === FALSE)
+ {
+ $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
+ }
+
+ return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
+ }
+
+ /**
+ * Prevents widow words by inserting a non-breaking space between the last two words.
+ * @see http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin
+ *
+ * @param string string to remove widows from
+ * @return string
+ */
+ public static function widont($str)
+ {
+ $str = rtrim($str);
+ $space = strrpos($str, ' ');
+
+ if ($space !== FALSE)
+ {
+ $str = substr($str, 0, $space).' '.substr($str, $space + 1);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Tests whether a string contains only 7bit ASCII bytes. This is used to
+ * determine when to use native functions or UTF-8 functions.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to check
+ * @return bool
+ */
+ public static function is_ascii($str)
+ {
+ return is_string($str) AND ! preg_match('/[^\x00-\x7F]/S', $str);
+ }
+
+ /**
+ * Strips out device control codes in the ASCII range.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_ascii_ctrl($str)
+ {
+ return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Strips out all non-7bit ASCII bytes.
+ *
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function strip_non_ascii($str)
+ {
+ return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+ }
+
+ /**
+ * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'.
+ *
+ * @author Andreas Gohr
+ * @see http://sourceforge.net/projects/phputf8/
+ * @copyright (c) 2007-2009 Kohana Team
+ * @copyright (c) 2005 Harry Fuecks
+ * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ *
+ * @param string string to transliterate
+ * @param integer -1 lowercase only, +1 uppercase only, 0 both cases
+ * @return string
+ */
+ public static function transliterate_to_ascii($str, $case = 0)
+ {
+ static $UTF8_LOWER_ACCENTS = NULL;
+ static $UTF8_UPPER_ACCENTS = NULL;
+
+ if ($case <= 0)
+ {
+ if ($UTF8_LOWER_ACCENTS === NULL)
+ {
+ $UTF8_LOWER_ACCENTS = array(
+ 'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
+ 'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
+ 'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
+ 'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
+ 'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
+ 'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
+ 'ū' => 'u', 'č' => 'c', 'ö' => 'o', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
+ 'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
+ 'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
+ 'ŗ' => 'r', 'ä' => 'a', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'u', 'ò' => 'o',
+ 'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
+ 'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
+ 'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
+ 'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
+ 'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e', 'ı' => 'i',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_LOWER_ACCENTS),
+ array_values($UTF8_LOWER_ACCENTS),
+ $str
+ );
+ }
+
+ if ($case >= 0)
+ {
+ if ($UTF8_UPPER_ACCENTS === NULL)
+ {
+ $UTF8_UPPER_ACCENTS = array(
+ 'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
+ 'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K', 'Ĕ' => 'E',
+ 'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
+ 'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
+ 'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
+ 'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
+ 'Ū' => 'U', 'Č' => 'C', 'Ö' => 'O', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
+ 'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
+ 'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
+ 'Ŗ' => 'R', 'Ä' => 'A', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'U', 'Ò' => 'O',
+ 'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
+ 'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
+ 'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
+ 'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
+ 'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'İ' => 'I',
+ );
+ }
+
+ $str = str_replace(
+ array_keys($UTF8_UPPER_ACCENTS),
+ array_values($UTF8_UPPER_ACCENTS),
+ $str
+ );
+ }
+
+ return $str;
+ }
+
+} // End text
\ No newline at end of file
diff --git a/web_client/system/helpers/upload.php b/web_client/system/helpers/upload.php
new file mode 100644
index 00000000..3aec2ac4
--- /dev/null
+++ b/web_client/system/helpers/upload.php
@@ -0,0 +1,159 @@
+ $directory));
+
+ if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions on filename
+ chmod($filename, $chmod);
+ }
+
+ // Return new file path
+ return $filename;
+ }
+
+ return FALSE;
+ }
+
+ /* Validation Rules */
+
+ /**
+ * Tests if input data is valid file type, even if no upload is present.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function valid($file)
+ {
+ return (is_array($file)
+ AND isset($file['error'])
+ AND isset($file['name'])
+ AND isset($file['type'])
+ AND isset($file['tmp_name'])
+ AND isset($file['size']));
+ }
+
+ /**
+ * Tests if input data has valid upload data.
+ *
+ * @param array $_FILES item
+ * @return bool
+ */
+ public static function required(array $file)
+ {
+ return (isset($file['tmp_name'])
+ AND isset($file['error'])
+ AND is_uploaded_file($file['tmp_name'])
+ AND (int) $file['error'] === UPLOAD_ERR_OK);
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by extension.
+ *
+ * @param array $_FILES item
+ * @param array allowed file extensions
+ * @return bool
+ */
+ public static function type(array $file, array $allowed_types)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Get the default extension of the file
+ $extension = strtolower(substr(strrchr($file['name'], '.'), 1));
+
+ // Make sure there is an extension and that the extension is allowed
+ return ( ! empty($extension) AND in_array($extension, $allowed_types));
+ }
+
+ /**
+ * Validation rule to test if an uploaded file is allowed by file size.
+ * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and
+ * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+ * Eg: to limit the size to 1MB or less, you would use "1M".
+ *
+ * @param array $_FILES item
+ * @param array maximum file size
+ * @return bool
+ */
+ public static function size(array $file, array $size)
+ {
+ if ((int) $file['error'] !== UPLOAD_ERR_OK)
+ return TRUE;
+
+ // Only one size is allowed
+ $size = strtoupper($size[0]);
+
+ if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+ return FALSE;
+
+ // Make the size into a power of 1024
+ switch (substr($size, -1))
+ {
+ case 'G': $size = intval($size) * pow(1024, 3); break;
+ case 'M': $size = intval($size) * pow(1024, 2); break;
+ case 'K': $size = intval($size) * pow(1024, 1); break;
+ default: $size = intval($size); break;
+ }
+
+ // Test that the file is under or equal to the max size
+ return ($file['size'] <= $size);
+ }
+
+} // End upload
\ No newline at end of file
diff --git a/web_client/system/helpers/url.php b/web_client/system/helpers/url.php
new file mode 100644
index 00000000..4a94e894
--- /dev/null
+++ b/web_client/system/helpers/url.php
@@ -0,0 +1,264 @@
+ 'Refresh',
+ '300' => 'Multiple Choices',
+ '301' => 'Moved Permanently',
+ '302' => 'Found',
+ '303' => 'See Other',
+ '304' => 'Not Modified',
+ '305' => 'Use Proxy',
+ '307' => 'Temporary Redirect'
+ );
+
+ // Validate the method and default to 302
+ $method = isset($codes[$method]) ? (string) $method : '302';
+
+ if ($method === '300')
+ {
+ $uri = (array) $uri;
+
+ $output = '';
+ foreach ($uri as $link)
+ {
+ $output .= ''.html::anchor($link).' ';
+ }
+ $output .= ' ';
+
+ // The first URI will be used for the Location header
+ $uri = $uri[0];
+ }
+ else
+ {
+ $output = ''.html::anchor($uri).'
';
+ }
+
+ // Run the redirect event
+ Event::run('system.redirect', $uri);
+
+ if (strpos($uri, '://') === FALSE)
+ {
+ // HTTP headers expect absolute URLs
+ $uri = url::site($uri, request::protocol());
+ }
+
+ if ($method === 'refresh')
+ {
+ header('Refresh: 0; url='.$uri);
+ }
+ else
+ {
+ header('HTTP/1.1 '.$method.' '.$codes[$method]);
+ header('Location: '.$uri);
+ }
+
+ // We are about to exit, so run the send_headers event
+ Event::run('system.send_headers');
+
+ exit(''.$method.' - '.$codes[$method].' '.$output);
+ }
+
+} // End url
\ No newline at end of file
diff --git a/web_client/system/helpers/utf8.php b/web_client/system/helpers/utf8.php
new file mode 100644
index 00000000..20c6878c
--- /dev/null
+++ b/web_client/system/helpers/utf8.php
@@ -0,0 +1,746 @@
+
+ *
+ * @param string input string
+ * @param string replacement string
+ * @param integer offset
+ * @return string
+ */
+ public static function substr_replace($str, $replacement, $offset, $length = NULL)
+ {
+ if (text::is_ascii($str))
+ return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length);
+
+ $length = ($length === NULL) ? mb_strlen($str) : (int) $length;
+ preg_match_all('/./us', $str, $str_array);
+ preg_match_all('/./us', $replacement, $replacement_array);
+
+ array_splice($str_array[0], $offset, $length, $replacement_array[0]);
+ return implode('', $str_array[0]);
+ }
+
+ /**
+ * Makes a UTF-8 string's first character uppercase.
+ * @see http://php.net/ucfirst
+ *
+ * @author Harry Fuecks
+ *
+ * @param string mixed case string
+ * @return string
+ */
+ public static function ucfirst($str)
+ {
+ if (text::is_ascii($str))
+ return ucfirst($str);
+
+ preg_match('/^(.?)(.*)$/us', $str, $matches);
+ return mb_strtoupper($matches[1]).$matches[2];
+ }
+
+ /**
+ * Case-insensitive UTF-8 string comparison.
+ * @see http://php.net/strcasecmp
+ *
+ * @author Harry Fuecks
+ *
+ * @param string string to compare
+ * @param string string to compare
+ * @return integer less than 0 if str1 is less than str2
+ * @return integer greater than 0 if str1 is greater than str2
+ * @return integer 0 if they are equal
+ */
+ public static function strcasecmp($str1, $str2)
+ {
+ if (text::is_ascii($str1) AND text::is_ascii($str2))
+ return strcasecmp($str1, $str2);
+
+ $str1 = mb_strtolower($str1);
+ $str2 = mb_strtolower($str2);
+ return strcmp($str1, $str2);
+ }
+
+ /**
+ * Returns a string or an array with all occurrences of search in subject (ignoring case).
+ * replaced with the given replace value.
+ * @see http://php.net/str_ireplace
+ *
+ * @note It's not fast and gets slower if $search and/or $replace are arrays.
+ * @author Harry Fuecks $val)
+ {
+ $str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
+ }
+ return $str;
+ }
+
+ if (is_array($search))
+ {
+ $keys = array_keys($search);
+
+ foreach ($keys as $k)
+ {
+ if (is_array($replace))
+ {
+ if (array_key_exists($k, $replace))
+ {
+ $str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], '', $str, $count);
+ }
+ }
+ else
+ {
+ $str = utf8::str_ireplace($search[$k], $replace, $str, $count);
+ }
+ }
+ return $str;
+ }
+
+ $search = mb_strtolower($search);
+ $str_lower = mb_strtolower($str);
+
+ $total_matched_strlen = 0;
+ $i = 0;
+
+ while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+ {
+ $matched_strlen = strlen($matches[0]);
+ $str_lower = substr($str_lower, $matched_strlen);
+
+ $offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+ $str = substr_replace($str, $replace, $offset, strlen($search));
+
+ $total_matched_strlen += $matched_strlen;
+ $i++;
+ }
+
+ $count += $i;
+ return $str;
+ }
+
+ /**
+ * Case-insenstive UTF-8 version of strstr. Returns all of input string
+ * from the first occurrence of needle to the end.
+ * @see http://php.net/stristr
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string needle
+ * @return string matched substring if found
+ * @return boolean FALSE if the substring was not found
+ */
+ public static function stristr($str, $search)
+ {
+ if (text::is_ascii($str) AND text::is_ascii($search))
+ return stristr($str, $search);
+
+ if ($search == '')
+ return $str;
+
+ $str_lower = mb_strtolower($str);
+ $search_lower = mb_strtolower($search);
+
+ preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches);
+
+ if (isset($matches[1]))
+ return substr($str, strlen($matches[1]));
+
+ return FALSE;
+ }
+
+ /**
+ * Finds the length of the initial segment matching mask.
+ * @see http://php.net/strspn
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters in the mask
+ */
+ public static function strspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length));
+
+ if ($offset !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Finds the length of the initial segment not matching mask.
+ * @see http://php.net/strcspn
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param string mask for search
+ * @param integer start position of the string to examine
+ * @param integer length of the string to examine
+ * @return integer length of the initial segment that contains characters not in the mask
+ */
+ public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
+ {
+ if ($str == '' OR $mask == '')
+ return 0;
+
+ if (text::is_ascii($str) AND text::is_ascii($mask))
+ return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length));
+
+ if ($str !== NULL OR $length !== NULL)
+ {
+ $str = mb_substr($str, $offset, $length);
+ }
+
+ // Escape these characters: - [ ] . : \ ^ /
+ // The . and : are escaped to prevent possible warnings about POSIX regex elements
+ $mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+ preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+ return isset($matches[0]) ? mb_strlen($matches[0]) : 0;
+ }
+
+ /**
+ * Pads a UTF-8 string to a certain length with another string.
+ * @see http://php.net/str_pad
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param integer desired string length after padding
+ * @param string string to use as padding
+ * @param string padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
+ * @return string
+ */
+ public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+ {
+ if (text::is_ascii($str) AND text::is_ascii($pad_str))
+ {
+ return str_pad($str, $final_str_length, $pad_str, $pad_type);
+ }
+
+ $str_length = mb_strlen($str);
+
+ if ($final_str_length <= 0 OR $final_str_length <= $str_length)
+ {
+ return $str;
+ }
+
+ $pad_str_length = mb_strlen($pad_str);
+ $pad_length = $final_str_length - $str_length;
+
+ if ($pad_type == STR_PAD_RIGHT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length);
+ }
+
+ if ($pad_type == STR_PAD_LEFT)
+ {
+ $repeat = ceil($pad_length / $pad_str_length);
+ return mb_substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str;
+ }
+
+ if ($pad_type == STR_PAD_BOTH)
+ {
+ $pad_length /= 2;
+ $pad_length_left = floor($pad_length);
+ $pad_length_right = ceil($pad_length);
+ $repeat_left = ceil($pad_length_left / $pad_str_length);
+ $repeat_right = ceil($pad_length_right / $pad_str_length);
+
+ $pad_left = mb_substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left);
+ $pad_right = mb_substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left);
+ return $pad_left.$str.$pad_right;
+ }
+
+ trigger_error('utf8::str_pad: Unknown padding type (' . $pad_type . ')', E_USER_ERROR);
+ }
+
+ /**
+ * Converts a UTF-8 string to an array.
+ * @see http://php.net/str_split
+ *
+ * @author Harry Fuecks
+ *
+ * @param string input string
+ * @param integer maximum length of each chunk
+ * @return array
+ */
+ public static function str_split($str, $split_length = 1)
+ {
+ $split_length = (int) $split_length;
+
+ if (text::is_ascii($str))
+ {
+ return str_split($str, $split_length);
+ }
+
+ if ($split_length < 1)
+ {
+ return FALSE;
+ }
+
+ if (mb_strlen($str) <= $split_length)
+ {
+ return array($str);
+ }
+
+ preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches);
+
+ return $matches[0];
+ }
+
+ /**
+ * Reverses a UTF-8 string.
+ * @see http://php.net/strrev
+ *
+ * @author Harry Fuecks
+ *
+ * @param string string to be reversed
+ * @return string
+ */
+ public static function strrev($str)
+ {
+ if (text::is_ascii($str))
+ return strrev($str);
+
+ preg_match_all('/./us', $str, $matches);
+ return implode('', array_reverse($matches[0]));
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning and
+ * end of a string.
+ * @see http://php.net/trim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function trim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return trim($str);
+
+ return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the beginning of a string.
+ * @see http://php.net/ltrim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function ltrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return ltrim($str);
+
+ if (text::is_ascii($charlist))
+ return ltrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/^['.$charlist.']+/u', '', $str);
+ }
+
+ /**
+ * Strips whitespace (or other UTF-8 characters) from the end of a string.
+ * @see http://php.net/rtrim
+ *
+ * @author Andreas Gohr
+ *
+ * @param string input string
+ * @param string string of characters to remove
+ * @return string
+ */
+ public static function rtrim($str, $charlist = NULL)
+ {
+ if ($charlist === NULL)
+ return rtrim($str);
+
+ if (text::is_ascii($charlist))
+ return rtrim($str, $charlist);
+
+ $charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+ return preg_replace('/['.$charlist.']++$/uD', '', $str);
+ }
+
+ /**
+ * Returns the unicode ordinal for a character.
+ * @see http://php.net/ord
+ *
+ * @author Harry Fuecks
+ *
+ * @param string UTF-8 encoded character
+ * @return integer
+ */
+ public static function ord($chr)
+ {
+ $ord0 = ord($chr);
+
+ if ($ord0 >= 0 AND $ord0 <= 127)
+ {
+ return $ord0;
+ }
+
+ if ( ! isset($chr[1]))
+ {
+ trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord1 = ord($chr[1]);
+
+ if ($ord0 >= 192 AND $ord0 <= 223)
+ {
+ return ($ord0 - 192) * 64 + ($ord1 - 128);
+ }
+
+ if ( ! isset($chr[2]))
+ {
+ trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord2 = ord($chr[2]);
+
+ if ($ord0 >= 224 AND $ord0 <= 239)
+ {
+ return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+ }
+
+ if ( ! isset($chr[3]))
+ {
+ trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord3 = ord($chr[3]);
+
+ if ($ord0 >= 240 AND $ord0 <= 247)
+ {
+ return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+ }
+
+ if ( ! isset($chr[4]))
+ {
+ trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ $ord4 = ord($chr[4]);
+
+ if ($ord0 >= 248 AND $ord0 <= 251)
+ {
+ return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+ }
+
+ if ( ! isset($chr[5]))
+ {
+ trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+ return FALSE;
+ }
+
+ if ($ord0 >= 252 AND $ord0 <= 253)
+ {
+ return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($chr[5]) - 128);
+ }
+
+ if ($ord0 >= 254 AND $ord0 <= 255)
+ {
+ trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ /**
+ * Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
+ * Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/.
+ * Slight modifications to fit with phputf8 library by Harry Fuecks .
+ *
+ * @param string UTF-8 encoded string
+ * @return array unicode code points
+ * @return boolean FALSE if the string is invalid
+ */
+ public static function to_unicode($str)
+ {
+ $mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence
+ $mUcs4 = 0; // cached Unicode character
+ $mBytes = 1; // cached expected number of octets in the current sequence
+
+ $out = array();
+
+ $len = strlen($str);
+
+ for ($i = 0; $i < $len; $i++)
+ {
+ $in = ord($str[$i]);
+
+ if ($mState == 0)
+ {
+ // When mState is zero we expect either a US-ASCII character or a
+ // multi-octet sequence.
+ if (0 == (0x80 & $in))
+ {
+ // US-ASCII, pass straight through.
+ $out[] = $in;
+ $mBytes = 1;
+ }
+ elseif (0xC0 == (0xE0 & $in))
+ {
+ // First octet of 2 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x1F) << 6;
+ $mState = 1;
+ $mBytes = 2;
+ }
+ elseif (0xE0 == (0xF0 & $in))
+ {
+ // First octet of 3 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x0F) << 12;
+ $mState = 2;
+ $mBytes = 3;
+ }
+ elseif (0xF0 == (0xF8 & $in))
+ {
+ // First octet of 4 octet sequence
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x07) << 18;
+ $mState = 3;
+ $mBytes = 4;
+ }
+ elseif (0xF8 == (0xFC & $in))
+ {
+ // First octet of 5 octet sequence.
+ //
+ // This is illegal because the encoded codepoint must be either
+ // (a) not the shortest form or
+ // (b) outside the Unicode range of 0-0x10FFFF.
+ // Rather than trying to resynchronize, we will carry on until the end
+ // of the sequence and let the later error handling code catch it.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 0x03) << 24;
+ $mState = 4;
+ $mBytes = 5;
+ }
+ elseif (0xFC == (0xFE & $in))
+ {
+ // First octet of 6 octet sequence, see comments for 5 octet sequence.
+ $mUcs4 = $in;
+ $mUcs4 = ($mUcs4 & 1) << 30;
+ $mState = 5;
+ $mBytes = 6;
+ }
+ else
+ {
+ // Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+ trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ else
+ {
+ // When mState is non-zero, we expect a continuation of the multi-octet sequence
+ if (0x80 == (0xC0 & $in))
+ {
+ // Legal continuation
+ $shift = ($mState - 1) * 6;
+ $tmp = $in;
+ $tmp = ($tmp & 0x0000003F) << $shift;
+ $mUcs4 |= $tmp;
+
+ // End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output
+ if (0 == --$mState)
+ {
+ // Check for illegal sequences and codepoints
+
+ // From Unicode 3.1, non-shortest form is illegal
+ if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR
+ ((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR
+ ((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR
+ (4 < $mBytes) OR
+ // From Unicode 3.2, surrogate characters are illegal
+ (($mUcs4 & 0xFFFFF800) == 0xD800) OR
+ // Codepoints outside the Unicode range are illegal
+ ($mUcs4 > 0x10FFFF))
+ {
+ trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+
+ if (0xFEFF != $mUcs4)
+ {
+ // BOM is legal but we don't want to output it
+ $out[] = $mUcs4;
+ }
+
+ // Initialize UTF-8 cache
+ $mState = 0;
+ $mUcs4 = 0;
+ $mBytes = 1;
+ }
+ }
+ else
+ {
+ // ((0xC0 & (*in) != 0x80) AND (mState != 0))
+ // Incomplete multi-octet sequence
+ trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+ return FALSE;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
+ * Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
+ * Occurrances of the BOM are ignored. Surrogates are not allowed.
+ *
+ * The Original Code is Mozilla Communicator client code.
+ * The Initial Developer of the Original Code is Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+ * Ported to PHP by Henri Sivonen , see http://hsivonen.iki.fi/php-utf8/.
+ * Slight modifications to fit with phputf8 library by Harry Fuecks .
+ *
+ * @param array unicode code points representing a string
+ * @return string utf8 string of characters
+ * @return boolean FALSE if a code point cannot be found
+ */
+ public static function from_unicode($arr)
+ {
+ ob_start();
+
+ $keys = array_keys($arr);
+
+ foreach ($keys as $k)
+ {
+ // ASCII range (including control chars)
+ if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f))
+ {
+ echo chr($arr[$k]);
+ }
+ // 2 byte sequence
+ elseif ($arr[$k] <= 0x07ff)
+ {
+ echo chr(0xc0 | ($arr[$k] >> 6));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // Byte order mark (skip)
+ elseif ($arr[$k] == 0xFEFF)
+ {
+ // nop -- zap the BOM
+ }
+ // Test for illegal surrogates
+ elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+ {
+ // Found a surrogate
+ trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ // 3 byte sequence
+ elseif ($arr[$k] <= 0xffff)
+ {
+ echo chr(0xe0 | ($arr[$k] >> 12));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+ echo chr(0x80 | ($arr[$k] & 0x003f));
+ }
+ // 4 byte sequence
+ elseif ($arr[$k] <= 0x10ffff)
+ {
+ echo chr(0xf0 | ($arr[$k] >> 18));
+ echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+ echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+ echo chr(0x80 | ($arr[$k] & 0x3f));
+ }
+ // Out of range
+ else
+ {
+ trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+ return FALSE;
+ }
+ }
+
+ $result = ob_get_contents();
+ ob_end_clean();
+ return $result;
+ }
+
+} // End utf8
\ No newline at end of file
diff --git a/web_client/system/helpers/valid.php b/web_client/system/helpers/valid.php
new file mode 100644
index 00000000..cffcd7c0
--- /dev/null
+++ b/web_client/system/helpers/valid.php
@@ -0,0 +1,366 @@
+= 0; $i -= 2)
+ {
+ // Add up every 2nd digit, starting from the right
+ $checksum += substr($number, $i, 1);
+ }
+
+ for ($i = $length - 2; $i >= 0; $i -= 2)
+ {
+ // Add up every 2nd digit doubled, starting from the right
+ $double = substr($number, $i, 1) * 2;
+
+ // Subtract 9 from the double where value is greater than 10
+ $checksum += ($double >= 10) ? $double - 9 : $double;
+ }
+
+ // If the checksum is a multiple of 10, the number is valid
+ return ($checksum % 10 === 0);
+ }
+
+ /**
+ * Checks if a phone number is valid.
+ *
+ * @param string phone number to check
+ * @return boolean
+ */
+ public static function phone($number, $lengths = NULL)
+ {
+ if ( ! is_array($lengths))
+ {
+ $lengths = array(7,10,11);
+ }
+
+ // Remove all non-digit characters from the number
+ $number = preg_replace('/\D+/', '', $number);
+
+ // Check if the number is within range
+ return in_array(strlen($number), $lengths);
+ }
+
+ /**
+ * Tests if a string is a valid date string.
+ *
+ * @param string date to check
+ * @return boolean
+ */
+ public static function date($str)
+ {
+ return (strtotime($str) !== FALSE);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pL++$/uD', (string) $str)
+ : ctype_alpha((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters and numbers only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_numeric($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str)
+ : ctype_alnum((string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function alpha_dash($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str)
+ : (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str);
+ }
+
+ /**
+ * Checks whether a string consists of digits only (no dots or dashes).
+ *
+ * @param string input string
+ * @param boolean trigger UTF-8 compatibility
+ * @return boolean
+ */
+ public static function digit($str, $utf8 = FALSE)
+ {
+ return ($utf8 === TRUE)
+ ? (bool) preg_match('/^\pN++$/uD', (string) $str)
+ : ctype_digit((string) $str);
+ }
+
+ /**
+ * Checks whether a string is a valid number (negative and decimal numbers allowed).
+ *
+ * @see Uses locale conversion to allow decimal point to be locale specific.
+ * @see http://www.php.net/manual/en/function.localeconv.php
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function numeric($str)
+ {
+ // Use localeconv to set the decimal_point value: Usually a comma or period.
+ $locale = localeconv();
+ return (bool) preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str);
+ }
+
+ /**
+ * Tests if an integer is within a range.
+ *
+ * @param integer number to check
+ * @param array valid range of input
+ * @return boolean
+ */
+ public static function range($number, array $range)
+ {
+ // Invalid by default
+ $status = FALSE;
+
+ if (is_int($number) OR ctype_digit($number))
+ {
+ if (count($range) > 1)
+ {
+ if ($number >= $range[0] AND $number <= $range[1])
+ {
+ // Number is within the required range
+ $status = TRUE;
+ }
+ }
+ elseif ($number >= $range[0])
+ {
+ // Number is greater than the minimum
+ $status = TRUE;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks if a string is a proper decimal format. The format array can be
+ * used to specify a decimal length, or a number and decimal length, eg:
+ * array(2) would force the number to have 2 decimal places, array(4,2)
+ * would force the number to have 4 digits and 2 decimal places.
+ *
+ * @param string input string
+ * @param array decimal format: y or x,y
+ * @return boolean
+ */
+ public static function decimal($str, $format = NULL)
+ {
+ // Create the pattern
+ $pattern = '/^[0-9]%s\.[0-9]%s$/';
+
+ if ( ! empty($format))
+ {
+ if (count($format) > 1)
+ {
+ // Use the format for number and decimal length
+ $pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}');
+ }
+ elseif (count($format) > 0)
+ {
+ // Use the format as decimal length
+ $pattern = sprintf($pattern, '+', '{'.$format[0].'}');
+ }
+ }
+ else
+ {
+ // No format
+ $pattern = sprintf($pattern, '+', '+');
+ }
+
+ return (bool) preg_match($pattern, (string) $str);
+ }
+
+ /**
+ * Checks if a string is a proper hexadecimal HTML color value. The validation
+ * is quite flexible as it does not require an initial "#" and also allows for
+ * the short notation using only three instead of six hexadecimal characters.
+ * You may want to normalize these values with format::color().
+ *
+ * @param string input string
+ * @return boolean
+ */
+ public static function color($str)
+ {
+ return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
+ }
+
+} // End valid
\ No newline at end of file
diff --git a/web_client/system/libraries/Cache.php b/web_client/system/libraries/Cache.php
new file mode 100644
index 00000000..024c1888
--- /dev/null
+++ b/web_client/system/libraries/Cache.php
@@ -0,0 +1,250 @@
+ $name));
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('cache.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('cache.default');
+ }
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ // Set driver name
+ $driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Cache_Exception('The :driver: driver for the :class: library could not be found',
+ array(':driver:' => $this->config['driver'], ':class:' => get_class($this)));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Cache_Driver))
+ throw new Cache_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Cache_Driver'));
+
+ Kohana_Log::add('debug', 'Cache Library initialized');
+ }
+
+ /**
+ * Set cache items
+ */
+ public function set($key, $value = NULL, $tags = NULL, $lifetime = NULL)
+ {
+ if ($lifetime === NULL)
+ {
+ $lifetime = $this->config['lifetime'];
+ }
+
+ if ( ! is_array($key))
+ {
+ $key = array($key => $value);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $key = $this->add_prefix($key);
+
+ if ($tags !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+ }
+
+ return $this->driver->set($key, $tags, $lifetime);
+ }
+
+ /**
+ * Get a cache items by key
+ */
+ public function get($keys)
+ {
+ $single = FALSE;
+
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ $single = TRUE;
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+
+ if ( ! $single)
+ {
+ return $this->strip_prefix($this->driver->get($keys, $single));
+ }
+
+ }
+
+ return $this->driver->get($keys, $single);
+ }
+
+ /**
+ * Get cache items by tags
+ */
+ public function get_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ return $this->strip_prefix($this->driver->get_tag($tags));
+ }
+ else
+ {
+ return $this->driver->get_tag($tags);
+ }
+ }
+
+ /**
+ * Delete cache item by key
+ */
+ public function delete($keys)
+ {
+ if ( ! is_array($keys))
+ {
+ $keys = array($keys);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $keys = $this->add_prefix($keys, FALSE);
+ }
+
+ return $this->driver->delete($keys);
+ }
+
+ /**
+ * Delete cache items by tag
+ */
+ public function delete_tag($tags)
+ {
+ if ( ! is_array($tags))
+ {
+ $tags = array($tags);
+ }
+
+ if ($this->config['prefix'] !== NULL)
+ {
+ $tags = $this->add_prefix($tags, FALSE);
+ }
+
+ return $this->driver->delete_tag($tags);
+ }
+
+ /**
+ * Empty the cache
+ */
+ public function delete_all()
+ {
+ return $this->driver->delete_all();
+ }
+
+ /**
+ * Add a prefix to keys or tags
+ */
+ protected function add_prefix($array, $to_key = TRUE)
+ {
+ $out = array();
+
+ foreach($array as $key => $value)
+ {
+ if ($to_key)
+ {
+ $out[$this->config['prefix'].$key] = $value;
+ }
+ else
+ {
+ $out[$key] = $this->config['prefix'].$value;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Strip a prefix to keys or tags
+ */
+ protected function strip_prefix($array)
+ {
+ $out = array();
+
+ $start = strlen($this->config['prefix']);
+
+ foreach($array as $key => $value)
+ {
+ $out[substr($key, $start)] = $value;
+ }
+
+ return $out;
+ }
+
+} // End Cache Library
\ No newline at end of file
diff --git a/web_client/system/libraries/Cache_Exception.php b/web_client/system/libraries/Cache_Exception.php
new file mode 100644
index 00000000..040d086d
--- /dev/null
+++ b/web_client/system/libraries/Cache_Exception.php
@@ -0,0 +1,12 @@
+uri = URI::instance();
+
+ // Input should always be available
+ $this->input = Input::instance();
+ }
+
+ /**
+ * Handles methods that do not exist.
+ *
+ * @param string method name
+ * @param array arguments
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ // Default to showing a 404 page
+ Event::run('system.404');
+ }
+
+} // End Controller Class
\ No newline at end of file
diff --git a/web_client/system/libraries/Database.php b/web_client/system/libraries/Database.php
new file mode 100644
index 00000000..38a38fbf
--- /dev/null
+++ b/web_client/system/libraries/Database.php
@@ -0,0 +1,645 @@
+config = $config;
+
+ if ($this->config['cache'] !== FALSE)
+ {
+ if (is_string($this->config['cache']))
+ {
+ // Use Cache library
+ $this->cache = new Cache($this->config['cache']);
+ }
+ elseif ($this->config['cache'] === TRUE)
+ {
+ // Use array
+ $this->cache = array();
+ }
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Connects to the database
+ *
+ * @return void
+ */
+ abstract public function connect();
+
+ /**
+ * Disconnects from the database
+ *
+ * @return void
+ */
+ abstract public function disconnect();
+
+ /**
+ * Sets the character set
+ *
+ * @return void
+ */
+ abstract public function set_charset($charset);
+
+ /**
+ * Executes the query
+ *
+ * @param string SQL
+ * @return Database_Result
+ */
+ abstract public function query_execute($sql);
+
+ /**
+ * Escapes the given value
+ *
+ * @param mixed Value
+ * @return mixed Escaped value
+ */
+ abstract public function escape($value);
+
+ /**
+ * List constraints for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_constraints($table);
+
+ /**
+ * List fields for the given table
+ *
+ * @param string Table name
+ * @return array
+ */
+ abstract public function list_fields($table);
+
+ /**
+ * List tables for the given connection (checks for prefix)
+ *
+ * @return array
+ */
+ abstract public function list_tables();
+
+ /**
+ * Converts the given DSN string to an array of database connection components
+ *
+ * @param string DSN string
+ * @return array
+ */
+ public static function parse_dsn($dsn)
+ {
+ $db = array
+ (
+ 'type' => FALSE,
+ 'user' => FALSE,
+ 'pass' => FALSE,
+ 'host' => FALSE,
+ 'port' => FALSE,
+ 'socket' => FALSE,
+ 'database' => FALSE
+ );
+
+ // Get the protocol and arguments
+ list ($db['type'], $connection) = explode('://', $dsn, 2);
+
+ if ($connection[0] === '/')
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection, 1);
+ }
+ else
+ {
+ $connection = parse_url('http://'.$connection);
+
+ if (isset($connection['user']))
+ {
+ $db['user'] = $connection['user'];
+ }
+
+ if (isset($connection['pass']))
+ {
+ $db['pass'] = $connection['pass'];
+ }
+
+ if (isset($connection['port']))
+ {
+ $db['port'] = $connection['port'];
+ }
+
+ if (isset($connection['host']))
+ {
+ if ($connection['host'] === 'unix(')
+ {
+ list($db['socket'], $connection['path']) = explode(')', $connection['path'], 2);
+ }
+ else
+ {
+ $db['host'] = $connection['host'];
+ }
+ }
+
+ if (isset($connection['path']) AND $connection['path'])
+ {
+ // Strip leading slash
+ $db['database'] = substr($connection['path'], 1);
+ }
+ }
+
+ return $db;
+ }
+
+ /**
+ * Returns the last executed query for this database
+ *
+ * @return string
+ */
+ public function last_query()
+ {
+ return $this->last_query;
+ }
+
+ /**
+ * Executes the given query, returning the cached version if enabled
+ *
+ * @param string SQL query
+ * @return Database_Result
+ */
+ public function query($sql)
+ {
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ if (is_array($this->cache))
+ {
+ $hash = $this->query_hash($sql);
+
+ if (isset($this->cache[$hash]))
+ {
+ // Use cached result
+ $result = $this->cache[$hash];
+
+ // It's from cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // No cache, execute query and store in cache
+ $result = $this->cache[$hash] = $this->query_execute($sql);
+ }
+ }
+ else
+ {
+ // Execute the query, cache is off
+ $result = $this->query_execute($sql);
+ }
+
+ // Stop the benchmark
+ $stop = microtime(TRUE);
+
+ if ($this->config['benchmark'] === TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs the query on the cache (and caches it if it's not found)
+ *
+ * @param string query
+ * @param int time-to-live (NULL for Cache default)
+ * @return Database_Cache_Result
+ */
+ public function query_cache($sql, $ttl)
+ {
+ if ( ! $this->cache instanceof Cache)
+ {
+ throw new Database_Exception('Database :name has not been configured to use the Cache library.');
+ }
+
+ // Start the benchmark
+ $start = microtime(TRUE);
+
+ $hash = $this->query_hash($sql);
+
+ if (($data = $this->cache->get($hash)) !== NULL)
+ {
+ // Found in cache, create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+
+ // It's from the cache
+ $sql .= ' [CACHE]';
+ }
+ else
+ {
+ // Run the query and return the full array of rows
+ $data = $this->query_execute($sql)->as_array(TRUE);
+
+ // Set the Cache
+ $this->cache->set($hash, $data, NULL, $ttl);
+
+ // Create result
+ $result = new Database_Cache_Result($data, $sql, $this->config['object']);
+ }
+
+ // Stop the benchmark
+ $stop = microtime(TRUE);
+
+ if ($this->config['benchmark'] === TRUE)
+ {
+ // Benchmark the query
+ Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generates a hash for the given query
+ *
+ * @param string SQL query string
+ * @return string
+ */
+ protected function query_hash($sql)
+ {
+ return sha1(str_replace("\n", ' ', trim($sql)));
+ }
+
+ /**
+ * Clears the internal query cache.
+ *
+ * @param mixed clear cache by SQL statement, NULL for all, or TRUE for last query
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return Database
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ if ($this->cache instanceof Cache AND ($type == NULL OR $type == Database::CROSS_REQUEST))
+ {
+ // Using cross-request Cache library
+ if ($sql === TRUE)
+ {
+ $this->cache->delete($this->query_hash($this->last_query));
+ }
+ elseif (is_string($sql))
+ {
+ $this->cache->delete($this->query_hash($sql));
+ }
+ else
+ {
+ $this->cache->delete_all();
+ }
+ }
+ elseif (is_array($this->cache) AND ($type == NULL OR $type == Database::PER_REQUEST))
+ {
+ // Using per-request memory cache
+ if ($sql === TRUE)
+ {
+ unset($this->cache[$this->query_hash($this->last_query)]);
+ }
+ elseif (is_string($sql))
+ {
+ unset($this->cache[$this->query_hash($sql)]);
+ }
+ else
+ {
+ $this->cache = array();
+ }
+ }
+ }
+
+ /**
+ * Quotes the given value
+ *
+ * @param mixed value
+ * @return mixed
+ */
+ public function quote($value)
+ {
+ if ( ! $this->config['escape'])
+ return $value;
+
+ if ($value === NULL)
+ {
+ return 'NULL';
+ }
+ elseif ($value === TRUE)
+ {
+ return 'TRUE';
+ }
+ elseif ($value === FALSE)
+ {
+ return 'FALSE';
+ }
+ elseif (is_int($value))
+ {
+ return (int) $value;
+ }
+ elseif ($value instanceof Database_Expression)
+ {
+ return (string) $value;
+ }
+
+ return '\''.$this->escape($value).'\'';
+ }
+
+ /**
+ * Quotes a table, adding the table prefix
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ *
+ * @param string|array table name or array - 'users u' or array('u' => 'users') both valid
+ * @param string table alias
+ * @return string
+ */
+ public function quote_table($table, $alias = NULL)
+ {
+ if (is_array($table))
+ {
+ // Using array('u' => 'user')
+ list($alias, $table) = each($table);
+ }
+ elseif (strpos(' ', $table) !== FALSE)
+ {
+ // Using format 'user u'
+ list($table, $alias) = explode(' ', $table);
+ }
+
+ if ($table instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ return (string) $table;
+ }
+
+ if ($this->config['table_prefix'])
+ {
+ $table = $this->config['table_prefix'].$table;
+ }
+
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $table.' AS '.$alias;
+ }
+
+ if ($this->config['escape'])
+ {
+ $table = $this->quote.$table.$this->quote;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Quotes column or table.column, adding the table prefix if necessary
+ * Reserved characters not allowed in table names for the builder are [ .*] (space, dot, asterisk)
+ * Complex column names must have table/columns in double quotes, e.g. array('mycount' => 'COUNT("users.id")')
+ *
+ * @param string|array column name or array('u' => 'COUNT("*")')
+ * @param string column alias
+ * @return string
+ */
+ public function quote_column($column, $alias = NULL)
+ {
+ if ($column === '*')
+ return $column;
+
+ if (is_array($column))
+ {
+ list($alias, $column) = each($column);
+ }
+
+ if ($column instanceof Database_Expression)
+ {
+ if ($alias)
+ {
+ if ($this->config['escape'])
+ {
+ $alias = $this->quote.$alias.$this->quote;
+ }
+
+ return $column.' AS '.$alias;
+ }
+
+ return (string) $column;
+ }
+
+ if ($this->config['table_prefix'] AND strpos($column, '.') !== FALSE)
+ {
+ if (strpos($column, '"') !== FALSE)
+ {
+ // Find "table.column" and replace them with "[prefix]table.column"
+ $column = preg_replace('/"([^.]++)\.([^"]++)"/', '"'.$this->config['table_prefix'].'$1.$2"', $column);
+ }
+ else
+ {
+ // Attach table prefix if table.column format
+ $column = $this->config['table_prefix'].$column;
+ }
+ }
+
+ if ($this->config['escape'])
+ {
+ if (strpos($column, '"') === FALSE)
+ {
+ // Quote the column
+ $column = $this->quote.$column.$this->quote;
+ }
+ elseif ($this->quote !== '"')
+ {
+ // Replace double quotes
+ $column = str_replace('"', $this->quote, $column);
+ }
+
+ // Replace . with "."
+ $column = str_replace('.', $this->quote.'.'.$this->quote, $column);
+
+ // Unescape any asterisks
+ $column = str_replace($this->quote.'*'.$this->quote, '*', $column);
+
+ if ($alias)
+ {
+ // Quote the alias
+ return $column.' AS '.$this->quote.$alias.$this->quote;
+ }
+
+ return $column;
+ }
+
+ // Strip double quotes
+ $column = str_replace('"', '', $column);
+
+ if ($alias)
+ return $column.' AS '.$alias;
+
+ return $column;
+ }
+
+ /**
+ * Get the table prefix
+ *
+ * @param string Optional new table prefix to set
+ * @return string
+ */
+ public function table_prefix($new_prefix = NULL)
+ {
+ $prefix = $this->config['table_prefix'];
+
+ if ($new_prefix !== NULL)
+ {
+ // Set a new prefix
+ $this->config['table_prefix'] = $new_prefix;
+ }
+
+ return $prefix;
+ }
+
+ /**
+ * Fetches SQL type information about a field, in a generic format.
+ *
+ * @param string field datatype
+ * @return array
+ */
+ protected function sql_type($str)
+ {
+ static $sql_types;
+
+ if ($sql_types === NULL)
+ {
+ // Load SQL data types
+ $sql_types = Kohana::config('sql_types');
+ }
+
+ $str = trim($str);
+
+ if (($open = strpos($str, '(')) !== FALSE)
+ {
+ // Closing bracket
+ $close = strpos($str, ')', $open);
+
+ // Length without brackets
+ $length = substr($str, $open + 1, $close - 1 - $open);
+
+ // Type without the length
+ $type = substr($str, 0, $open).substr($str, $close + 1);
+ }
+ else
+ {
+ // No length
+ $type = $str;
+ }
+
+ if (empty($sql_types[$type]))
+ throw new Database_Exception('Undefined field type :type', array(':type' => $str));
+
+ // Fetch the field definition
+ $field = $sql_types[$type];
+
+ $field['sql_type'] = $type;
+
+ if (isset($length))
+ {
+ // Add the length to the field info
+ $field['length'] = $length;
+ }
+
+ return $field;
+ }
+
+} // End Database
diff --git a/web_client/system/libraries/Database_Builder.php b/web_client/system/libraries/Database_Builder.php
new file mode 100644
index 00000000..b5ec52b0
--- /dev/null
+++ b/web_client/system/libraries/Database_Builder.php
@@ -0,0 +1,1005 @@
+db = $db;
+ }
+
+ /**
+ * Resets all query components
+ */
+ public function reset()
+ {
+ $this->select = array();
+ $this->from = array();
+ $this->join = array();
+ $this->where = array();
+ $this->group_by = array();
+ $this->having = array();
+ $this->order_by = array();
+ $this->limit = NULL;
+ $this->offset = NULL;
+ $this->set = array();
+ $this->values = array();
+ }
+
+ public function __toString()
+ {
+ return $this->compile();
+ }
+
+ /**
+ * Compiles the builder object into a SQL query
+ *
+ * @return string Compiled query
+ */
+ protected function compile()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Use default database for compiling to string if none is given
+ $this->db = Database::instance($this->db);
+ }
+
+ if ($this->type === Database::SELECT)
+ {
+ // SELECT columns FROM table
+ $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+ $sql .= $this->compile_select();
+
+ if ( ! empty($this->from))
+ {
+ $sql .= "\nFROM ".$this->compile_from();
+ }
+ }
+ elseif ($this->type === Database::UPDATE)
+ {
+ $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
+ }
+ elseif ($this->type === Database::INSERT)
+ {
+ $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
+ }
+ elseif ($this->type === Database::DELETE)
+ {
+ $sql = 'DELETE FROM '.$this->compile_from();
+ }
+
+ if ( ! empty($this->join))
+ {
+ $sql .= $this->compile_join();
+ }
+
+ if ( ! empty($this->where))
+ {
+ $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
+ }
+
+ if ( ! empty($this->having))
+ {
+ $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
+ }
+
+ if ( ! empty($this->group_by))
+ {
+ $sql .= "\n".'GROUP BY '.$this->compile_group_by();
+ }
+
+ if ( ! empty($this->order_by))
+ {
+ $sql .= "\nORDER BY ".$this->compile_order_by();
+ }
+
+ if (is_int($this->limit))
+ {
+ $sql .= "\nLIMIT ".$this->limit;
+ }
+
+ if (is_int($this->offset))
+ {
+ $sql .= "\nOFFSET ".$this->offset;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the SELECT portion of the query
+ *
+ * @return string
+ */
+ protected function compile_select()
+ {
+ $vals = array();
+
+ foreach ($this->select as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ $vals[] = $this->db->quote_column($name, $alias);
+ }
+ else
+ {
+ $vals[] = $this->db->quote_column($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the FROM portion of the query
+ *
+ * @return string
+ */
+ protected function compile_from()
+ {
+ $vals = array();
+
+ foreach ($this->from as $alias => $name)
+ {
+ if (is_string($alias))
+ {
+ // Using AS format so escape both
+ $vals[] = $this->db->quote_table($name, $alias);
+ }
+ else
+ {
+ // Just using the table name itself
+ $vals[] = $this->db->quote_table($name);
+ }
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the JOIN portion of the query
+ *
+ * @return string
+ */
+ protected function compile_join()
+ {
+ $sql = '';
+ foreach ($this->join as $join)
+ {
+ list($table, $keys, $type) = $join;
+
+ if ($type !== NULL)
+ {
+ // Join type
+ $sql .= ' '.$type;
+ }
+
+ $sql .= ' JOIN '.$this->db->quote_table($table);
+
+ $condition = '';
+ if ($keys instanceof Database_Expression)
+ {
+ $condition = (string) $keys;
+ }
+ elseif (is_array($keys))
+ {
+ // ON condition is an array of matches
+ foreach ($keys as $key => $value)
+ {
+ if ( ! empty($condition))
+ {
+ $condition .= ' AND ';
+ }
+
+ $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
+ }
+ }
+
+ if ( ! empty($condition))
+ {
+ // Add ON condition
+ $sql .= ' ON ('.$condition.')';
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compiles the GROUP BY portion of the query
+ *
+ * @return string
+ */
+ protected function compile_group_by()
+ {
+ $vals = array();
+
+ foreach ($this->group_by as $column)
+ {
+ // Escape the column
+ $vals[] = $this->db->quote_column($column);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Compiles the ORDER BY portion of the query
+ *
+ * @return string
+ */
+ public function compile_order_by()
+ {
+ $ordering = array();
+
+ foreach ($this->order_by as $column => $order_by)
+ {
+ list($column, $direction) = each($order_by);
+
+ $column = $this->db->quote_column($column);
+
+ if ($direction !== NULL)
+ {
+ $direction = ' '.$direction;
+ }
+
+ $ordering[] = $column.$direction;
+ }
+
+ return implode(', ', $ordering);
+ }
+
+ /**
+ * Compiles the SET portion of the query for UPDATE
+ *
+ * @return string
+ */
+ public function compile_set()
+ {
+ $vals = array();
+
+ foreach ($this->set as $key => $value)
+ {
+ // Using an UPDATE so Key = Val
+ $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
+ }
+
+ return implode(', ', $vals);
+ }
+
+ /**
+ * Join tables to the builder
+ *
+ * @param mixed Table name
+ * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
+ * @param mixed Value if $keys is not an array or Database_Expression
+ * @param string Join type (LEFT, RIGHT, INNER, etc.)
+ * @return Database_Builder
+ */
+ public function join($table, $keys, $value = NULL, $type = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ if ($type !== NULL)
+ {
+ $type = strtoupper($type);
+ }
+
+ $this->join[] = array($table, $keys, $type);
+
+ return $this;
+ }
+
+ /**
+ * Add tables to the FROM portion of the builder
+ *
+ * @param string|array table name or array(alias => table)
+ * @return Database_Builder
+ */
+ public function from($tables)
+ {
+ if ( ! is_array($tables))
+ {
+ $tables = func_get_args();
+ }
+
+ $this->from = array_merge($this->from, $tables);
+
+ return $this;
+ }
+
+ /**
+ * Add fields to the GROUP BY portion
+ *
+ * @param mixed Field names or an array of fields
+ * @return Database_Builder
+ */
+ public function group_by($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->group_by = array_merge($this->group_by, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function having($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_having($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the HAVING clause (AND)
+ *
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the HAVING clause (OR)
+ *
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_having($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->having[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->having[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add fields to the ORDER BY portion
+ *
+ * @param mixed Field names or an array of fields (field => direction)
+ * @param string Direction or NULL for ascending
+ * @return Database_Builder
+ */
+ public function order_by($columns, $direction = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column => $direction)
+ {
+ if (is_string($column))
+ {
+ $this->order_by[] = array($column => $direction);
+ }
+ else
+ {
+ // $direction is the column name when the array key is numeric
+ $this->order_by[] = array($direction => NULL);
+ }
+ }
+ }
+ else
+ {
+ $this->order_by[] = array($columns => $direction);
+ }
+ return $this;
+ }
+
+ /**
+ * Limit rows returned
+ *
+ * @param int Number of rows
+ * @return Database_Builder
+ */
+ public function limit($number)
+ {
+ $this->limit = (int) $number;
+
+ return $this;
+ }
+
+ /**
+ * Offset into result set
+ *
+ * @param int Offset
+ * @return Database_Builder
+ */
+ public function offset($number)
+ {
+ $this->offset = (int) $number;
+
+ return $this;
+ }
+
+ public function left_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT');
+ }
+
+ public function right_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT');
+ }
+
+ public function inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'INNER');
+ }
+
+ public function outer_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'OUTER');
+ }
+
+ public function full_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'FULL');
+ }
+
+ public function left_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'LEFT INNER');
+ }
+
+ public function right_inner_join($table, $keys, $value = NULL)
+ {
+ return $this->join($table, $keys, $value, 'RIGHT INNER');
+ }
+
+ public function open($clause = 'WHERE')
+ {
+ return $this->and_open($clause);
+ }
+
+ public function and_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('AND' => '(');
+ }
+ else
+ {
+ $this->having[] = array('AND' => '(');
+ }
+
+ return $this;
+ }
+
+ public function or_open($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array('OR' => '(');
+ }
+ else
+ {
+ $this->having[] = array('OR' => '(');
+ }
+
+ return $this;
+ }
+
+ public function close($clause = 'WHERE')
+ {
+ if ($clause === 'WHERE')
+ {
+ $this->where[] = array(')');
+ }
+ else
+ {
+ $this->having[] = array(')');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add conditions to the WHERE clause (AND)
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function where($columns, $op = '=', $value = NULL)
+ {
+ return $this->and_where($columns, $op, $value);
+ }
+
+ /**
+ * Add conditions to the WHERE clause (AND)
+ *
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function and_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('AND' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('AND' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Add conditions to the WHERE clause (OR)
+ *
+ * @param mixed Column name or array of triplets
+ * @param string Operation to perform
+ * @param mixed Value
+ * @return Database_Builder
+ */
+ public function or_where($columns, $op = '=', $value = NULL)
+ {
+ if (is_array($columns))
+ {
+ foreach ($columns as $column)
+ {
+ $this->where[] = array('OR' => $column);
+ }
+ }
+ else
+ {
+ $this->where[] = array('OR' => array($columns, $op, $value));
+ }
+ return $this;
+ }
+
+ /**
+ * Compiles the given clause's conditions
+ *
+ * @param array Clause conditions
+ * @return string
+ */
+ protected function compile_conditions($groups)
+ {
+ $last_condition = NULL;
+
+ $sql = '';
+ foreach ($groups as $group)
+ {
+ // Process groups of conditions
+ foreach ($group as $logic => $condition)
+ {
+ if ($condition === '(')
+ {
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Include logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ $sql .= '(';
+ }
+ elseif ($condition === ')')
+ {
+ $sql .= ')';
+ }
+ else
+ {
+ list($columns, $op, $value) = $condition;
+
+ // Stores each individual condition
+ $vals = array();
+
+ if ($columns instanceof Database_Expression)
+ {
+ // Add directly to condition list
+ $vals[] = (string) $columns;
+ }
+ else
+ {
+ $op = strtoupper($op);
+
+ if ( ! is_array($columns))
+ {
+ $columns = array($columns => $value);
+ }
+
+ foreach ($columns as $column => $value)
+ {
+ if ($value instanceof Database_Builder)
+ {
+ // Using a subquery
+ $value->db = $this->db;
+ $value = '('.(string) $value.')';
+ }
+ elseif (is_array($value))
+ {
+ if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN')
+ {
+ // Falls between two values
+ $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]);
+ }
+ else
+ {
+ // Return as list
+ $value = array_map(array($this->db, 'quote'), $value);
+ $value = '('.implode(', ', $value).')';
+ }
+ }
+ else
+ {
+ $value = $this->db->quote($value);
+ }
+
+ if ( ! empty($column))
+ {
+ // Ignore blank columns
+ $column = $this->db->quote_column($column);
+ }
+
+ // Add to condition list
+ $vals[] = $column.' '.$op.' '.$value;
+ }
+ }
+
+ if ( ! empty($sql) AND $last_condition !== '(')
+ {
+ // Add the logic operator
+ $sql .= ' '.$logic.' ';
+ }
+
+ // Join the condition list items together by the given logic operator
+ $sql .= implode(' '.$logic.' ', $vals);
+ }
+
+ $last_condition = $condition;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Set values for UPDATE
+ *
+ * @param mixed Column name or array of columns => vals
+ * @param mixed Value (can be a Database_Expression)
+ * @return Database_Builder
+ */
+ public function set($keys, $value = NULL)
+ {
+ if (is_string($keys))
+ {
+ $keys = array($keys => $value);
+ }
+
+ $this->set = array_merge($keys, $this->set);
+
+ return $this;
+ }
+
+ /**
+ * Columns used for INSERT queries
+ *
+ * @param array Columns
+ * @return Database_Builder
+ */
+ public function columns($columns)
+ {
+ if ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->columns = $columns;
+
+ return $this;
+ }
+
+ /**
+ * Compiles the columns portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_columns()
+ {
+ return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')';
+ }
+
+ /**
+ * Values used for INSERT queries
+ *
+ * @param array Values
+ * @return Database_Builder
+ */
+ public function values($values)
+ {
+ if ( ! is_array($values))
+ {
+ $values = func_get_args();
+ }
+
+ $this->values[] = $values;
+
+ return $this;
+ }
+
+ /**
+ * Compiles the VALUES portion of the query for INSERT
+ *
+ * @return string
+ */
+ protected function compile_values()
+ {
+ $values = array();
+ foreach ($this->values as $group)
+ {
+ // Each set of values to be inserted
+ $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')';
+ }
+
+ return implode(', ', $values);
+ }
+
+ /**
+ * Create a SELECT query and specify selected columns
+ *
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select($columns = NULL)
+ {
+ $this->type = Database::SELECT;
+
+ if ($columns === NULL)
+ {
+ $columns = array('*');
+ }
+ elseif ( ! is_array($columns))
+ {
+ $columns = func_get_args();
+ }
+
+ $this->select = array_merge($this->select, $columns);
+
+ return $this;
+ }
+
+ /**
+ * Create a SELECT query and specify selected columns
+ *
+ * @param string|array column name or array(alias => column)
+ * @return Database_Builder
+ */
+ public function select_distinct($columns = NULL)
+ {
+ $this->select($columns);
+ $this->distinct = TRUE;
+ return $this;
+ }
+
+ /**
+ * Create an UPDATE query
+ *
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function update($table = NULL, $set = NULL, $where = NULL)
+ {
+ $this->type = Database::UPDATE;
+
+ if (is_array($set))
+ {
+ $this->set($set);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
+ *
+ * @param string Table name
+ * @param array Array of Keys => Values
+ * @return Database_Builder
+ */
+ public function insert($table = NULL, $set = NULL)
+ {
+ $this->type = Database::INSERT;
+
+ if (is_array($set))
+ {
+ $this->columns(array_keys($set));
+ $this->values(array_values($set));
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a DELETE query
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return Database_Builder
+ */
+ public function delete($table, $where = NULL)
+ {
+ $this->type = Database::DELETE;
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ if ($table !== NULL)
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Count records for a given table
+ *
+ * @param string Table name
+ * @param array WHERE conditions
+ * @return int
+ */
+ public function count_records($table = FALSE, $where = NULL)
+ {
+ if (count($this->from) < 1)
+ {
+ if ($table === FALSE)
+ throw new Database_Exception('Database count_records requires a table');
+
+ $this->from($table);
+ }
+
+ if ($where !== NULL)
+ {
+ $this->where($where);
+ }
+
+ // Grab the count AS records_found
+ $result = $this->select(array('records_found' => 'COUNT("*")'))->execute();
+
+ return $result->get('records_found');
+ }
+
+ /**
+ * Executes the built query
+ *
+ * @param mixed Database name or object
+ * @return Database_Result
+ */
+ public function execute($db = NULL)
+ {
+ if ($db !== NULL)
+ {
+ $this->db = $db;
+ }
+
+ if ( ! is_object($this->db))
+ {
+ // Get the database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ $query = $this->compile();
+
+ // Reset the query after executing
+ $this->reset();
+
+ if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
+ {
+ // Return result from cache (only allowed with SELECT)
+ return $this->db->query_cache($query, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $this->db->query($query);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Builder
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Builder
diff --git a/web_client/system/libraries/Database_Cache_Result.php b/web_client/system/libraries/Database_Cache_Result.php
new file mode 100644
index 00000000..12ad48ea
--- /dev/null
+++ b/web_client/system/libraries/Database_Cache_Result.php
@@ -0,0 +1,83 @@
+data = $data;
+ $this->sql = $sql;
+ $this->total_rows = count($data);
+ $this->return_objects = $return_objects;
+ }
+
+ public function __destruct()
+ {
+ // Not used
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return the entire array of rows
+ return $this->data;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ if ($class !== NULL)
+ throw new Database_Exception('Database cache results do not support object casting');
+
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = TRUE;
+
+ return $this;
+ }
+
+ public function seek($offset)
+ {
+ if ( ! $this->offsetExists($offset))
+ return FALSE;
+
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+
+ public function current()
+ {
+ if ($this->return_objects)
+ {
+ // Return a new object with the current row of data
+ return (object) $this->data[$this->current_row];
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->data[$this->current_row];
+ }
+ }
+
+} // End Database_Cache_Result
\ No newline at end of file
diff --git a/web_client/system/libraries/Database_Exception.php b/web_client/system/libraries/Database_Exception.php
new file mode 100644
index 00000000..4a9cd83e
--- /dev/null
+++ b/web_client/system/libraries/Database_Exception.php
@@ -0,0 +1,17 @@
+expression = $expression;
+ }
+
+ public function __toString()
+ {
+ return $this->expression;
+ }
+}
diff --git a/web_client/system/libraries/Database_Mysql.php b/web_client/system/libraries/Database_Mysql.php
new file mode 100644
index 00000000..a5775037
--- /dev/null
+++ b/web_client/system/libraries/Database_Mysql.php
@@ -0,0 +1,228 @@
+connection)
+ return;
+
+ if (Database_Mysql::$set_names === NULL)
+ {
+ // Determine if we can use mysql_set_charset(), which is only
+ // available on PHP 5.2.3+ when compiled against MySQL 5.0+
+ Database_Mysql::$set_names = ! function_exists('mysql_set_charset');
+ }
+
+ extract($this->config['connection']);
+
+ $host = isset($host) ? $host : $socket;
+ $port = isset($port) ? ':'.$port : '';
+
+ try
+ {
+ // Connect to the database
+ $this->connection = ($this->config['persistent'] === TRUE)
+ ? mysql_pconnect($host.$port, $user, $pass, $params)
+ : mysql_connect($host.$port, $user, $pass, TRUE, $params);
+ }
+ catch (Kohana_PHP_Exception $e)
+ {
+ // No connection exists
+ $this->connection = NULL;
+
+ // Unable to connect to the database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error(),
+ ':errno' => mysql_errno()));
+ }
+
+ if ( ! mysql_select_db($database, $this->connection))
+ {
+ // Unable to select database
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ try
+ {
+ // Database is assumed disconnected
+ $status = TRUE;
+
+ if (is_resource($this->connection))
+ {
+ $status = mysql_close($this->connection);
+ }
+ }
+ catch (Exception $e)
+ {
+ // Database is probably not disconnected
+ $status = is_resource($this->connection);
+ }
+
+ return $status;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (Database_Mysql::$set_names === TRUE)
+ {
+ // PHP is compiled against MySQL 4.x
+ $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->connection);
+ }
+ else
+ {
+ // PHP is compiled against MySQL 5.x
+ $status = mysql_set_charset($charset, $this->connection);
+ }
+
+ if ($status === FALSE)
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ $result = mysql_query($sql, $this->connection);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysql_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ $this->connection or $this->connect();
+
+ if (($value = mysql_real_escape_string($value, $this->connection)) === FALSE)
+ {
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => mysql_error($this->connection),
+ ':errno' => mysql_errno($this->connection)));
+ }
+
+ return $value;
+ }
+
+ public function list_constraints($table)
+ {
+ $prefix = strlen($this->table_prefix());
+ $result = array();
+
+ $constraints = $this->query('
+ SELECT c.constraint_name, c.constraint_type, k.column_name, k.referenced_table_name, k.referenced_column_name
+ FROM information_schema.table_constraints c
+ JOIN information_schema.key_column_usage k ON (k.table_schema = c.table_schema AND k.table_name = c.table_name AND k.constraint_name = c.constraint_name)
+ WHERE c.table_schema = '.$this->quote($this->config['connection']['database']).'
+ AND c.table_name = '.$this->quote($this->table_prefix().$table).'
+ AND (k.referenced_table_schema IS NULL OR k.referenced_table_schema ='.$this->quote($this->config['connection']['database']).')
+ ORDER BY k.ordinal_position
+ ');
+
+ foreach ($constraints->as_array() as $row)
+ {
+ switch ($row['constraint_type'])
+ {
+ case 'FOREIGN KEY':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ $result[$row['constraint_name']][3][] = $row['referenced_column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']), substr($row['referenced_table_name'], $prefix), array($row['referenced_column_name']));
+ }
+ break;
+ case 'PRIMARY KEY':
+ case 'UNIQUE':
+ if (isset($result[$row['constraint_name']]))
+ {
+ $result[$row['constraint_name']][1][] = $row['column_name'];
+ }
+ else
+ {
+ $result[$row['constraint_name']] = array($row['constraint_type'], array($row['column_name']));
+ }
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function list_fields($table)
+ {
+ $result = array();
+
+ foreach ($this->query('SHOW COLUMNS FROM '.$this->quote_table($table))->as_array() as $row)
+ {
+ $column = $this->sql_type($row['Type']);
+
+ $column['default'] = $row['Default'];
+ $column['nullable'] = $row['Null'] === 'YES';
+ $column['sequenced'] = $row['Extra'] === 'auto_increment';
+
+ if (isset($column['length']) AND $column['type'] === 'float')
+ {
+ list($column['precision'], $column['scale']) = explode(',', $column['length']);
+ }
+
+ $result[$row['Field']] = $column;
+ }
+
+ return $result;
+ }
+
+ public function list_tables()
+ {
+ $prefix = strlen($this->table_prefix());
+ $tables = array();
+
+ foreach ($this->query('SHOW TABLES FROM '.$this->escape($this->config['connection']['database']).' LIKE '.$this->quote($this->table_prefix().'%'))->as_array() as $row)
+ {
+ // The value is the table name
+ $tables[] = substr(current($row), $prefix);
+ }
+
+ return $tables;
+ }
+
+} // End Database_MySQL
diff --git a/web_client/system/libraries/Database_Mysql_Result.php b/web_client/system/libraries/Database_Mysql_Result.php
new file mode 100644
index 00000000..020360d1
--- /dev/null
+++ b/web_client/system/libraries/Database_Mysql_Result.php
@@ -0,0 +1,176 @@
+return_objects = $return_objects;
+
+ $this->total_rows = mysql_num_rows($result);
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => mysql_error($link),
+ ':query' => $sql,
+ ':errno' => mysql_errno($link)));
+
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = mysql_insert_id($link);
+ $this->total_rows = mysql_affected_rows($link);
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_resource($this->result))
+ {
+ mysql_free_result($this->result);
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ while ($row = mysql_fetch_assoc($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ mysql_data_seek($this->result, 0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = mysql_fetch_object($this->result, $this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = mysql_fetch_object($this->result))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $this->internal_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return mysql_fetch_object($this->result, $this->return_objects);
+ }
+ else
+ {
+ return mysql_fetch_object($this->result);
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return mysql_fetch_assoc($this->result);
+ }
+ }
+
+} // End Database_MySQL_Result
\ No newline at end of file
diff --git a/web_client/system/libraries/Database_Mysqli.php b/web_client/system/libraries/Database_Mysqli.php
new file mode 100644
index 00000000..08ed99df
--- /dev/null
+++ b/web_client/system/libraries/Database_Mysqli.php
@@ -0,0 +1,92 @@
+connection))
+ return;
+
+ extract($this->config['connection']);
+
+ // Persistent connections are supported as of PHP 5.3
+ if (RUNS_MYSQLND AND $this->config['persistent'] === TRUE)
+ {
+ $host = 'p:'.$host;
+ }
+
+ $host = isset($host) ? $host : $socket;
+
+ $mysqli = mysqli_init();
+
+ if ( ! $mysqli->real_connect($host, $user, $pass, $database, $port, $socket, $params))
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $mysqli->connect_error, ':errno' => $mysqli->connect_errno));
+
+ $this->connection = $mysqli;
+
+ if (isset($this->config['character_set']))
+ {
+ // Set the character set
+ $this->set_charset($this->config['character_set']);
+ }
+ }
+
+ public function disconnect()
+ {
+ if (is_object($this->connection))
+ {
+ $this->connection->close();
+ }
+
+ $this->connection = NULL;
+ }
+
+ public function set_charset($charset)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ if ( ! $this->connection->set_charset($charset))
+ {
+ // Unable to set charset
+ throw new Database_Exception('#:errno: :error',
+ array(':error' => $this->connection->connect_error,
+ ':errno' => $this->connection->connect_errno));
+ }
+ }
+
+ public function query_execute($sql)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ $result = $this->connection->query($sql);
+
+ // Set the last query
+ $this->last_query = $sql;
+
+ return new Database_Mysqli_Result($result, $sql, $this->connection, $this->config['object']);
+ }
+
+ public function escape($value)
+ {
+ // Make sure the database is connected
+ is_object($this->connection) or $this->connect();
+
+ return $this->connection->real_escape_string($value);
+ }
+
+} // End Database_MySQLi
diff --git a/web_client/system/libraries/Database_Mysqli_Result.php b/web_client/system/libraries/Database_Mysqli_Result.php
new file mode 100644
index 00000000..3601aeac
--- /dev/null
+++ b/web_client/system/libraries/Database_Mysqli_Result.php
@@ -0,0 +1,177 @@
+return_objects = $return_objects;
+
+ $this->total_rows = $result->num_rows;
+ }
+ elseif (is_bool($result))
+ {
+ if ($result == FALSE)
+ {
+ throw new Database_Exception('#:errno: :error [ :query ]',
+ array(':error' => $link->error,
+ ':query' => $sql,
+ ':errno' => $link->errno));
+ }
+ else
+ {
+ // It's a DELETE, INSERT, REPLACE, or UPDATE query
+ $this->insert_id = $link->insert_id;
+ $this->total_rows = $link->affected_rows;
+ }
+ }
+
+ // Store the result locally
+ $this->result = $result;
+
+ $this->sql = $sql;
+ }
+
+ public function __destruct()
+ {
+ if (is_object($this->result))
+ {
+ $this->result->free();
+ }
+ }
+
+ public function as_array($return = FALSE)
+ {
+ // Return arrays rather than objects
+ $this->return_objects = FALSE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ if (RUNS_MYSQLND)
+ return $this->result->fetch_all(MYSQLI_ASSOC);
+
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ while ($row = $this->result->fetch_assoc())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ public function as_object($class = NULL, $return = FALSE)
+ {
+ // Return objects of type $class (or stdClass if none given)
+ $this->return_objects = ($class !== NULL) ? $class : TRUE;
+
+ if ( ! $return )
+ {
+ // Return this result object
+ return $this;
+ }
+
+ // Return a nested array of all results
+ $array = array();
+
+ if ($this->total_rows > 0)
+ {
+ // Seek to the beginning of the result
+ $this->result->data_seek(0);
+
+ if (is_string($this->return_objects))
+ {
+ while ($row = $this->result->fetch_object($this->return_objects))
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+ else
+ {
+ while ($row = $this->result->fetch_object())
+ {
+ // Add each row to the array
+ $array[] = $row;
+ }
+ }
+
+ $this->internal_row = $this->total_rows;
+ }
+
+ return $array;
+ }
+
+ /**
+ * SeekableIterator: seek
+ */
+ public function seek($offset)
+ {
+ if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
+ {
+ // Set the current row to the offset
+ $this->current_row = $offset;
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($this->current_row !== $this->internal_row AND ! $this->seek($this->current_row))
+ return NULL;
+
+ ++$this->internal_row;
+
+ if ($this->return_objects)
+ {
+ if (is_string($this->return_objects))
+ {
+ return $this->result->fetch_object($this->return_objects);
+ }
+ else
+ {
+ return $this->result->fetch_object();
+ }
+ }
+ else
+ {
+ // Return an array of the row
+ return $this->result->fetch_assoc();
+ }
+ }
+
+} // End Database_MySQLi_Result
\ No newline at end of file
diff --git a/web_client/system/libraries/Database_Query.php b/web_client/system/libraries/Database_Query.php
new file mode 100644
index 00000000..d9399d66
--- /dev/null
+++ b/web_client/system/libraries/Database_Query.php
@@ -0,0 +1,95 @@
+sql = $sql;
+ }
+
+ public function __toString()
+ {
+ // Return the SQL of this query
+ return $this->sql;
+ }
+
+ public function sql($sql)
+ {
+ $this->sql = $sql;
+
+ return $this;
+ }
+
+ public function value($key, $value)
+ {
+ $this->params[$key] = $value;
+
+ return $this;
+ }
+
+ public function bind($key, & $value)
+ {
+ $this->params[$key] =& $value;
+
+ return $this;
+ }
+
+ public function execute($db = 'default')
+ {
+ if ( ! is_object($db))
+ {
+ // Get the database instance
+ $db = Database::instance($db);
+ }
+
+ // Import the SQL locally
+ $sql = $this->sql;
+
+ if ( ! empty($this->params))
+ {
+ // Quote all of the values
+ $params = array_map(array($db, 'quote'), $this->params);
+
+ // Replace the values in the SQL
+ $sql = strtr($sql, $params);
+ }
+
+ if ($this->ttl !== FALSE)
+ {
+ // Load the result from the cache
+ return $db->query_cache($sql, $this->ttl);
+ }
+ else
+ {
+ // Load the result (no caching)
+ return $db->query($sql);
+ }
+ }
+
+ /**
+ * Set caching for the query
+ *
+ * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
+ * @return Database_Query
+ */
+ public function cache($ttl = NULL)
+ {
+ $this->ttl = $ttl;
+
+ return $this;
+ }
+
+} // End Database_Query
\ No newline at end of file
diff --git a/web_client/system/libraries/Database_Result.php b/web_client/system/libraries/Database_Result.php
new file mode 100644
index 00000000..cf2056f3
--- /dev/null
+++ b/web_client/system/libraries/Database_Result.php
@@ -0,0 +1,170 @@
+insert_id;
+ }
+
+ /**
+ * Return the named column from the current row.
+ *
+ * @param string Column name
+ * @return mixed
+ */
+ public function get($name)
+ {
+ // Get the current row
+ $row = $this->current();
+
+ if ( ! $this->return_objects)
+ return $row[$name];
+
+ return $row->$name;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->total_rows;
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return ($offset >= 0 AND $offset < $this->total_rows);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ( ! $this->seek($offset))
+ return NULL;
+
+ return $this->current();
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ final public function offsetUnset($offset)
+ {
+ throw new Kohana_Exception('Database results are read-only');
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->current_row;
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ ++$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: prev
+ */
+ public function prev()
+ {
+ --$this->current_row;
+ return $this;
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->current_row = 0;
+ return $this;
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->offsetExists($this->current_row);
+ }
+
+} // End Database_Result
\ No newline at end of file
diff --git a/web_client/system/libraries/Encrypt.php b/web_client/system/libraries/Encrypt.php
new file mode 100644
index 00000000..0fbcfc1b
--- /dev/null
+++ b/web_client/system/libraries/Encrypt.php
@@ -0,0 +1,176 @@
+ $name));
+ }
+
+ if (is_array($config))
+ {
+ // Append the default configuration options
+ $config += Kohana::config('encryption.default');
+ }
+ else
+ {
+ // Load the default group
+ $config = Kohana::config('encryption.default');
+ }
+
+ if (empty($config['key']))
+ throw new Kohana_Exception('To use the Encrypt library, you must set an encryption key in your config file');
+
+ // Find the max length of the key, based on cipher and mode
+ $size = mcrypt_get_key_size($config['cipher'], $config['mode']);
+
+ if (strlen($config['key']) > $size)
+ {
+ // Shorten the key to the maximum size
+ $config['key'] = substr($config['key'], 0, $size);
+ }
+
+ // Find the initialization vector size
+ $config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']);
+
+ // Cache the config in the object
+ $this->config = $config;
+
+ Kohana_Log::add('debug', 'Encrypt Library initialized');
+ }
+
+ /**
+ * Encrypts a string and returns an encrypted string that can be decoded.
+ *
+ * @param string data to be encrypted
+ * @return string encrypted data
+ */
+ public function encode($data)
+ {
+ // Set the rand type if it has not already been set
+ if (Encrypt::$rand === NULL)
+ {
+ if (KOHANA_IS_WIN)
+ {
+ // Windows only supports the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ else
+ {
+ if (defined('MCRYPT_DEV_URANDOM'))
+ {
+ // Use /dev/urandom
+ Encrypt::$rand = MCRYPT_DEV_URANDOM;
+ }
+ elseif (defined('MCRYPT_DEV_RANDOM'))
+ {
+ // Use /dev/random
+ Encrypt::$rand = MCRYPT_DEV_RANDOM;
+ }
+ else
+ {
+ // Use the system random number generator
+ Encrypt::$rand = MCRYPT_RAND;
+ }
+ }
+ }
+
+ if (Encrypt::$rand === MCRYPT_RAND)
+ {
+ // The system random number generator must always be seeded each
+ // time it is used, or it will not produce true random results
+ mt_srand();
+ }
+
+ // Create a random initialization vector of the proper size for the current cipher
+ $iv = mcrypt_create_iv($this->config['iv_size'], Encrypt::$rand);
+
+ // Encrypt the data using the configured options and generated iv
+ $data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv);
+
+ // Use base64 encoding to convert to a string
+ return base64_encode($iv.$data);
+ }
+
+ /**
+ * Decrypts an encoded string back to its original value.
+ *
+ * @param string encoded string to be decrypted
+ * @return string decrypted data or FALSE if decryption fails
+ */
+ public function decode($data)
+ {
+ // Convert the data back to binary
+ $data = base64_decode($data, TRUE);
+
+ if ( ! $data)
+ {
+ // Invalid base64 data
+ return FALSE;
+ }
+
+ // Extract the initialization vector from the data
+ $iv = substr($data, 0, $this->config['iv_size']);
+
+ if ($this->config['iv_size'] !== strlen($iv))
+ {
+ // The iv is not the correct size
+ return FALSE;
+ }
+
+ // Remove the iv from the data
+ $data = substr($data, $this->config['iv_size']);
+
+ // Return the decrypted data, trimming the \0 padding bytes from the end of the data
+ return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0");
+ }
+
+} // End Encrypt
diff --git a/web_client/system/libraries/I18n.php b/web_client/system/libraries/I18n.php
new file mode 100644
index 00000000..96752e51
--- /dev/null
+++ b/web_client/system/libraries/I18n.php
@@ -0,0 +1,103 @@
+ 'gif',
+ IMAGETYPE_JPEG => 'jpg',
+ IMAGETYPE_PNG => 'png',
+ IMAGETYPE_TIFF_II => 'tiff',
+ IMAGETYPE_TIFF_MM => 'tiff',
+ );
+
+ // Driver instance
+ protected $driver;
+
+ // Driver actions
+ protected $actions = array();
+
+ // Reference to the current image filename
+ protected $image = '';
+
+ /**
+ * Creates a new Image instance and returns it.
+ *
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return object
+ */
+ public static function factory($image, $config = NULL)
+ {
+ return new Image($image, $config);
+ }
+
+ /**
+ * Creates a new image editor instance.
+ *
+ * @throws Kohana_Exception
+ * @param string filename of image
+ * @param array non-default configurations
+ * @return void
+ */
+ public function __construct($image, $config = NULL)
+ {
+ static $check;
+
+ // Make the check exactly once
+ ($check === NULL) and $check = function_exists('getimagesize');
+
+ if ($check === FALSE)
+ throw new Kohana_Exception('The Image library requires the getimagesize() PHP function, which is not available in your installation.');
+
+ // Check to make sure the image exists
+ if ( ! is_file($image))
+ throw new Kohana_Exception('The specified image, :image:, was not found. Please verify that images exist by using file_exists() before manipulating them.', array(':image:' => $image));
+
+ // Disable error reporting, to prevent PHP warnings
+ $ER = error_reporting(0);
+
+ // Fetch the image size and mime type
+ $image_info = getimagesize($image);
+
+ // Turn on error reporting again
+ error_reporting($ER);
+
+ // Make sure that the image is readable and valid
+ if ( ! is_array($image_info) OR count($image_info) < 3)
+ throw new Kohana_Exception('The file specified, :file:, is not readable or is not an image', array(':file:' => $image));
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $image));
+
+ // Image has been validated, load it
+ $this->image = array
+ (
+ 'file' => str_replace('\\', '/', realpath($image)),
+ 'width' => $image_info[0],
+ 'height' => $image_info[1],
+ 'type' => $image_info[2],
+ 'ext' => Image::$allowed_types[$image_info[2]],
+ 'mime' => $image_info['mime']
+ );
+
+ $this->determine_orientation();
+
+ // Load configuration
+ $this->config = (array) $config + Kohana::config('image');
+
+ // Set driver class name
+ $driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
+
+ // Load the driver
+ if ( ! Kohana::auto_load($driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library could not be found',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this)));
+
+ // Initialize the driver
+ $this->driver = new $driver($this->config['params']);
+
+ // Validate the driver
+ if ( ! ($this->driver instanceof Image_Driver))
+ throw new Kohana_Exception('The :driver: driver for the :library: library must implement the :interface: interface',
+ array(':driver:' => $this->config['driver'], ':library:' => get_class($this), ':interface:' => 'Image_Driver'));
+ }
+
+ /**
+ * Works out the correct orientation for the image
+ *
+ * @return void
+ */
+ protected function determine_orientation()
+ {
+ switch (TRUE)
+ {
+ case $this->image['height'] > $this->image['width']:
+ $orientation = Image::PORTRAIT;
+ break;
+
+ case $this->image['height'] < $this->image['width']:
+ $orientation = Image::LANDSCAPE;
+ break;
+
+ default:
+ $orientation = Image::SQUARE;
+ }
+
+ $this->image['orientation'] = $orientation;
+ }
+
+ /**
+ * Handles retrieval of pre-save image properties
+ *
+ * @param string property name
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ if (isset($this->image[$property]))
+ {
+ return $this->image[$property];
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class.',
+ array(':property:' => $property, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Resize an image to a specific width and height. By default, Kohana will
+ * maintain the aspect ratio using the width as the master dimension. If you
+ * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
+ * @return object
+ */
+ public function resize($width, $height, $master = NULL)
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ if ($master === NULL)
+ {
+ // Maintain the aspect ratio by default
+ $master = Image::AUTO;
+ }
+ elseif ( ! $this->valid_size('master', $master))
+ throw new Kohana_Exception('The master dimension specified is not valid.');
+
+ $this->actions['resize'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'master' => $master,
+ );
+
+ $this->determine_orientation();
+
+ return $this;
+ }
+
+ /**
+ * Crop an image to a specific width and height. You may also set the top
+ * and left offset.
+ * This method is chainable.
+ *
+ * @throws Kohana_Exception
+ * @param integer width
+ * @param integer height
+ * @param integer top offset, pixel value or one of: top, center, bottom
+ * @param integer left offset, pixel value or one of: left, center, right
+ * @return object
+ */
+ public function crop($width, $height, $top = 'center', $left = 'center')
+ {
+ if ( ! $this->valid_size('width', $width))
+ throw new Kohana_Exception('The width you specified, :width:, is not valid.', array(':width:' => $width));
+
+ if ( ! $this->valid_size('height', $height))
+ throw new Kohana_Exception('The height you specified, :height:, is not valid.', array(':height:' => $height));
+
+ if ( ! $this->valid_size('top', $top))
+ throw new Kohana_Exception('The top offset you specified, :top:, is not valid.', array(':top:' => $top));
+
+ if ( ! $this->valid_size('left', $left))
+ throw new Kohana_Exception('The left offset you specified, :left:, is not valid.', array(':left:' => $left));
+
+ if (empty($width) AND empty($height))
+ throw new Kohana_Exception('The dimensions specified for :function: are not valid.', array(':function:' => __FUNCTION__));
+
+ $this->actions['crop'] = array
+ (
+ 'width' => $width,
+ 'height' => $height,
+ 'top' => $top,
+ 'left' => $left,
+ );
+
+ $this->determine_orientation();
+
+ return $this;
+ }
+
+ /**
+ * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
+ *
+ * @param integer degrees
+ * @return object
+ */
+ public function rotate($degrees)
+ {
+ $degrees = (int) $degrees;
+
+ if ($degrees > 180)
+ {
+ do
+ {
+ // Keep subtracting full circles until the degrees have normalized
+ $degrees -= 360;
+ }
+ while($degrees > 180);
+ }
+
+ if ($degrees < -180)
+ {
+ do
+ {
+ // Keep adding full circles until the degrees have normalized
+ $degrees += 360;
+ }
+ while($degrees < -180);
+ }
+
+ $this->actions['rotate'] = $degrees;
+
+ return $this;
+ }
+
+ /**
+ * Overlay a second image on top of this one.
+ *
+ * @throws Kohana_Exception
+ * @param string $overlay_file path to an image file
+ * @param integer $x x offset for the overlay
+ * @param integer $y y offset for the overlay
+ * @param integer $transparency transparency percent
+ */
+ public function composite($overlay_file, $x, $y, $transparency)
+ {
+ $image_info = getimagesize($overlay_file);
+
+ // Check to make sure the image type is allowed
+ if ( ! isset(Image::$allowed_types[$image_info[2]]))
+ throw new Kohana_Exception('The specified image, :type:, is not an allowed image type.', array(':type:' => $overlay_file));
+
+ $this->actions['composite'] = array
+ (
+ 'overlay_file' => $overlay_file,
+ 'mime' => $image_info['mime'],
+ 'x' => $x,
+ 'y' => $y,
+ 'transparency' => $transparency
+ );
+
+ return $this;
+ }
+
+ /**
+ * Flip an image horizontally or vertically.
+ *
+ * @throws Kohana_Exception
+ * @param integer direction
+ * @return object
+ */
+ public function flip($direction)
+ {
+ if ($direction !== Image::HORIZONTAL AND $direction !== Image::VERTICAL)
+ throw new Kohana_Exception('The flip direction specified is not valid.');
+
+ $this->actions['flip'] = $direction;
+
+ return $this;
+ }
+
+ /**
+ * Change the quality of an image.
+ *
+ * @param integer quality as a percentage
+ * @return object
+ */
+ public function quality($amount)
+ {
+ $this->actions['quality'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Sharpen an image.
+ *
+ * @param integer amount to sharpen, usually ~20 is ideal
+ * @return object
+ */
+ public function sharpen($amount)
+ {
+ $this->actions['sharpen'] = max(1, min($amount, 100));
+
+ return $this;
+ }
+
+ /**
+ * Save the image to a new image or overwrite this image.
+ *
+ * @throws Kohana_Exception
+ * @param string new image filename
+ * @param integer permissions for new image
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE, $background = NULL)
+ {
+ // If no new image is defined, use the current image
+ empty($new_image) and $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ if ( ! is_writable($dir))
+ throw new Kohana_Exception('The specified directory, :dir:, is not writable.', array(':dir:' => $dir));
+
+ if ($status = $this->driver->process($this->image, $this->actions, $dir, $file, FALSE, $background))
+ {
+ if ($chmod !== FALSE)
+ {
+ // Set permissions
+ chmod($new_image, $chmod);
+ }
+ }
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $this->actions = array();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Output the image to the browser.
+ *
+ * @param boolean keep or discard image process actions
+ * @return object
+ */
+ public function render($keep_actions = FALSE, $background = NULL)
+ {
+ $new_image = $this->image['file'];
+
+ // Separate the directory and filename
+ $dir = pathinfo($new_image, PATHINFO_DIRNAME);
+ $file = pathinfo($new_image, PATHINFO_BASENAME);
+
+ // Normalize the path
+ $dir = str_replace('\\', '/', realpath($dir)).'/';
+
+ // Process the image with the driver
+ $status = $this->driver->process($this->image, $this->actions, $dir, $file, TRUE, $background);
+
+ if ($keep_actions !== TRUE)
+ {
+ // Reset actions. Subsequent save() or render() will not apply previous actions.
+ $this->actions = array();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Sanitize a given value type.
+ *
+ * @param string type of property
+ * @param mixed property value
+ * @return boolean
+ */
+ protected function valid_size($type, & $value)
+ {
+ if (is_null($value))
+ return TRUE;
+
+ if ( ! is_scalar($value))
+ return FALSE;
+
+ switch ($type)
+ {
+ case 'width':
+ case 'height':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ // Only numbers and percent signs
+ if ( ! preg_match('/^[0-9]++%$/D', $value))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'top':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('top', 'bottom', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'left':
+ if (is_string($value) AND ! ctype_digit($value))
+ {
+ if ( ! in_array($value, array('left', 'right', 'center')))
+ return FALSE;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'master':
+ if ($value !== Image::NONE AND
+ $value !== Image::AUTO AND
+ $value !== Image::WIDTH AND
+ $value !== Image::HEIGHT)
+ return FALSE;
+ break;
+ }
+
+ return TRUE;
+ }
+
+} // End Image
\ No newline at end of file
diff --git a/web_client/system/libraries/Input.php b/web_client/system/libraries/Input.php
new file mode 100644
index 00000000..f65815b3
--- /dev/null
+++ b/web_client/system/libraries/Input.php
@@ -0,0 +1,532 @@
+use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
+
+ if (Input::$instance === NULL)
+ {
+ // magic_quotes_runtime is enabled
+ if (get_magic_quotes_runtime())
+ {
+ @set_magic_quotes_runtime(0);
+ Kohana_Log::add('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ // magic_quotes_gpc is enabled
+ if (get_magic_quotes_gpc())
+ {
+ $this->magic_quotes_gpc = TRUE;
+ Kohana_Log::add('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+ }
+
+ // register_globals is enabled
+ if (ini_get('register_globals'))
+ {
+ if (isset($_REQUEST['GLOBALS']))
+ {
+ // Prevent GLOBALS override attacks
+ exit('Global variable overload attack.');
+ }
+
+ // Destroy the REQUEST global
+ $_REQUEST = array();
+
+ // These globals are standard and should not be removed
+ $preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+ // This loop has the same effect as disabling register_globals
+ foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+ {
+ global $$key;
+ $$key = NULL;
+
+ // Unset the global variable
+ unset($GLOBALS[$key], $$key);
+ }
+
+ // Warn the developer about register globals
+ Kohana_Log::add('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+ }
+
+ if (is_array($_GET))
+ {
+ foreach ($_GET as $key => $val)
+ {
+ // Sanitize $_GET
+ $_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_GET = array();
+ }
+
+ if (is_array($_POST))
+ {
+ foreach ($_POST as $key => $val)
+ {
+ // Sanitize $_POST
+ $_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_POST = array();
+ }
+
+ if (is_array($_COOKIE))
+ {
+ foreach ($_COOKIE as $key => $val)
+ {
+ // Ignore special attributes in RFC2109 compliant cookies
+ if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
+ continue;
+
+ // Sanitize $_COOKIE
+ $_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ }
+ else
+ {
+ $_COOKIE = array();
+ }
+
+ // Create a singleton
+ Input::$instance = $this;
+
+ Kohana_Log::add('debug', 'Global GET, POST and COOKIE data sanitized');
+ }
+ }
+
+ /**
+ * Fetch an item from the $_GET array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function get($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_GET, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_POST array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function post($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_POST, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the cookie::get() ($_COOKIE won't work with signed
+ * cookies.)
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array(cookie::get(), $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from the $_SERVER array.
+ *
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ public function server($key = array(), $default = NULL, $xss_clean = FALSE)
+ {
+ return $this->search_array($_SERVER, $key, $default, $xss_clean);
+ }
+
+ /**
+ * Fetch an item from a global array.
+ *
+ * @param array array to search
+ * @param string key to find
+ * @param mixed default value
+ * @param boolean XSS clean the value
+ * @return mixed
+ */
+ protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
+ {
+ if ($key === array())
+ return $array;
+
+ if ( ! isset($array[$key]))
+ return $default;
+
+ // Get the value
+ $value = $array[$key];
+
+ if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
+ {
+ // XSS clean the value
+ $value = $this->xss_clean($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Fetch the IP Address.
+ *
+ * @return string
+ */
+ public function ip_address()
+ {
+ if ($this->ip_address !== NULL)
+ return $this->ip_address;
+
+ // Server keys that could contain the client IP address
+ $keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
+
+ foreach ($keys as $key)
+ {
+ if ($ip = $this->server($key))
+ {
+ $this->ip_address = $ip;
+
+ // An IP address has been found
+ break;
+ }
+ }
+
+ if ($comma = strrpos($this->ip_address, ',') !== FALSE)
+ {
+ $this->ip_address = substr($this->ip_address, $comma + 1);
+ }
+
+ if ( ! valid::ip($this->ip_address))
+ {
+ // Use an empty IP
+ $this->ip_address = '0.0.0.0';
+ }
+
+ return $this->ip_address;
+ }
+
+ /**
+ * Clean cross site scripting exploits from string.
+ * HTMLPurifier may be used if installed, otherwise defaults to built in method.
+ * Note - This function should only be used to deal with data upon submission.
+ * It's not something that should be used for general runtime processing
+ * since it requires a fair amount of processing overhead.
+ *
+ * @param string data to clean
+ * @param string xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+ * @return string
+ */
+ public function xss_clean($data, $tool = NULL)
+ {
+ if ($tool === NULL)
+ {
+ // Use the default tool
+ $tool = Kohana::config('core.global_xss_filtering');
+ }
+
+ if (is_array($data))
+ {
+ foreach ($data as $key => $val)
+ {
+ $data[$key] = $this->xss_clean($val, $tool);
+ }
+
+ return $data;
+ }
+
+ // Do not clean empty strings
+ if (trim($data) === '')
+ return $data;
+
+ if ($tool === TRUE)
+ {
+ $tool = 'default';
+ }
+ elseif ( ! method_exists($this, 'xss_filter_'.$tool))
+ {
+ Kohana_Log::add('error', 'Unable to use Input::xss_filter_'.$tool.'(), no such method exists');
+ $tool = 'default';
+ }
+
+ $method = 'xss_filter_'.$tool;
+
+ return $this->$method($data);
+ }
+
+ /**
+ * Default built-in cross site scripting filter.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_default($data)
+ {
+ // http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+ // +----------------------------------------------------------------------+
+ // | Copyright (c) 2001-2006 Bitflux GmbH |
+ // +----------------------------------------------------------------------+
+ // | Licensed under the Apache License, Version 2.0 (the "License"); |
+ // | you may not use this file except in compliance with the License. |
+ // | You may obtain a copy of the License at |
+ // | http://www.apache.org/licenses/LICENSE-2.0 |
+ // | Unless required by applicable law or agreed to in writing, software |
+ // | distributed under the License is distributed on an "AS IS" BASIS, |
+ // | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+ // | implied. See the License for the specific language governing |
+ // | permissions and limitations under the License. |
+ // +----------------------------------------------------------------------+
+ // | Author: Christian Stocker |
+ // +----------------------------------------------------------------------+
+ //
+ // Kohana Modifications:
+ // * Changed double quotes to single quotes, changed indenting and spacing
+ // * Removed magic_quotes stuff
+ // * Increased regex readability:
+ // * Used delimeters that aren't found in the pattern
+ // * Removed all unneeded escapes
+ // * Deleted U modifiers and swapped greediness where needed
+ // * Increased regex speed:
+ // * Made capturing parentheses non-capturing where possible
+ // * Removed parentheses where possible
+ // * Split up alternation alternatives
+ // * Made some quantifiers possessive
+
+ // Fix &entity\n;
+ $data = str_replace(array('&','<','>'), array('&','<','>'), $data);
+ $data = preg_replace('/(*\w+)[\x00-\x20]+;/u', '$1;', $data);
+ $data = preg_replace('/(*[0-9A-F]+);*/iu', '$1;', $data);
+ $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+ // Remove any attribute starting with "on" or xmlns
+ $data = preg_replace('#(?:on[a-z]+|xmlns)\s*=\s*[\'"\x00-\x20]?[^\'>"]*[\'"\x00-\x20]?\s?#iu', '', $data);
+
+ // Remove javascript: and vbscript: protocols
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+ $data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+ // Only works in IE:
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+ $data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
+
+ // Remove namespaced elements (we do not need them)
+ $data = preg_replace('#*\w+:\w[^>]*+>#i', '', $data);
+
+ do
+ {
+ // Remove really unwanted tags
+ $old_data = $data;
+ $data = preg_replace('#*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+ }
+ while ($old_data !== $data);
+
+ return $data;
+ }
+
+ /**
+ * HTMLPurifier cross site scripting filter. This version assumes the
+ * existence of the "Standalone Distribution" htmlpurifier library, and is set to not tidy
+ * input.
+ *
+ * @param string data to clean
+ * @return string
+ */
+ protected function xss_filter_htmlpurifier($data)
+ {
+ /**
+ * @todo License should go here, http://htmlpurifier.org/
+ */
+ if ( ! class_exists('HTMLPurifier_Config', FALSE))
+ {
+ // Load HTMLPurifier
+ require Kohana::find_file('vendor', 'htmlpurifier/HTMLPurifier.standalone', TRUE);
+ }
+
+ // Set configuration
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
+
+ $cache = Kohana::config('html_purifier.cache');
+
+ if ($cache AND is_string($cache))
+ {
+ $config->set('Cache.SerializerPath', $cache);
+ }
+
+ // Run HTMLPurifier
+ $data = HTMLPurifier::instance($config)->purify($data);
+
+ return $data;
+ }
+
+ /**
+ * This is a helper method. It enforces W3C specifications for allowed
+ * key name strings, to prevent malicious exploitation.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public function clean_input_keys($str)
+ {
+ if ( ! preg_match('#^[\pL0-9:_.-]++$#uD', $str))
+ {
+ exit('Disallowed key characters in global data.');
+ }
+
+ return $str;
+ }
+
+ /**
+ * This is a helper method. It escapes data and forces all newline
+ * characters to "\n".
+ *
+ * @param unknown_type string to clean
+ * @return string
+ */
+ public function clean_input_data($str)
+ {
+ if (is_array($str))
+ {
+ $new_array = array();
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+ }
+ return $new_array;
+ }
+
+ if ($this->magic_quotes_gpc === TRUE)
+ {
+ // Remove annoying magic quotes
+ $str = stripslashes($str);
+ }
+
+ if ($this->use_xss_clean === TRUE)
+ {
+ $str = $this->xss_clean($str);
+ }
+
+ if (strpos($str, "\r") !== FALSE)
+ {
+ // Standardize newlines
+ $str = str_replace(array("\r\n", "\r"), "\n", $str);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Recursively cleans arrays, objects, and strings. Removes ASCII control
+ * codes and converts to UTF-8 while silently discarding incompatible
+ * UTF-8 characters.
+ *
+ * @param string string to clean
+ * @return string
+ */
+ public static function clean($str)
+ {
+ if (is_array($str) OR is_object($str))
+ {
+ foreach ($str as $key => $val)
+ {
+ // Recursion!
+ $str[Input::clean($key)] = Input::clean($val);
+ }
+ }
+ elseif (is_string($str) AND $str !== '')
+ {
+ // Remove control characters
+ $str = text::strip_ascii_ctrl($str);
+
+ if ( ! text::is_ascii($str))
+ {
+ // Disable notices
+ $ER = error_reporting(~E_NOTICE);
+
+ // iconv is expensive, so it is only used when needed
+ $str = iconv(Kohana::CHARSET, Kohana::CHARSET.'//IGNORE', $str);
+
+ // Turn notices back on
+ error_reporting($ER);
+ }
+ }
+
+ return $str;
+ }
+
+} // End Input Class
diff --git a/web_client/system/libraries/Kohana_404_Exception.php b/web_client/system/libraries/Kohana_404_Exception.php
new file mode 100644
index 00000000..8c7cc787
--- /dev/null
+++ b/web_client/system/libraries/Kohana_404_Exception.php
@@ -0,0 +1,56 @@
+ $page));
+ }
+
+ /**
+ * Throws a new 404 exception.
+ *
+ * @throws Kohana_404_Exception
+ * @return void
+ */
+ public static function trigger($page = NULL)
+ {
+ throw new Kohana_404_Exception($page);
+ }
+
+ /**
+ * Sends 404 headers, to emulate server behavior.
+ *
+ * @return void
+ */
+ public function sendHeaders()
+ {
+ // Send the 404 header
+ header('HTTP/1.1 404 File Not Found');
+ }
+
+} // End Kohana 404 Exception
\ No newline at end of file
diff --git a/web_client/system/libraries/Kohana_Log.php b/web_client/system/libraries/Kohana_Log.php
new file mode 100644
index 00000000..44ef8af8
--- /dev/null
+++ b/web_client/system/libraries/Kohana_Log.php
@@ -0,0 +1,90 @@
+ $driver));
+
+ // Initialize the driver
+ $driver = new $driver(array_merge(Kohana::config('log'), Kohana::config('log_'.$driver_name)));
+
+ // Validate the driver
+ if ( ! ($driver instanceof Log_Driver))
+ throw new Kohana_Exception('%driver% does not implement the Log_Driver interface', array('%driver%' => $driver));
+
+ Kohana_Log::$drivers[] = $driver;
+ }
+
+ // Always save logs on shutdown
+ Event::add('system.shutdown', array('Kohana_Log', 'save'));
+ }
+
+ Kohana_Log::$messages[] = array('date' => time(), 'type' => $type, 'message' => $message);
+ }
+
+ /**
+ * Save all currently logged messages.
+ *
+ * @return void
+ */
+ public static function save()
+ {
+ if (empty(Kohana_Log::$messages))
+ return;
+
+ foreach (Kohana_Log::$drivers as $driver)
+ {
+ // We can't throw exceptions here or else we will get a
+ // Exception thrown without a stack frame error
+ try
+ {
+ $driver->save(Kohana_Log::$messages);
+ }
+ catch(Exception $e){}
+ }
+
+ // Reset the messages
+ Kohana_Log::$messages = array();
+ }
+}
\ No newline at end of file
diff --git a/web_client/system/libraries/Kohana_PHP_Exception.php b/web_client/system/libraries/Kohana_PHP_Exception.php
new file mode 100644
index 00000000..019c686b
--- /dev/null
+++ b/web_client/system/libraries/Kohana_PHP_Exception.php
@@ -0,0 +1,99 @@
+code = $code;
+ $this->file = $file;
+ $this->line = $line;
+ }
+
+ /**
+ * PHP error handler.
+ *
+ * @throws Kohana_PHP_Exception
+ * @return void
+ */
+ public static function error_handler($code, $error, $file, $line, $context = NULL)
+ {
+ // Respect error_reporting settings
+ if (error_reporting() & $code)
+ {
+ // Throw an exception
+ throw new Kohana_PHP_Exception($code, $error, $file, $line, $context);
+ }
+ }
+
+ /**
+ * Catches errors that are not caught by the error handler, such as E_PARSE.
+ *
+ * @uses Kohana_Exception::handle()
+ * @return void
+ */
+ public static function shutdown_handler()
+ {
+ if (Kohana_PHP_Exception::$enabled AND $error = error_get_last() AND (error_reporting() & $error['type']))
+ {
+ // Fake an exception for nice debugging
+ Kohana_Exception::handle(new Kohana_PHP_Exception($error['type'], $error['message'], $error['file'], $error['line']));
+ }
+ }
+
+} // End Kohana PHP Exception
diff --git a/web_client/system/libraries/Kohana_User_Exception.php b/web_client/system/libraries/Kohana_User_Exception.php
new file mode 100644
index 00000000..95b7bc68
--- /dev/null
+++ b/web_client/system/libraries/Kohana_User_Exception.php
@@ -0,0 +1,30 @@
+code = $title;
+ }
+
+} // End Kohana User Exception
diff --git a/web_client/system/libraries/Model.php b/web_client/system/libraries/Model.php
new file mode 100644
index 00000000..01d16fdd
--- /dev/null
+++ b/web_client/system/libraries/Model.php
@@ -0,0 +1,62 @@
+newInstanceArgs($args);
+ }
+
+ // Database object
+ protected $db = 'default';
+
+ /**
+ * Loads the database instance, if the database is not already loaded.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Load the default database
+ $this->db = Database::instance($this->db);
+ }
+ }
+
+} // End Model
\ No newline at end of file
diff --git a/web_client/system/libraries/ORM.php b/web_client/system/libraries/ORM.php
new file mode 100644
index 00000000..e2f27bac
--- /dev/null
+++ b/web_client/system/libraries/ORM.php
@@ -0,0 +1,1586 @@
+object_name = strtolower(substr(get_class($this), 0, -6));
+ $this->object_plural = inflector::plural($this->object_name);
+
+ if ( ! isset($this->sorting))
+ {
+ // Default sorting
+ $this->sorting = array($this->primary_key => 'asc');
+ }
+
+ // Initialize database
+ $this->__initialize();
+
+ // Clear the object
+ $this->clear();
+
+ if (is_object($id))
+ {
+ // Load an object
+ $this->_load_values((array) $id);
+ }
+ elseif ( ! empty($id))
+ {
+ // Set the object's primary key, but don't load it until needed
+ $this->object[$this->primary_key] = $id;
+
+ // Object is considered saved until something is set
+ $this->_saved = TRUE;
+ }
+ }
+
+ /**
+ * Prepares the model database connection, determines the table name,
+ * and loads column information.
+ *
+ * @return void
+ */
+ public function __initialize()
+ {
+ if ( ! is_object($this->db))
+ {
+ // Get database instance
+ $this->db = Database::instance($this->db);
+ }
+
+ if (empty($this->table_name))
+ {
+ // Table name is the same as the object name
+ $this->table_name = $this->object_name;
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the table name plural
+ $this->table_name = inflector::plural($this->table_name);
+ }
+ }
+
+ if (is_array($this->ignored_columns))
+ {
+ // Make the ignored columns mirrored = mirrored
+ $this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
+ }
+
+ // Load column information
+ $this->reload_columns();
+
+ // Initialize the builder
+ $this->db_builder = db::build();
+ }
+
+ /**
+ * Allows serialization of only the object data and state, to prevent
+ * "stale" objects being unserialized, which also requires less memory.
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ // Store only information about the object
+ return array('object_name', 'object', 'changed', '_loaded', '_saved', 'sorting');
+ }
+
+ /**
+ * Prepares the database connection and reloads the object.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ // Initialize database
+ $this->__initialize();
+
+ if ($this->reload_on_wakeup === TRUE)
+ {
+ // Reload the object
+ $this->reload();
+ }
+ }
+
+ /**
+ * Handles pass-through to database methods. Calls to query methods
+ * (query, get, insert, update) are not allowed. Query builder methods
+ * are chainable.
+ *
+ * @param string method name
+ * @param array method arguments
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ if (method_exists($this->db_builder, $method))
+ {
+ if (in_array($method, array('execute', 'insert', 'update', 'delete')))
+ throw new Kohana_Exception('Query methods cannot be used through ORM');
+
+ // Method has been applied to the database
+ $this->db_applied[$method] = $method;
+
+ // Number of arguments passed
+ $num_args = count($args);
+
+ if ($method === 'select' AND $num_args > 3)
+ {
+ // Call select() manually to avoid call_user_func_array
+ $this->db_builder->select($args);
+ }
+ else
+ {
+ // We use switch here to manually call the database methods. This is
+ // done for speed: call_user_func_array can take over 300% longer to
+ // make calls. Most database methods are 4 arguments or less, so this
+ // avoids almost any calls to call_user_func_array.
+
+ switch ($num_args)
+ {
+ case 0:
+ if (in_array($method, array('open', 'and_open', 'or_open', 'close', 'cache')))
+ {
+ // Should return ORM, not Database
+ $this->db_builder->$method();
+ }
+ else
+ {
+ // Support for things like reset_select, reset_write, list_tables
+ return $this->db_builder->$method();
+ }
+ break;
+ case 1:
+ $this->db_builder->$method($args[0]);
+ break;
+ case 2:
+ $this->db_builder->$method($args[0], $args[1]);
+ break;
+ case 3:
+ $this->db_builder->$method($args[0], $args[1], $args[2]);
+ break;
+ case 4:
+ $this->db_builder->$method($args[0], $args[1], $args[2], $args[3]);
+ break;
+ default:
+ // Here comes the snail...
+ call_user_func_array(array($this->db, $method), $args);
+ break;
+ }
+ }
+
+ return $this;
+ }
+ else
+ {
+ throw new Kohana_Exception('Invalid method :method called in :class',
+ array(':method' => $method, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Handles retrieval of all model values, relationships, and metadata.
+ *
+ * @param string column name
+ * @return mixed
+ */
+ public function __get($column)
+ {
+ if (array_key_exists($column, $this->object))
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Column asked for but the object hasn't been loaded yet, so do it now
+ // Ignore loading of any columns that have been changed
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ return $this->object[$column];
+ }
+ elseif (isset($this->related[$column]))
+ {
+ return $this->related[$column];
+ }
+ elseif ($column === 'primary_key_value')
+ {
+ if( ! $this->loaded() AND ! $this->empty_primary_key() AND $this->unique_key($this->object[$this->primary_key]) !== $this->primary_key)
+ {
+ // Load if object hasn't been loaded and the key given isn't the primary_key
+ // that we need (i.e. passing an email address to ORM::factory rather than the id)
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ return $this->object[$this->primary_key];
+ }
+ elseif ($model = $this->related_object($column))
+ {
+ // This handles the has_one and belongs_to relationships
+
+ if (in_array($model->object_name, $this->belongs_to))
+ {
+ if ( ! $this->loaded() AND ! $this->empty_primary_key())
+ {
+ // Load this object first so we know what id to look for in the foreign table
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+
+ // Foreign key lies in this table (this model belongs_to target model)
+ $where = array($model->foreign_key(TRUE), '=', $this->object[$this->foreign_key($column)]);
+ }
+ else
+ {
+ // Foreign key lies in the target table (this model has_one target model)
+ $where = array($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+
+ // one<>alias:one relationship
+ return $this->related[$column] = $model->find($where);
+ }
+ elseif (isset($this->has_many_through[$column]))
+ {
+ // Load the "middle" model
+ $through = ORM::factory(inflector::singular($this->has_many_through[$column]));
+
+ // Load the "end" model
+ $model = ORM::factory(inflector::singular($column));
+
+ // Join ON target model's primary key set to 'through' model's foreign key
+ // User-defined foreign keys must be defined in the 'through' model
+ $join_table = $through->table_name;
+ $join_col1 = $through->foreign_key($model->object_name, $join_table);
+ $join_col2 = $model->foreign_key(TRUE);
+
+ // one<>alias:many relationship
+ return $model
+ ->join($join_table, $join_col1, $join_col2)
+ ->where($through->foreign_key($this->object_name, $join_table), '=', $this->primary_key_value);
+ }
+ elseif (isset($this->has_many[$column]))
+ {
+ // one<>many aliased relationship
+ $model_name = $this->has_many[$column];
+
+ $model = ORM::factory(inflector::singular($model_name));
+
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ elseif (in_array($column, $this->has_many))
+ {
+ // one<>many relationship
+ $model = ORM::factory(inflector::singular($column));
+ return $model->where($this->foreign_key($column, $model->table_name), '=', $this->primary_key_value);
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many))
+ {
+ // Load the remote model, always singular
+ $model = ORM::factory(inflector::singular($column));
+
+ if ($this->has($model, TRUE))
+ {
+ // many<>many relationship
+ return $model->where($model->foreign_key(TRUE), 'IN', $this->changed_relations[$column]);
+ }
+ else
+ {
+ // empty many<>many relationship
+ return $model->where($model->foreign_key(TRUE), 'IS', NULL);
+ }
+ }
+ elseif (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (in_array($column, array
+ (
+ 'object_name', 'object_plural','_valid', // Object
+ 'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+ 'has_one', 'belongs_to', 'has_many', 'has_many_through', 'has_and_belongs_to_many', 'load_with' // Relationships
+ )))
+ {
+ // Model meta information
+ return $this->$column;
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property property does not exist in the :class class',
+ array(':property' => $column, ':class' => get_class($this)));
+ }
+ }
+
+ /**
+ * Tells you if the Model has been loaded or not
+ *
+ * @return bool
+ */
+ public function loaded() {
+ if ( ! $this->_loaded AND ! $this->empty_primary_key())
+ {
+ // If returning the loaded member and no load has been attempted, do it now
+ $this->find($this->object[$this->primary_key], TRUE);
+ }
+ return $this->_loaded;
+ }
+
+ /**
+ * Tells you if the model was saved successfully or not
+ *
+ * @return bool
+ */
+ public function saved() {
+ return $this->_saved;
+ }
+
+ /**
+ * Handles setting of all model values, and tracks changes between values.
+ *
+ * @param string column name
+ * @param mixed column value
+ * @return void
+ */
+ public function __set($column, $value)
+ {
+ if (isset($this->ignored_columns[$column]))
+ {
+ return NULL;
+ }
+ elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ // Data has changed
+ $this->changed[$column] = $column;
+
+ // Object is no longer saved
+ $this->_saved = FALSE;
+ }
+
+ $this->object[$column] = $this->load_type($column, $value);
+ }
+ elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
+ {
+ // Load relations
+ $model = ORM::factory(inflector::singular($column));
+
+ if ( ! isset($this->object_relations[$column]))
+ {
+ // Load relations
+ $this->has($model);
+ }
+
+ // Change the relationships
+ $this->changed_relations[$column] = $value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+ }
+ else
+ {
+ throw new Kohana_Exception('The :property: property does not exist in the :class: class',
+ array(':property:' => $column, ':class:' => get_class($this)));
+ }
+ }
+
+ /**
+ * Chainable set method
+ *
+ * @param string name of field or array of key => val
+ * @param mixed value
+ * @return ORM
+ */
+ public function set($name, $value = NULL)
+ {
+ if (is_array($name))
+ {
+ foreach ($name as $key => $value)
+ {
+ $this->__set($key, $value);
+ }
+ }
+ else
+ {
+ $this->__set($name, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Checks if object data is set.
+ *
+ * @param string column name
+ * @return boolean
+ */
+ public function __isset($column)
+ {
+ return (isset($this->object[$column]) OR isset($this->related[$column]));
+ }
+
+ /**
+ * Unsets object data.
+ *
+ * @param string column name
+ * @return void
+ */
+ public function __unset($column)
+ {
+ unset($this->object[$column], $this->changed[$column], $this->related[$column]);
+ }
+
+ /**
+ * Returns the values of this object as an array.
+ *
+ * @return array
+ */
+ public function as_array()
+ {
+ $object = array();
+
+ foreach ($this->object as $key => $val)
+ {
+ // Reconstruct the array (calls __get)
+ $object[$key] = $this->$key;
+ }
+
+ foreach ($this->with_applied as $model => $enabled)
+ {
+ // Generate arrays for relationships
+ if ($enabled)
+ {
+ $object[$model] = $this->$model->as_array();
+ }
+ }
+
+ return $object;
+ }
+
+ /**
+ * Binds another one-to-one object to this model. One-to-one objects
+ * can be nested using 'object1:object2' syntax
+ *
+ * @param string target model to bind to
+ * @return void
+ */
+ public function with($target_path)
+ {
+ if (isset($this->with_applied[$target_path]))
+ {
+ // Don't join anything already joined
+ return $this;
+ }
+
+ // Split object parts
+ $objects = explode(':', $target_path);
+ $target = $this;
+ foreach ($objects as $object)
+ {
+ // Go down the line of objects to find the given target
+ $parent = $target;
+ $target = $parent->related_object($object);
+
+ if ( ! $target)
+ {
+ // Can't find related object
+ return $this;
+ }
+ }
+
+ $target_name = $object;
+
+ // Pop-off top object to get the parent object (user:photo:tag becomes user:photo - the parent table prefix)
+ array_pop($objects);
+ $parent_path = implode(':', $objects);
+
+ if (empty($parent_path))
+ {
+ // Use this table name itself for the parent object
+ $parent_path = $this->table_name;
+ }
+ else
+ {
+ if( ! isset($this->with_applied[$parent_path]))
+ {
+ // If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
+ $this->with($parent_path);
+ }
+ }
+
+ // Add to with_applied to prevent duplicate joins
+ $this->with_applied[$target_path] = TRUE;
+
+ $select = array();
+
+ // Use the keys of the empty object to determine the columns
+ foreach (array_keys($target->object) as $column)
+ {
+ // Add the prefix so that load_result can determine the relationship
+ $select[$target_path.':'.$column] = $target_path.'.'.$column;
+ }
+
+ // Select all of the prefixed keys in the object
+ $this->db_builder->select($select);
+
+ if (in_array($target->object_name, $parent->belongs_to))
+ {
+ // Parent belongs_to target, use target's primary key as join column
+ $join_col1 = $target->foreign_key(TRUE, $target_path);
+ $join_col2 = $parent->foreign_key($target_name, $parent_path);
+ }
+ else
+ {
+ // Parent has_one target, use parent's primary key as join column
+ $join_col2 = $parent->foreign_key(TRUE, $parent_path);
+ $join_col1 = $parent->foreign_key($target_name, $target_path);
+ }
+
+ // This trick allows for models to use different table prefixes (sharing the same database)
+ $join_table = array($this->db->quote_table($target_path) => $target->db->quote_table($target->table_name));
+
+ // Join the related object into the result
+ // Use Database_Expression to disable prefixing
+ $this->db_builder->join(new Database_Expression($join_table), $join_col1, $join_col2, 'LEFT');
+
+ return $this;
+ }
+
+ /**
+ * Finds and loads a single database row into the object.
+ *
+ * @chainable
+ * @param mixed primary key or an array of clauses
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function find($id = NULL, $ignore_changed = FALSE)
+ {
+ if ($id !== NULL)
+ {
+ if (is_array($id))
+ {
+ // Search for all clauses
+ $this->db_builder->where(array($id));
+ }
+ else
+ {
+ // Search for a specific column
+ $this->db_builder->where($this->table_name.'.'.$this->unique_key($id), '=', $id);
+ }
+ }
+
+ return $this->load_result(FALSE, $ignore_changed);
+ }
+
+ /**
+ * Finds multiple database rows and returns an iterator of the rows found.
+ *
+ * @chainable
+ * @param integer SQL limit
+ * @param integer SQL offset
+ * @return ORM_Iterator
+ */
+ public function find_all($limit = NULL, $offset = NULL)
+ {
+ if ($limit !== NULL AND ! isset($this->db_applied['limit']))
+ {
+ // Set limit
+ $this->limit($limit);
+ }
+
+ if ($offset !== NULL AND ! isset($this->db_applied['offset']))
+ {
+ // Set offset
+ $this->offset($offset);
+ }
+
+ return $this->load_result(TRUE);
+ }
+
+ /**
+ * Creates a key/value array from all of the objects available. Uses find_all
+ * to find the objects.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ $val = $this->primary_val;
+ }
+
+ // Return a select list from the results
+ return $this->select($this->table_name.'.'.$key, $this->table_name.'.'.$val)->find_all()->select_list($key, $val);
+ }
+
+ /**
+ * Validates the current object. This method requires that rules are set to be useful, if called with out
+ * any rules set, or if a Validation object isn't passed, nothing happens.
+ *
+ * @param object Validation array
+ * @param boolean Save on validate
+ * @return ORM
+ * @chainable
+ */
+ public function validate(Validation $array = NULL)
+ {
+ if ($array === NULL)
+ $array = new Validation($this->object);
+
+ if (count($this->rules) > 0)
+ {
+ foreach ($this->rules as $field => $parameters)
+ {
+ foreach ($parameters as $type => $value) {
+ switch ($type) {
+ case 'pre_filter':
+ $array->pre_filter($value,$field);
+ break;
+ case 'rules':
+ $rules = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_rules'), $rules);
+ break;
+ case 'callbacks':
+ $callbacks = array_merge(array($field),$value);
+ call_user_func_array(array($array,'add_callbacks'), $callbacks);
+ break;
+ }
+ }
+ }
+ }
+ // Validate the array
+ if (($this->_valid = $array->validate()) === FALSE)
+ {
+ ORM_Validation_Exception::handle_validation($this->table_name, $array);
+ }
+
+ // Fields may have been modified by filters
+ $this->object = array_merge($this->object, $array->getArrayCopy());
+
+ // Return validation status
+ return $this;
+ }
+
+ /**
+ * Saves the current object.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function save()
+ {
+ if ( ! empty($this->changed))
+ {
+ // Require model validation before saving
+ if ( ! $this->_valid)
+ {
+ $this->validate();
+ }
+
+ $data = array();
+ foreach ($this->changed as $column)
+ {
+ // Compile changed data
+ $data[$column] = $this->object[$column];
+ }
+
+ if ( ! $this->empty_primary_key() AND ! isset($this->changed[$this->primary_key]))
+ {
+ // Primary key isn't empty and hasn't been changed so do an update
+
+ if (is_array($this->updated_column))
+ {
+ // Fill the updated column
+ $column = $this->updated_column['column'];
+ $format = $this->updated_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $query = db::update($this->table_name)
+ ->set($data)
+ ->where($this->primary_key, '=', $this->primary_key_value)
+ ->execute($this->db);
+
+ // Object has been saved
+ $this->_saved = TRUE;
+ }
+ else
+ {
+ if (is_array($this->created_column))
+ {
+ // Fill the created column
+ $column = $this->created_column['column'];
+ $format = $this->created_column['format'];
+
+ $data[$column] = $this->object[$column] = ($format === TRUE) ? time() : date($format);
+ }
+
+ $result = db::insert($this->table_name)
+ ->columns(array_keys($data))
+ ->values(array_values($data))
+ ->execute($this->db);
+
+ if ($result->count() > 0)
+ {
+ if (empty($this->object[$this->primary_key]))
+ {
+ // Load the insert id as the primary key
+ $this->object[$this->primary_key] = $result->insert_id();
+ }
+
+ // Object is now loaded and saved
+ $this->_loaded = $this->_saved = TRUE;
+ }
+ }
+
+ if ($this->saved() === TRUE)
+ {
+ // All changes have been saved
+ $this->changed = array();
+ }
+ }
+
+ if ($this->saved() === TRUE AND ! empty($this->changed_relations))
+ {
+ foreach ($this->changed_relations as $column => $values)
+ {
+ // All values that were added
+ $added = array_diff($values, $this->object_relations[$column]);
+
+ // All values that were saved
+ $removed = array_diff($this->object_relations[$column], $values);
+
+ if (empty($added) AND empty($removed))
+ {
+ // No need to bother
+ continue;
+ }
+
+ // Clear related columns
+ unset($this->related[$column]);
+
+ // Load the model
+ $model = ORM::factory(inflector::singular($column));
+
+ if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
+ continue;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ // Foreign keys for the join table
+ $object_fk = $this->foreign_key($join_table);
+ $related_fk = $model->foreign_key($join_table);
+
+ if ( ! empty($added))
+ {
+ foreach ($added as $id)
+ {
+ // Insert the new relationship
+ db::insert($join_table)
+ ->columns($object_fk, $related_fk)
+ ->values($this->primary_key_value, $id)
+ ->execute($this->db);
+ }
+ }
+
+ if ( ! empty($removed))
+ {
+ db::delete($join_table)
+ ->where($object_fk, '=', $this->primary_key_value)
+ ->where($related_fk, 'IN', $removed)
+ ->execute($this->db);
+ }
+
+ // Clear all relations for this column
+ unset($this->object_relations[$column], $this->changed_relations[$column]);
+ }
+ }
+
+ if ($this->saved() === TRUE)
+ {
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Deletes the current object from the database. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param mixed id to delete
+ * @return ORM
+ */
+ public function delete($id = NULL)
+ {
+ if ($id === NULL)
+ {
+ // Use the the primary key value
+ $id = $this->primary_key_value;
+ }
+
+ // Delete this object
+ db::delete($this->table_name)
+ ->where($this->primary_key, '=', $id)
+ ->execute($this->db);
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ return $this->clear();
+ }
+
+ /**
+ * Delete all objects in the associated table. This does NOT destroy
+ * relationships that have been created with other objects.
+ *
+ * @chainable
+ * @param array ids to delete
+ * @return ORM
+ */
+ public function delete_all($ids = NULL)
+ {
+ if (is_array($ids))
+ {
+ // Delete only given ids
+ db::delete($this->table_name)
+ ->where($this->primary_key, 'IN', $ids)
+ ->execute($this->db);
+ }
+ elseif ($ids === NULL)
+ {
+ // Delete all records
+ db::delete($this->table_name)
+ ->execute($this->db);
+ }
+ else
+ {
+ // Do nothing - safeguard
+ return $this;
+ }
+
+ // Clear the per-request database cache
+ $this->db->clear_cache(NULL, Database::PER_REQUEST);
+
+ return $this->clear();
+ }
+
+ /**
+ * Unloads the current object and clears the status.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function clear()
+ {
+ // Create an array with all the columns set to NULL
+ $columns = array_keys($this->table_columns);
+ $values = array_combine($columns, array_fill(0, count($columns), NULL));
+
+ // Replace the current object with an empty one
+ $this->_load_values($values);
+
+ return $this;
+ }
+
+ /**
+ * Reloads the current object from the database.
+ *
+ * @chainable
+ * @return ORM
+ */
+ public function reload()
+ {
+ return $this->find($this->object[$this->primary_key]);
+ }
+
+ /**
+ * Reload column definitions.
+ *
+ * @chainable
+ * @param boolean force reloading
+ * @return ORM
+ */
+ public function reload_columns($force = FALSE)
+ {
+ if ($force === TRUE OR empty($this->table_columns))
+ {
+ if (isset(ORM::$column_cache[$this->object_name]))
+ {
+ // Use cached column information
+ $this->table_columns = ORM::$column_cache[$this->object_name];
+ }
+ else
+ {
+ // Load table columns
+ ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Tests if this object has a relationship to a different model.
+ *
+ * @param object related ORM model
+ * @param boolean check for any relations to given model
+ * @return boolean
+ */
+ public function has(ORM $model, $any = FALSE)
+ {
+ // Determine plural or singular relation name
+ $related = ($model->table_names_plural === TRUE) ? $model->object_plural : $model->object_name;
+
+ if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
+ return FALSE;
+
+ if (is_int($join_table))
+ {
+ // No "through" table, load the default JOIN table
+ $join_table = $model->join_table($this->table_name);
+ }
+
+ if ( ! isset($this->object_relations[$related]))
+ {
+ // Load the object relationships
+ $this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
+ }
+
+ if( ! $model->loaded() AND ! $model->empty_primary_key())
+ {
+ // Load the related model if it hasn't already been
+ $model->find($model->object[$model->primary_key]);
+ }
+
+ if ( ! $model->empty_primary_key())
+ {
+ // Check if a specific object exists
+ return in_array($model->primary_key_value, $this->changed_relations[$related]);
+ }
+ elseif ($any)
+ {
+ // Check if any relations to given model exist
+ return ! empty($this->changed_relations[$related]);
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function add(ORM $model)
+ {
+ if ($this->has($model))
+ return TRUE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ // Add the new relation to the update
+ $this->changed_relations[$column][] = $model->primary_key_value;
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Adds a new relationship to between this model and another.
+ *
+ * @param object related ORM model
+ * @return boolean
+ */
+ public function remove(ORM $model)
+ {
+ if ( ! $this->has($model))
+ return FALSE;
+
+ // Get the faked column name
+ $column = $model->object_plural;
+
+ if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
+ return FALSE;
+
+ // Remove the relationship
+ unset($this->changed_relations[$column][$key]);
+
+ if (isset($this->related[$column]))
+ {
+ // Force a reload of the relationships
+ unset($this->related[$column]);
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Count the number of records in the table.
+ *
+ * @return integer
+ */
+ public function count_all()
+ {
+ // Return the total number of records in a table
+ return $this->db_builder->count_records($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database list_fields.
+ *
+ * @return array
+ */
+ public function list_fields()
+ {
+ // Proxy to database
+ return $this->db->list_fields($this->table_name);
+ }
+
+ /**
+ * Proxy method to Database clear_cache.
+ *
+ * @chainable
+ * @param string SQL query to clear
+ * @param integer Type of cache to clear, Database::CROSS_REQUEST or Database::PER_REQUEST
+ * @return ORM
+ */
+ public function clear_cache($sql = NULL, $type = NULL)
+ {
+ // Proxy to database
+ $this->db->clear_cache($sql, $type);
+
+ ORM::$column_cache = array();
+
+ return $this;
+ }
+
+ /**
+ * Returns the unique key for a specific value. This method is expected
+ * to be overloaded in models if the model has other unique columns.
+ *
+ * @param mixed unique value
+ * @return string
+ */
+ public function unique_key($id)
+ {
+ return $this->primary_key;
+ }
+
+ /**
+ * Determines the name of a foreign key for a specific table.
+ *
+ * @param string related table name
+ * @param string prefix table name (used for JOINs)
+ * @return string
+ */
+ public function foreign_key($table = NULL, $prefix_table = NULL)
+ {
+ if ($table === TRUE)
+ {
+ if (is_string($prefix_table))
+ {
+ // Use prefix table name and this table's PK
+ return $prefix_table.'.'.$this->primary_key;
+ }
+ else
+ {
+ // Return the name of this table's PK
+ return $this->table_name.'.'.$this->primary_key;
+ }
+ }
+
+ if (is_string($prefix_table))
+ {
+ // Add a period for prefix_table.column support
+ $prefix_table .= '.';
+ }
+
+ if (isset($this->foreign_key[$table]))
+ {
+ // Use the defined foreign key name, no magic here!
+ $foreign_key = $this->foreign_key[$table];
+ }
+ else
+ {
+ if ( ! is_string($table) OR ! array_key_exists($table.'_'.$this->primary_key, $this->object))
+ {
+ // Use this table
+ $table = $this->table_name;
+
+ if (strpos($table, '.') !== FALSE)
+ {
+ // Hack around support for PostgreSQL schemas
+ list ($schema, $table) = explode('.', $table, 2);
+ }
+
+ if ($this->table_names_plural === TRUE)
+ {
+ // Make the key name singular
+ $table = inflector::singular($table);
+ }
+ }
+
+ $foreign_key = $table.'_'.$this->primary_key;
+ }
+
+ return $prefix_table.$foreign_key;
+ }
+
+ /**
+ * This uses alphabetical comparison to choose the name of the table.
+ *
+ * Example: The joining table of users and roles would be roles_users,
+ * because "r" comes before "u". Joining products and categories would
+ * result in categories_products, because "c" comes before "p".
+ *
+ * Example: zoo > zebra > robber > ocean > angel > aardvark
+ *
+ * @param string table name
+ * @return string
+ */
+ public function join_table($table)
+ {
+ if ($this->table_name > $table)
+ {
+ $table = $table.'_'.$this->table_name;
+ }
+ else
+ {
+ $table = $this->table_name.'_'.$table;
+ }
+
+ return $table;
+ }
+
+ /**
+ * Returns an ORM model for the given object name;
+ *
+ * @param string object name
+ * @return ORM
+ */
+ protected function related_object($object)
+ {
+ if (isset($this->has_one[$object]))
+ {
+ $object = ORM::factory($this->has_one[$object]);
+ }
+ elseif (isset($this->belongs_to[$object]))
+ {
+ $object = ORM::factory($this->belongs_to[$object]);
+ }
+ elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
+ {
+ $object = ORM::factory($object);
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Loads an array of values into into the current object.
+ *
+ * @chainable
+ * @param array values to load
+ * @return ORM
+ */
+ public function load_values(array $values)
+ {
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! isset($this->changed[$column]))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ //Update the column, respects __get()
+ $this->$column = $value;
+ }
+ }
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Loads an array of values into into the current object. Only used internally
+ *
+ * @chainable
+ * @param array values to load
+ * @param bool ignore loading of columns that have been modified
+ * @return ORM
+ */
+ public function _load_values(array $values, $ignore_changed = FALSE)
+ {
+ if (array_key_exists($this->primary_key, $values))
+ {
+ if ( ! $ignore_changed)
+ {
+ // Replace the object and reset the object status
+ $this->object = $this->changed = $this->related = array();
+ }
+
+ // Set the loaded and saved object status based on the primary key
+ $this->_loaded = $this->_saved = ($values[$this->primary_key] !== NULL);
+ }
+
+ // Related objects
+ $related = array();
+
+ foreach ($values as $column => $value)
+ {
+ if (strpos($column, ':') === FALSE)
+ {
+ if ( ! $ignore_changed OR ! isset($this->changed[$column]))
+ {
+ if (isset($this->table_columns[$column]))
+ {
+ // The type of the value can be determined, convert the value
+ $value = $this->load_type($column, $value);
+ }
+
+ $this->object[$column] = $value;
+ }
+ }
+ else
+ {
+ list ($prefix, $column) = explode(':', $column, 2);
+
+ $related[$prefix][$column] = $value;
+ }
+ }
+
+ if ( ! empty($related))
+ {
+ foreach ($related as $object => $values)
+ {
+ // Load the related objects with the values in the result
+ $this->related[$object] = $this->related_object($object)->_load_values($values);
+ }
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Loads a value according to the types defined by the column metadata.
+ *
+ * @param string column name
+ * @param mixed value to load
+ * @return mixed
+ */
+ protected function load_type($column, $value)
+ {
+ $type = gettype($value);
+ if ($type == 'object' OR $type == 'array' OR ! isset($this->table_columns[$column]))
+ return $value;
+
+ // Load column data
+ $column = $this->table_columns[$column];
+
+ if ($value === NULL AND ! empty($column['nullable']))
+ return $value;
+
+ if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
+ {
+ // Use boolean for BINARY(1) fields
+ $column['type'] = 'boolean';
+ }
+
+ switch ($column['type'])
+ {
+ case 'int':
+ if ($value === '' AND ! empty($column['nullable']))
+ {
+ // Forms will only submit strings, so empty integer values must be null
+ $value = NULL;
+ }
+ elseif ((float) $value > PHP_INT_MAX)
+ {
+ // This number cannot be represented by a PHP integer, so we convert it to a string
+ $value = (string) $value;
+ }
+ else
+ {
+ $value = (int) $value;
+ }
+ break;
+ case 'float':
+ $value = (float) $value;
+ break;
+ case 'boolean':
+ $value = (bool) $value;
+ break;
+ case 'string':
+ $value = (string) $value;
+ break;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Loads a database result, either as a new object for this model, or as
+ * an iterator for multiple rows.
+ *
+ * @chainable
+ * @param boolean return an iterator or load a single row
+ * @param boolean ignore loading of columns that have been modified
+ * @return ORM for single rows
+ * @return ORM_Iterator for multiple rows
+ */
+ protected function load_result($array = FALSE, $ignore_changed = FALSE)
+ {
+ $this->db_builder->from($this->table_name);
+
+ if ($array === FALSE)
+ {
+ // Only fetch 1 record
+ $this->db_builder->limit(1);
+ }
+
+ if ( ! isset($this->db_applied['select']))
+ {
+ // Select all columns by default
+ $this->db_builder->select($this->table_name.'.*');
+ }
+
+ if ( ! empty($this->load_with))
+ {
+ foreach ($this->load_with as $alias => $object)
+ {
+ // Join each object into the results
+ if (is_string($alias))
+ {
+ // Use alias
+ $this->with($alias);
+ }
+ else
+ {
+ // Use object
+ $this->with($object);
+ }
+ }
+ }
+
+ if ( ! isset($this->db_applied['order_by']) AND ! empty($this->sorting))
+ {
+ $sorting = array();
+ foreach ($this->sorting as $column => $direction)
+ {
+ if (strpos($column, '.') === FALSE)
+ {
+ // Keeps sorting working properly when using JOINs on
+ // tables with columns of the same name
+ $column = $this->table_name.'.'.$column;
+ }
+
+ $sorting[$column] = $direction;
+ }
+
+ // Apply the user-defined sorting
+ $this->db_builder->order_by($sorting);
+ }
+
+ // Load the result
+ $result = $this->db_builder->execute($this->db);
+
+ if ($array === TRUE)
+ {
+ // Return an iterated result
+ return new ORM_Iterator($this, $result);
+ }
+
+ if ($result->count() === 1)
+ {
+ // Load object values
+ $this->_load_values($result->as_array()->current(), $ignore_changed);
+ }
+ else
+ {
+ // Clear the object, nothing was found
+ $this->clear();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return an array of all the primary keys of the related table.
+ *
+ * @param string table name
+ * @param object ORM model to find relations of
+ * @return array
+ */
+ protected function load_relations($table, ORM $model)
+ {
+ $result = db::select(array('id' => $model->foreign_key($table)))
+ ->from($table)
+ ->where($this->foreign_key($table, $table), '=', $this->primary_key_value)
+ ->execute($this->db)
+ ->as_object();
+
+ $relations = array();
+ foreach ($result as $row)
+ {
+ $relations[] = $row->id;
+ }
+
+ return $relations;
+ }
+
+ /**
+ * Returns whether or not primary key is empty
+ *
+ * @return bool
+ */
+ protected function empty_primary_key()
+ {
+ return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
+ }
+
+} // End ORM
diff --git a/web_client/system/libraries/ORM_Iterator.php b/web_client/system/libraries/ORM_Iterator.php
new file mode 100644
index 00000000..403d1e93
--- /dev/null
+++ b/web_client/system/libraries/ORM_Iterator.php
@@ -0,0 +1,268 @@
+class_name = get_class($model);
+ $this->primary_key = $model->primary_key;
+ $this->primary_val = $model->primary_val;
+
+ // Database result (make sure rows are returned as arrays)
+ $this->result = $result;
+ }
+
+ /**
+ * Returns an array of the results as ORM objects or a nested array
+ *
+ * @param bool TRUE to return an array of ORM objects, FALSE for an array of arrays
+ * @param string key column to index on, NULL to ignore
+ * @return array
+ */
+ public function as_array($objects = TRUE, $key = NULL)
+ {
+ $array = array();
+
+ // Import class name
+ $class = $this->class_name;
+
+ if ($objects)
+ {
+ // Generate an array of objects
+ foreach ($this->result as $data)
+ {
+ if ($key === NULL)
+ {
+ // No indexing
+ $array[] = new $class($data);
+ }
+ else
+ {
+ // Index on the given key
+ $array[$data->$key] = new $class($data);
+ }
+ }
+ }
+ else
+ {
+ // Generate an array of arrays (and the subarrays may be nested in the case of relationships)
+ // This could be done by creating a new ORM object and calling as_array on it, but this is much faster
+ foreach ($this->result as $data)
+ {
+ // Have to do a bit of magic here to handle any relationships and generate a nested array for them
+ $temp = array();
+
+ foreach ($data as $key => $val)
+ {
+ $ptr = & $temp;
+
+ foreach (explode(':', $key) as $subkey)
+ {
+ // Walk thru the relationships (separated in the key name by a ':')
+ // 'user:email:address' will be array['user']['email']['address']
+ $ptr = & $ptr[$subkey];
+ }
+
+ // Set the value
+ $ptr = $val;
+ }
+
+ // Append the result
+ $array[] = $temp;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Return an array of all of the primary keys for this object.
+ *
+ * @return array
+ */
+ public function primary_key_array()
+ {
+ $ids = array();
+ foreach ($this->result as $row)
+ {
+ $ids[] = $row->{$this->primary_key};
+ }
+ return $ids;
+ }
+
+ /**
+ * Create a key/value array from the results.
+ *
+ * @param string key column
+ * @param string value column
+ * @return array
+ */
+ public function select_list($key = NULL, $val = NULL)
+ {
+ if ($key === NULL)
+ {
+ // Use the default key
+ $key = $this->primary_key;
+ }
+
+ if ($val === NULL)
+ {
+ // Use the default value
+ $val = $this->primary_val;
+ }
+
+ $array = array();
+ foreach ($this->result as $row)
+ {
+ $array[$row->$key] = $row->$val;
+ }
+ return $array;
+ }
+
+ /**
+ * Return a range of offsets.
+ *
+ * @param integer start
+ * @param integer end
+ * @return array
+ */
+ public function range($start, $end)
+ {
+ // Array of objects
+ $array = array();
+
+ if ($this->result->offsetExists($start))
+ {
+ // Import the class name
+ $class = $this->class_name;
+
+ // Set the end offset
+ $end = $this->result->offsetExists($end) ? $end : $this->count();
+
+ for ($i = $start; $i < $end; $i++)
+ {
+ // Insert each object in the range
+ $array[] = new $class($this->result->offsetGet($i));
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Countable: count
+ */
+ public function count()
+ {
+ return $this->result->count();
+ }
+
+ /**
+ * Iterator: current
+ */
+ public function current()
+ {
+ if ($row = $this->result->current())
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ $row = new $class($row);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Iterator: key
+ */
+ public function key()
+ {
+ return $this->result->key();
+ }
+
+ /**
+ * Iterator: next
+ */
+ public function next()
+ {
+ return $this->result->next();
+ }
+
+ /**
+ * Iterator: rewind
+ */
+ public function rewind()
+ {
+ $this->result->rewind();
+ }
+
+ /**
+ * Iterator: valid
+ */
+ public function valid()
+ {
+ return $this->result->valid();
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ */
+ public function offsetExists($offset)
+ {
+ return $this->result->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ */
+ public function offsetGet($offset)
+ {
+ if ($this->result->offsetExists($offset))
+ {
+ // Import class name
+ $class = $this->class_name;
+
+ return new $class($this->result->offsetGet($offset));
+ }
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @throws Kohana_Database_Exception
+ */
+ public function offsetUnset($offset)
+ {
+ throw new Kohana_Database_Exception('database.result_read_only');
+ }
+
+} // End ORM Iterator
\ No newline at end of file
diff --git a/web_client/system/libraries/ORM_Validation_Exception.php b/web_client/system/libraries/ORM_Validation_Exception.php
new file mode 100644
index 00000000..95f96c3b
--- /dev/null
+++ b/web_client/system/libraries/ORM_Validation_Exception.php
@@ -0,0 +1,26 @@
+$table));
+ $exception->validation = $array;
+ throw $exception;
+ }
+} // End ORM_Validation_Exception
\ No newline at end of file
diff --git a/web_client/system/libraries/Profiler.php b/web_client/system/libraries/Profiler.php
new file mode 100644
index 00000000..b7a5ecae
--- /dev/null
+++ b/web_client/system/libraries/Profiler.php
@@ -0,0 +1,308 @@
+styles();
+ }
+
+ // Load the profiler view
+ $data = array
+ (
+ 'profiles' => Profiler::$profiles,
+ 'styles' => $styles,
+ 'execution_time' => microtime(TRUE) - $start
+ );
+ $view = new View('profiler/profiler', $data);
+
+ // Return rendered view if $return is TRUE
+ if ($return === TRUE)
+ return $view->render();
+
+ // Add profiler data to the output
+ if (stripos(Kohana::$output, '