diff options
39 files changed, 4855 insertions, 0 deletions
diff --git a/api/.htaccess b/api/.htaccess new file mode 100755 index 0000000..706a6f4 --- /dev/null +++ b/api/.htaccess @@ -0,0 +1,10 @@ +RewriteEngine On + +# Some hosts may require you to use the `RewriteBase` directive. +# If you need to use the `RewriteBase` directive, it should be the +# absolute physical path to the directory that contains this htaccess file. +# +# RewriteBase / + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ index.php [QSA,L]
\ No newline at end of file diff --git a/api/Slim/Exception/Pass.php b/api/Slim/Exception/Pass.php new file mode 100755 index 0000000..bdefea0 --- /dev/null +++ b/api/Slim/Exception/Pass.php @@ -0,0 +1,45 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Pass Exception + * + * This Exception will cause the Router::dispatch method + * to skip the current matching route and continue to the next + * matching route. If no subsequent routes are found, a + * HTTP 404 Not Found response will be sent to the client. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Exception_Pass extends Exception {}
\ No newline at end of file diff --git a/api/Slim/Exception/RequestSlash.php b/api/Slim/Exception/RequestSlash.php new file mode 100755 index 0000000..1e11b91 --- /dev/null +++ b/api/Slim/Exception/RequestSlash.php @@ -0,0 +1,46 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Request Slash Exception + * + * This Exception is thrown when Slim detects a matching route + * (defined with a trailing slash) and the HTTP request + * matches the route but does not have a trailing slash. This + * exception will be caught in `Slim::run` and trigger a 301 redirect + * to the same resource URI with a trailing slash. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Exception_RequestSlash extends Exception {}
\ No newline at end of file diff --git a/api/Slim/Exception/Stop.php b/api/Slim/Exception/Stop.php new file mode 100755 index 0000000..086adb3 --- /dev/null +++ b/api/Slim/Exception/Stop.php @@ -0,0 +1,43 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Stop Exception + * + * This Exception is thrown when the Slim application needs to abort + * processing and return control flow to the outer PHP script. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Exception_Stop extends Exception {}
\ No newline at end of file diff --git a/api/Slim/Http/Cookie.php b/api/Slim/Http/Cookie.php new file mode 100755 index 0000000..ffb2881 --- /dev/null +++ b/api/Slim/Http/Cookie.php @@ -0,0 +1,222 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Cookie + * + * Object-oriented representation of a Cookie to be sent in an HTTP response + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Http_Cookie { + + /** + * @var string + */ + protected $name; + + /** + * @var string + */ + protected $value; + + /** + * @var int UNIX timestamp + */ + protected $expires; + + /** + * @var string + */ + protected $path; + + /** + * @var string + */ + protected $domain; + + /** + * @var bool + */ + protected $secure; + + /** + * @var bool + */ + protected $httponly; + + /** + * Constructor + * @param string $name The cookie name + * @param string $value The cookie value + * @param mixed $time The duration of the cookie; + * If integer, should be a UNIX timestamp; + * If string, converted to UNIX timestamp with `strtotime`; + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param bool $secure Indicates that the cookie should only be transmitted over a secure + * HTTPS connection from the client + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + * @return void + */ + public function __construct( $name, $value = null, $expires = 0, $path = null, $domain = null, $secure = false, $httponly = false ) { + $this->setName($name); + $this->setValue($value); + $this->setExpires($expires); + $this->setPath($path); + $this->setDomain($domain); + $this->setSecure($secure); + $this->setHttpOnly($httponly); + } + + /** + * Get cookie name + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Set cookie name + * @param string $name + * @return void + */ + public function setName( $name ) { + $this->name = (string)$name; + } + + /** + * Get cookie value + * @return string + */ + public function getValue() { + return $this->value; + } + + /** + * Set cookie value + * @param string $value + * @return void + */ + public function setValue( $value ) { + $this->value = (string)$value; + } + + /** + * Get cookie expiration time + * @return int UNIX timestamp + */ + public function getExpires() { + return $this->expires; + } + + /** + * Set cookie expiration time + * @param string|int Cookie expiration time + * @return void + */ + public function setExpires( $time ) { + $this->expires = is_string($time) ? strtotime($time) : (int)$time; + } + + /** + * Get cookie path + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Set cookie path + * @param string $path + * @return void + */ + public function setPath( $path ) { + $this->path = (string)$path; + } + + /** + * Get cookie domain + * @return string + */ + public function getDomain() { + return $this->domain; + } + + /** + * Set cookie domain + * @param string $domain + * @return void + */ + public function setDomain( $domain ) { + $this->domain = (string)$domain; + } + + /** + * Is cookie sent only if SSL/HTTPS is used? + * @return bool + */ + public function getSecure() { + return $this->secure; + } + + /** + * Set whether cookie is sent only if SSL/HTTPS is used + * @param bool $secure + * @return void + */ + public function setSecure( $secure ) { + $this->secure = (bool)$secure; + } + + /** + * Is cookie sent with HTTP protocol only? + * @return bool + */ + public function getHttpOnly() { + return $this->httponly; + } + + /** + * Set whether cookie is sent with HTTP protocol only + * @param bool $httponly + * @return void + */ + public function setHttpOnly( $httponly ) { + $this->httponly = (bool)$httponly; + } + +}
\ No newline at end of file diff --git a/api/Slim/Http/CookieJar.php b/api/Slim/Http/CookieJar.php new file mode 100755 index 0000000..f2f0305 --- /dev/null +++ b/api/Slim/Http/CookieJar.php @@ -0,0 +1,401 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Cooke Jar + * + * Used to manage signed, encrypted Cookies. Provides: + * + * - Cookie integrity and authenticity with HMAC + * - Confidentiality with symmetric encryption + * - Protection from replay attack if using SSL or TLS + * - Protection from interception if using SSL or TLS + * + * This code was originally called "BigOrNot_CookieManager" and written by + * Matthieu Huguet released under "CopyLeft" license. I have cleaned up the + * code formatting to conform with Slim Framework contributor guidelines and + * added additional code where necessary to play nice with Slim Cookie objects. + * + * Requirements: + * + * - libmcrypt > 2.4.x + * + * @author Matthies Huguet <http://bigornot.blogspot.com/2008/06/security-cookies-and-rest.html> + */ +class Slim_Http_CookieJar { + + /** + * @var string Server secret key + */ + protected $_secret = ''; + + /** + * @var int Cryptographic algorithm used to encrypt cookies data + */ + protected $_algorithm = MCRYPT_RIJNDAEL_256; + + /** + * @var int Cryptographic mode (CBC, CFB ...) + */ + protected $_mode = MCRYPT_MODE_CBC; + + /** + * @var resource mcrypt module resource + */ + protected $_cryptModule = null; + + /** + * @var bool Enable high confidentiality for cookie value (symmetric encryption) + */ + protected $_highConfidentiality = true; + + /** + * @var bool Enable SSL support + */ + protected $_ssl = false; + + /** + * @var array[Cookie] Cookie objects + */ + protected $_cookies = array(); + + /** + * Constructor + * + * Initialize cookie manager and mcrypt module. + * + * @param string $secret Server's secret key + * @param array $config + * @throws Exception If secret key is empty + * @throws Exception If unable to open mcypt module + */ + public function __construct( $secret, $config = null ) { + if ( empty($secret) ) { + throw new Exception('You must provide a secret key'); + } + $this->_secret = $secret; + if ( $config !== null && !is_array($config) ) { + throw new Exception('Config must be an array'); + } + if ( is_array($config) ) { + if ( isset($config['high_confidentiality']) ) { + $this->_highConfidentiality = $config['high_confidentiality']; + } + if ( isset($config['mcrypt_algorithm']) ) { + $this->_algorithm = $config['mcrypt_algorithm']; + } + if ( isset($config['mcrypt_mode']) ) { + $this->_mode = $config['mcrypt_mode']; + } + if ( isset($config['enable_ssl']) ) { + $this->_ssl = $config['enable_ssl']; + } + } + if ( extension_loaded('mcrypt') ) { + $this->_cryptModule = mcrypt_module_open($this->_algorithm, '', $this->_mode, ''); + if ( $this->_cryptModule === false ) { + throw new Exception('Error while loading mcrypt module'); + } + } + } + + /** + * Get the high confidentiality mode + * + * @return bool TRUE if cookie data encryption is enabled, or FALSE if it isn't + */ + public function getHighConfidentiality() { + return $this->_highConfidentiality; + } + + /** + * Enable or disable cookie data encryption + * + * @param bool $enable TRUE to enable, FALSE to disable + * @return CookieJar + */ + public function setHighConfidentiality( $enable ) { + $this->_highConfidentiality = (bool)$enable; + return $this; + } + + /** + * Get the SSL status (enabled or disabled?) + * + * @return bool TRUE if SSL support is enabled, or FALSE if it isn't + */ + public function getSSL() { + return $this->_ssl; + } + + /** + * Enable SSL support (not enabled by default) + * + * Pro: Protect against replay attack + * Con: Cookie's lifetime is limited to SSL session's lifetime + * + * @param bool $enable TRUE to enable, FALSE to disable + * @return CookieJar + */ + public function setSSL( $enable ) { + $this->_ssl = (bool)$enable; + return $this; + } + + /** + * Get Cookies for Response + * + * @author Josh Lockhart <info@joshlockhart.com> + * @return array[Cookie] + */ + public function getResponseCookies() { + return $this->_cookies; + } + + /** + * Get Cookie with name for Response + * + * @author Josh Lockhart <info@joshlockhart.com> + * @param string $cookiename The name of the Cookie + * @return Cookie|null Cookie, or NULL if Cookie with name not found + */ + public function getResponseCookie( $cookiename ) { + return isset($this->_cookies[$cookiename]) ? $this->_cookies[$cookiename] : null; + } + + /** + * Set a secure cookie + * + * @param string $name Cookie name + * @param string $value Cookie value + * @param string $username User identifier + * @param integer $expire Expiration time + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function setCookie( $cookiename, $value, $username, $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = null ) { + $secureValue = extension_loaded('mcrypt') ? $this->_secureCookieValue($value, $username, $expire) : $value; + $this->setClassicCookie($cookiename, $secureValue, $expire, $path, $domain, $secure, $httponly); + } + + /** + * Delete a cookie + * + * @param string $name Cookie name + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function deleteCookie( $name, $path = '/', $domain = '', $secure = false, $httponly = null ) { + $expire = 315554400; /* 1980-01-01 */ + $this->_cookies[$name] = new Slim_Http_Cookie($name, '', $expire, $path, $domain, $secure, $httponly); + //setcookie($name, '', $expire, $path, $domain, $secure, $httponly); + } + + /** + * Get a secure cookie value + * + * Verify the integrity of cookie data and decrypt it. If the cookie + * is invalid, it can be automatically destroyed (default behaviour) + * + * @param string $cookiename Cookie name + * @param bool $delete Destroy the cookie if invalid? + * @return string|false The Cookie value, or FALSE if Cookie invalid + */ + public function getCookieValue( $cookiename, $deleteIfInvalid = true ) { + if ( $this->cookieExists($cookiename) ) { + if ( extension_loaded('mcrypt') ) { + $cookieValues = explode('|', $_COOKIE[$cookiename]); + if ( (count($cookieValues) === 4) && ($cookieValues[1] == 0 || $cookieValues[1] >= time()) ) { + $key = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1], $this->_secret); + $cookieData = base64_decode($cookieValues[2]); + if ( $cookieData !== '' && $this->getHighConfidentiality() ) { + $data = $this->_decrypt($cookieData, $key, md5($cookieValues[1])); + } else { + $data = $cookieData; + } + if ( $this->_ssl && isset($_SERVER['SSL_SESSION_ID']) ) { + $verifKey = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1] . $data . $_SERVER['SSL_SESSION_ID'], $key); + } else { + $verifKey = hash_hmac('sha1', $cookieValues[0] . $cookieValues[1] . $data, $key); + } + if ( $verifKey == $cookieValues[3] ) { + return $data; + } + } + } else { + return $_COOKIE[$cookiename]; + } + } + if ( $deleteIfInvalid ) { + $this->deleteCookie($cookiename); + } + return false; + } + + /** + * Send a classic (unsecure) cookie + * + * @param string $name Cookie name + * @param string $value Cookie value + * @param integer $expire Expiration time + * @param string $path Cookie path + * @param string $domain Cookie domain + * @param bool $secure When TRUE, send the cookie only on a secure connection + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + */ + public function setClassicCookie( $cookiename, $value, $expire = 0, $path = '/', $domain = '', $secure = false, $httponly = null ) { + /* httponly option is only available for PHP version >= 5.2 */ + if ( $httponly === null ) { + $this->_cookies[$cookiename] = new Slim_Http_Cookie($cookiename, $value, $expire, $path, $domain, $secure); + //setcookie($cookiename, $value, $expire, $path, $domain, $secure); + } else { + $this->_cookies[$cookiename] = new Slim_Http_Cookie($cookiename, $value, $expire, $path, $domain, $secure, $httponly); + //setcookie($cookiename, $value, $expire, $path, $domain, $secure, $httponly); + } + } + + /** + * Verify if a cookie exists + * + * @param string $cookiename + * @return bool TRUE if cookie exist, or FALSE if not + */ + public function cookieExists($cookiename) { + return isset($_COOKIE[$cookiename]); + } + + /** + * Secure a cookie value + * + * The initial value is transformed with this protocol: + * + * secureValue = username|expire|base64((value)k,expire)|HMAC(user|expire|value,k) + * where k = HMAC(user|expire, sk) + * and sk is server's secret key + * (value)k,md5(expire) is the result an cryptographic function (ex: AES256) on "value" with key k and initialisation vector = md5(expire) + * + * @param string $value Unsecure value + * @param string $username User identifier + * @param integer $expire Expiration time + * @return string Secured value + */ + protected function _secureCookieValue( $value, $username, $expire ) { + if ( is_string($expire) ) { + $expire = strtotime($expire); + } + $key = hash_hmac('sha1', $username . $expire, $this->_secret); + if ( $value !== '' && $this->getHighConfidentiality() ) { + $encryptedValue = base64_encode($this->_encrypt($value, $key, md5($expire))); + } else { + $encryptedValue = base64_encode($value); + } + if ( $this->_ssl && isset($_SERVER['SSL_SESSION_ID']) ) { + $verifKey = hash_hmac('sha1', $username . $expire . $value . $_SERVER['SSL_SESSION_ID'], $key); + } else { + $verifKey = hash_hmac('sha1', $username . $expire . $value, $key); + } + $result = array($username, $expire, $encryptedValue, $verifKey); + return implode('|', $result); + } + + /** + * Encrypt a given data with a given key and a given initialisation vector + * + * @param string $data Data to crypt + * @param string $key Secret key + * @param string $iv Initialisation vector + * @return string Encrypted data + */ + protected function _encrypt( $data, $key, $iv ) { + $iv = $this->_validateIv($iv); + $key = $this->_validateKey($key); + mcrypt_generic_init($this->_cryptModule, $key, $iv); + $res = @mcrypt_generic($this->_cryptModule, $data); + mcrypt_generic_deinit($this->_cryptModule); + return $res; + } + + /** + * Decrypt a given data with a given key and a given initialisation vector + * + * @param string $data Data to crypt + * @param string $key Secret key + * @param string $iv Initialisation vector + * @return string Encrypted data + */ + protected function _decrypt( $data, $key, $iv ) { + $iv = $this->_validateIv($iv); + $key = $this->_validateKey($key); + mcrypt_generic_init($this->_cryptModule, $key, $iv); + $decryptedData = mdecrypt_generic($this->_cryptModule, $data); + $res = str_replace("\x0", '', $decryptedData); + mcrypt_generic_deinit($this->_cryptModule); + return $res; + } + + /** + * Validate Initialization vector + * + * If given IV is too long for the selected mcrypt algorithm, it will be truncated + * + * @param string $iv Initialization vector + * @return string + */ + protected function _validateIv($iv) { + $ivSize = mcrypt_enc_get_iv_size($this->_cryptModule); + if ( strlen($iv) > $ivSize ) { + $iv = substr($iv, 0, $ivSize); + } + return $iv; + } + + /** + * Validate key + * + * If given key is too long for the selected mcrypt algorithm, it will be truncated + * + * @param string $key key + * @param string + */ + protected function _validateKey($key) { + $keySize = mcrypt_enc_get_key_size($this->_cryptModule); + if ( strlen($key) > $keySize ) { + $key = substr($key, 0, $keySize); + } + return $key; + } + +} diff --git a/api/Slim/Http/Request.php b/api/Slim/Http/Request.php new file mode 100755 index 0000000..3880a53 --- /dev/null +++ b/api/Slim/Http/Request.php @@ -0,0 +1,405 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Request + * + * Object-oriented representation of an HTTP request. This class + * is responsible for parsing the raw HTTP request into a format + * usable by the Slim application. + * + * This class will automatically remove slashes from GET, POST, PUT, + * and Cookie data if magic quotes are enabled. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @author Kris Jordan <http://www.github.com/KrisJordan> + * @since Version 1.0 + */ +class Slim_Http_Request { + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_DELETE = 'DELETE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_OVERRIDE = '_METHOD'; + + /** + * @var string Request method (ie. "GET", "POST", "PUT", "DELETE", "HEAD") + */ + protected $method; + + /** + * @var array Key-value array of HTTP request headers + */ + protected $headers; + + /** + * @var array Names of additional headers to parse from the current + * HTTP request that are not prefixed with "HTTP_" + */ + protected $additionalHeaders = array('content-type', 'content-length', 'php-auth-user', 'php-auth-pw', 'auth-type', 'x-requested-with'); + + /** + * @var array Key-value array of cookies sent with the + * current HTTP request + */ + protected $cookies; + + /** + * @var array Key-value array of HTTP GET parameters + */ + protected $get; + + /** + * @var array Key-value array of HTTP POST parameters + */ + protected $post; + + /** + * @var array Key-value array of HTTP PUT parameters + */ + protected $put; + + /** + * @var string Raw body of HTTP request + */ + protected $body; + + /** + * @var string Content type of HTTP request + */ + protected $contentType; + + /** + * @var string Resource URI (ie. "/person/1") + */ + protected $resource; + + /** + * @var string The root URI of the Slim application without trailing slash. + * This will be "" if the app is installed at the web + * document root. If the app is installed in a + * sub-directory "/foo", this will be "/foo". + */ + protected $root; + + /** + * Constructor + */ + public function __construct() { + $this->method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : false; + $this->headers = $this->loadHttpHeaders(); + $this->body = @file_get_contents('php://input'); + $this->get = self::stripSlashesIfMagicQuotes($_GET); + $this->post = self::stripSlashesIfMagicQuotes($_POST); + $this->put = self::stripSlashesIfMagicQuotes($this->loadPutParameters()); + $this->cookies = self::stripSlashesIfMagicQuotes($_COOKIE); + $this->root = Slim_Http_Uri::getBaseUri(true); + $this->resource = Slim_Http_Uri::getUri(true); + $this->checkForHttpMethodOverride(); + } + + /** + * Is this a GET request? + * @return bool + */ + public function isGet() { + return $this->method === self::METHOD_GET; + } + + /** + * Is this a POST request? + * @return bool + */ + public function isPost() { + return $this->method === self::METHOD_POST; + } + + /** + * Is this a PUT request? + * @return bool + */ + public function isPut() { + return $this->method === self::METHOD_PUT; + } + + /** + * Is this a DELETE request? + * @return bool + */ + public function isDelete() { + return $this->method === self::METHOD_DELETE; + } + + /** + * Is this a HEAD request? + * @return bool + */ + public function isHead() { + return $this->method === self::METHOD_HEAD; + } + + /** + * Is this a OPTIONS request? + * @return bool + */ + public function isOptions() { + return $this->method === self::METHOD_OPTIONS; + } + + /** + * Is this a XHR request? + * @return bool + */ + public function isAjax() { + return ( $this->params('isajax') || $this->headers('X_REQUESTED_WITH') === 'XMLHttpRequest' ); + } + + /** + * Fetch a PUT|POST|GET parameter value + * + * The preferred method to fetch the value of a + * PUT, POST, or GET parameter (searched in that order). + * + * @param string $key The paramter name + * @return string|null The value of parameter, or NULL if parameter not found + */ + public function params( $key ) { + foreach ( array('put', 'post', 'get') as $dataSource ) { + $source = $this->$dataSource; + if ( isset($source[(string)$key]) ) { + return $source[(string)$key]; + } + } + return null; + } + + /** + * Fetch GET parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function get( $key = null ) { + return $this->arrayOrArrayValue($this->get, $key); + } + + /** + * Fetch POST parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function post( $key = null ) { + return $this->arrayOrArrayValue($this->post, $key); + } + + /** + * Fetch PUT parameter(s) + * @param string $key Name of parameter + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function put( $key = null ) { + return $this->arrayOrArrayValue($this->put, $key); + } + + /** + * Fetch COOKIE value(s) + * @param string $key The cookie name + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function cookies( $key = null ) { + return $this->arrayOrArrayValue($this->cookies, $key); + } + + /** + * Get HTTP request header + * @param string $key The header name + * @return array|string|null All parameters, parameter value if $key + * and parameter exists, or NULL if $key + * and parameter does not exist. + */ + public function headers( $key = null ) { + return is_null($key) ? $this->headers : $this->arrayOrArrayValue($this->headers, $this->convertHttpHeaderName($key)); + } + + /** + * Get HTTP request body + * @return string|false String, or FALSE if body could not be read + */ + public function getBody() { + return $this->body; + } + + /** + * Get HTTP method + * @return string + */ + public function getMethod() { + return $this->method; + } + + /** + * Get HTTP request content type + * @return string + */ + public function getContentType() { + if ( !isset($this->contentType) ) { + $contentType = 'application/x-www-form-urlencoded'; + $header = $this->headers('CONTENT_TYPE'); + if ( !is_null($header) ) { + $headerParts = preg_split('/\s*;\s*/', $header); + $contentType = $headerParts[0]; + } + $this->contentType = $contentType; + } + return $this->contentType; + } + + /** + * Get HTTP request resource URI + * @return string + */ + public function getResourceUri() { + return $this->resource; + } + + /** + * Get HTTP request root URI + * @return string + */ + public function getRootUri() { + return $this->root; + } + + /** + * Fetch array or array value + * @param array $array + * @param string $key + * @return array|mixed Array if key is null, else array value + */ + protected function arrayOrArrayValue( array &$array, $key = null ) { + return is_null($key) ? $array : $this->arrayValueForKey($array, $key); + } + + /** + * Fetch value from array + * @return mixed|null + */ + protected function arrayValueForKey( array &$array, $key ) { + return isset($array[(string)$key]) ? $array[(string)$key] : null; + } + + /** + * Strip slashes from string or array of strings + * @param array|string $rawData + * @return array|string + */ + public static function stripSlashesIfMagicQuotes( $rawData ) { + if ( get_magic_quotes_gpc() ) { + return is_array($rawData) ? array_map(array('self', 'stripSlashesIfMagicQuotes'), $rawData) : stripslashes($rawData); + } else { + return $rawData; + } + } + + /** + * Get PUT parameters + * @return array Key-value array of HTTP request PUT parameters + */ + protected function loadPutParameters() { + if ( $this->getContentType() === 'application/x-www-form-urlencoded' ) { + $input = is_string($this->body) ? $this->body : ''; + if ( function_exists('mb_parse_str') ) { + mb_parse_str($input, $output); + } else { + parse_str($input, $output); + } + return $output; + } else { + return array(); + } + } + + /** + * Get HTTP request headers + * @return array Key-value array of HTTP request headers + */ + protected function loadHttpHeaders() { + $headers = array(); + foreach ( $_SERVER as $key => $value ) { + $key = $this->convertHttpHeaderName($key); + if ( strpos($key, 'http-') === 0 || in_array($key, $this->additionalHeaders) ) { + $name = str_replace('http-', '', $key); + $headers[$name] = $value; + } + } + return $headers; + } + + /** + * Convert HTTP header name + * @return string + */ + protected function convertHttpHeaderName( $name ) { + return str_replace('_', '-', strtolower($name)); + } + + /** + * Check for HTTP request method override + * + * Because traditional web browsers do not support PUT and DELETE + * HTTP methods, we use a hidden form input field to + * mimic PUT and DELETE requests. We check for this override here. + * + * @return void + */ + protected function checkForHttpMethodOverride() { + if ( isset($this->post[self::METHOD_OVERRIDE]) ) { + $this->method = $this->post[self::METHOD_OVERRIDE]; + unset($this->post[self::METHOD_OVERRIDE]); + if ( $this->isPut() ) { + $this->put = $this->post; + } + } + } + +}
\ No newline at end of file diff --git a/api/Slim/Http/Response.php b/api/Slim/Http/Response.php new file mode 100755 index 0000000..5414edb --- /dev/null +++ b/api/Slim/Http/Response.php @@ -0,0 +1,321 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Response + * + * Object-oriented representation of an HTTP response that is + * returned to the client. This class is responsible for: + * + * - HTTP response status + * - HTTP response body + * - HTTP response headers + * - HTTP response cookies + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @author Kris Jordan <http://github.com/KrisJordan> + * @since Version 1.0 + */ +class Slim_Http_Response { + + /** + * @var Slim_Http_Request + */ + protected $request; + + /** + * @var string + */ + protected $httpVersion = '1.1'; + + /** + * @var int HTTP status code + */ + protected $status = 200; + + /** + * @var array Key-value array of HTTP response headers + */ + protected $headers = array(); + + /** + * @var string HTTP response body + */ + protected $body = ''; + + /** + * @var int Length of HTTP response body + */ + protected $length = 0; + + /** + * @var array HTTP response codes and messages + */ + protected static $messages = array( + //Informational 1xx + 100 => '100 Continue', + 101 => '101 Switching Protocols', + //Successful 2xx + 200 => '200 OK', + 201 => '201 Created', + 202 => '202 Accepted', + 203 => '203 Non-Authoritative Information', + 204 => '204 No Content', + 205 => '205 Reset Content', + 206 => '206 Partial Content', + //Redirection 3xx + 300 => '300 Multiple Choices', + 301 => '301 Moved Permanently', + 302 => '302 Found', + 303 => '303 See Other', + 304 => '304 Not Modified', + 305 => '305 Use Proxy', + 306 => '306 (Unused)', + 307 => '307 Temporary Redirect', + //Client Error 4xx + 400 => '400 Bad Request', + 401 => '401 Unauthorized', + 402 => '402 Payment Required', + 403 => '403 Forbidden', + 404 => '404 Not Found', + 405 => '405 Method Not Allowed', + 406 => '406 Not Acceptable', + 407 => '407 Proxy Authentication Required', + 408 => '408 Request Timeout', + 409 => '409 Conflict', + 410 => '410 Gone', + 411 => '411 Length Required', + 412 => '412 Precondition Failed', + 413 => '413 Request Entity Too Large', + 414 => '414 Request-URI Too Long', + 415 => '415 Unsupported Media Type', + 416 => '416 Requested Range Not Satisfiable', + 417 => '417 Expectation Failed', + 422 => '422 Unprocessable Entity', + 423 => '423 Locked', + //Server Error 5xx + 500 => '500 Internal Server Error', + 501 => '501 Not Implemented', + 502 => '502 Bad Gateway', + 503 => '503 Service Unavailable', + 504 => '504 Gateway Timeout', + 505 => '505 HTTP Version Not Supported' + ); + + /** + * @var CookieJar Manages Cookies to be sent with this Response + */ + protected $cookieJar; + + /** + * Constructor + */ + public function __construct( Slim_Http_Request $req ) { + $this->request = $req; + $this->header('Content-Type', 'text/html'); + } + + /** + * Set and/or get the HTTP response version + * @param string $version + * @return void + * @throws InvalidArgumentException If argument is not a valid HTTP version + */ + public function httpVersion( $version = null ) { + if ( $version ) { + $version = (string)$version; + if ( $version === '1.0' || $version === '1.1' ) { + $this->httpVersion = $version; + } else { + throw new InvalidArgumentException('Invalid HTTP version in Response object'); + } + } + return $this->httpVersion; + } + + /** + * Set and/or get the HTTP response status code + * @param int $status + * @return int + * @throws InvalidArgumentException If argument is not a valid HTTP status code + */ + public function status( $status = null ) { + if ( !is_null($status) ) { + if ( !in_array(intval($status), array_keys(self::$messages)) ) { + throw new InvalidArgumentException('Cannot set Response status. Provided status code "' . $status . '" is not a valid HTTP response code.'); + } + $this->status = intval($status); + } + return $this->status; + } + + /** + * Get HTTP response headers + * @return array + */ + public function headers() { + return $this->headers; + } + + /** + * Get and/or set an HTTP response header + * @param string $key The header name + * @param string $value The header value + * @return string|null The header value, or NULL if header not set + */ + public function header( $key, $value = null ) { + if ( !is_null($value) ) { + $this->headers[$key] = $value; + } + return isset($this->headers[$key]) ? $this->headers[$key] : null; + } + + /** + * Set the HTTP response body + * @param string $body The new HTTP response body + * @return string The new HTTP response body + */ + public function body( $body = null ) { + if ( !is_null($body) ) { + $this->body = ''; + $this->length = 0; + $this->write($body); + } + return $this->body; + } + + /** + * Append the HTTP response body + * @param string $body Content to append to the current HTTP response body + * @return string The updated HTTP response body + */ + public function write( $body ) { + $body = (string)$body; + $this->length += strlen($body); + $this->body .= $body; + $this->header('Content-Length', $this->length); + return $body; + } + + /** + * Set cookie jar + * @param Slim_Http_CookieJar $cookieJar + * @return void + */ + public function setCookieJar( Slim_Http_CookieJar $cookieJar ) { + $this->cookieJar = $cookieJar; + } + + /** + * Get cookie jar + * @return Slim_Http_CookieJar + */ + public function getCookieJar() { + return $this->cookieJar; + } + + /** + * Finalize response headers before response is sent + * @return void + */ + public function finalize() { + if ( in_array($this->status, array(204, 304)) ) { + $this->body(''); + unset($this->headers['Content-Type']); + } + } + + /** + * Get message for HTTP status code + * @return string|null + */ + public static function getMessageForCode( $status ) { + return isset(self::$messages[$status]) ? self::$messages[$status] : null; + } + + /** + * Can this HTTP response have a body? + * @return bool + */ + public function canHaveBody() { + return ( $this->status < 100 || $this->status >= 200 ) && $this->status != 204 && $this->status != 304; + } + + /** + * Send headers for HTTP response + * @return void + */ + protected function sendHeaders() { + //Finalize response + $this->finalize(); + + if ( substr(PHP_SAPI, 0, 3) === 'cgi') { + //Send Status header if running with fastcgi + header('Status: ' . self::getMessageForCode($this->status())); + } else { + //Else send HTTP message + header(sprintf('HTTP/%s %s', $this->httpVersion, self::getMessageForCode($this->status()))); + } + + //Send headers + foreach ( $this->headers() as $name => $value ) { + header("$name: $value"); + } + + //Send cookies + foreach ( $this->getCookieJar()->getResponseCookies() as $name => $cookie ) { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpires(), $cookie->getPath(), $cookie->getDomain(), $cookie->getSecure(), $cookie->getHttpOnly()); + } + + //Flush all output to client + flush(); + } + + /** + * Send HTTP response + * + * This method will set Response headers, set Response cookies, + * and `echo` the Response body to the current output buffer. + * + * @return void + */ + public function send() { + if ( !headers_sent() ) { + $this->sendHeaders(); + } + if ( $this->canHaveBody() && $this->request->isHead() === false ) { + echo $this->body; + } + } + +}
\ No newline at end of file diff --git a/api/Slim/Http/Uri.php b/api/Slim/Http/Uri.php new file mode 100755 index 0000000..dfe3448 --- /dev/null +++ b/api/Slim/Http/Uri.php @@ -0,0 +1,131 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Uri + * + * Parses base uri and application uri from Request. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Http_Uri { + + /** + * @var string "https" or "http" + */ + protected static $scheme; + + /** + * @var string + */ + protected static $baseUri; + + /** + * @var string + */ + protected static $uri; + + /** + * @var string The URI query string, excluding leading "?" + */ + protected static $queryString; + + /** + * Get Base URI without trailing slash + * @param bool $reload Force reparse the base URI? + * @return string + */ + public static function getBaseUri( $reload = false ) { + if ( $reload || is_null(self::$baseUri) ) { + $requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF']; //Full Request URI + $scriptName = $_SERVER['SCRIPT_NAME']; //Script path from docroot + $baseUri = strpos($requestUri, $scriptName) === 0 ? $scriptName : str_replace('\\', '/', dirname($scriptName)); + self::$baseUri = rtrim($baseUri, '/'); + } + return self::$baseUri; + } + + /** + * Get URI with leading slash + * @param bool $reload Force reparse the URI? + * @return string + * @throws RuntimeException If unable if unable to determine URI + */ + public static function getUri( $reload = false ) { + if ( $reload || is_null(self::$uri) ) { + $uri = ''; + if ( !empty($_SERVER['PATH_INFO']) ) { + $uri = $_SERVER['PATH_INFO']; + } else { + if ( isset($_SERVER['REQUEST_URI']) ) { + $uri = parse_url(self::getScheme() . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], PHP_URL_PATH); + } else if ( isset($_SERVER['PHP_SELF']) ) { + $uri = $_SERVER['PHP_SELF']; + } else { + throw new RuntimeException('Unable to detect request URI'); + } + } + if ( self::getBaseUri() !== '' && strpos($uri, self::getBaseUri()) === 0 ) { + $uri = substr($uri, strlen(self::getBaseUri())); + } + self::$uri = '/' . ltrim($uri, '/'); + } + return self::$uri; + } + + /** + * Get URI Scheme + * @param bool $reload For reparse the URL scheme? + * @return string "https" or "http" + */ + public static function getScheme( $reload = false ) { + if ( $reload || is_null(self::$scheme) ) { + self::$scheme = ( empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ) ? 'http' : 'https'; + } + return self::$scheme; + } + + /** + * Get URI Query String + * @param bool $reload For reparse the URL query string? + * @return string + */ + public static function getQueryString( $reload = false ) { + if ( $reload || is_null(self::$queryString) ) { + self::$queryString = $_SERVER['QUERY_STRING']; + } + return self::$queryString; + } + +}
\ No newline at end of file diff --git a/api/Slim/Log.php b/api/Slim/Log.php new file mode 100755 index 0000000..b958890 --- /dev/null +++ b/api/Slim/Log.php @@ -0,0 +1,155 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Log Adapter + * + * This is an adapter for your own custom Logger. This adapter assumes + * your custom Logger provides the following public instance methods: + * + * debug( mixed $object ) + * info( mixed $object ) + * warn( mixed $object ) + * error( mixed $object ) + * fatal( mixed $object ) + * + * This class assumes nothing else about your custom Logger, so you are free + * to use Apache's Log4PHP logger or any other log class that, at the + * very least, implements the five public instance methods shown above. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Log { + + /** + * @var mixed An object that implements expected Logger interface + */ + protected $logger; + + /** + * @var bool Enable logging? + */ + protected $enabled; + + /** + * Constructor + */ + public function __construct() { + $this->enabled = true; + } + + /** + * Enable or disable logging + * @param bool $enabled + * @return void + */ + public function setEnabled( $enabled ) { + if ( $enabled ) { + $this->enabled = true; + } else { + $this->enabled = false; + } + } + + /** + * Is logging enabled? + * @return bool + */ + public function isEnabled() { + return $this->enabled; + } + + /** + * Log debug message + * @param mixed $object + * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + */ + public function debug( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->debug($object) : false; + } + + /** + * Log info message + * @param mixed $object + * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + */ + public function info( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->info($object) : false; + } + + /** + * Log warn message + * @param mixed $object + * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + */ + public function warn( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->warn($object) : false; + } + + /** + * Log error message + * @param mixed $object + * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + */ + public function error( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->error($object) : false; + } + + /** + * Log fatal message + * @param mixed $object + * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + */ + public function fatal( $object ) { + return isset($this->logger) && $this->isEnabled() ? $this->logger->fatal($object) : false; + } + + /** + * Set Logger + * @param mixed $logger + * @return void + */ + public function setLogger( $logger ) { + $this->logger = $logger; + } + + /** + * Get Logger + * @return mixed + */ + public function getLogger() { + return $this->logger; + } + +}
\ No newline at end of file diff --git a/api/Slim/Logger.php b/api/Slim/Logger.php new file mode 100755 index 0000000..b55eaa2 --- /dev/null +++ b/api/Slim/Logger.php @@ -0,0 +1,200 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Logger + * + * A simple Logger that writes to a daily-unique log file in + * a user-specified directory. By default, this class will write log + * messages for all log levels; the log level may be changed to filter + * unwanted log messages from the log file. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Logger { + + /** + * @var array Log levels + */ + protected $levels = array( + 0 => 'FATAL', + 1 => 'ERROR', + 2 => 'WARN', + 3 => 'INFO', + 4 => 'DEBUG' + ); + + /** + * @var string Absolute path to log directory with trailing slash + */ + protected $directory; + + /** + * Constructor + * @param string $directory Absolute or relative path to log directory + * @param int $level The maximum log level reported by this Logger + */ + public function __construct( $directory, $level = 4 ) { + $this->setDirectory($directory); + $this->setLevel($level); + } + + /** + * Set log directory + * @param string $directory Absolute or relative path to log directory + * @return void + */ + public function setDirectory( $directory ) { + $realPath = realpath($directory); + if ( $realPath ) { + $this->directory = rtrim($realPath, '/') . '/'; + } else { + $this->directory = false; + } + } + + /** + * Get log directory + * @return string|false Absolute path to log directory with trailing slash + */ + public function getDirectory() { + return $this->directory; + } + + /** + * Set log level + * @param int The maximum log level reported by this Logger + * @return void + * @throws InvalidArgumentException If level specified is not 0, 1, 2, 3, 4 + */ + public function setLevel( $level ) { + $theLevel = (int)$level; + if ( $theLevel >= 0 && $theLevel <= 4 ) { + $this->level = $theLevel; + } else { + throw new InvalidArgumentException('Invalid Log Level. Must be one of: 0, 1, 2, 3, 4.'); + } + } + + /** + * Get log level + * @return int + */ + public function getLevel() { + return $this->level; + } + + /** + * Log debug data + * @param mixed $data + * @return void + */ + public function debug( $data ) { + $this->log($data, 4); + } + + /** + * Log info data + * @param mixed $data + * @return void + */ + public function info( $data ) { + $this->log($data, 3); + } + + /** + * Log warn data + * @param mixed $data + * @return void + */ + public function warn( $data ) { + $this->log($data, 2); + } + + /** + * Log error data + * @param mixed $data + * @return void + */ + public function error( $data ) { + $this->log($data, 1); + } + + /** + * Log fatal data + * @param mixed $data + * @return void + */ + public function fatal( $data ) { + $this->log($data, 0); + } + + /** + * Get absolute path to current daily log file + * @return string + */ + public function getFile() { + return $this->getDirectory() . strftime('%Y-%m-%d') . '.log'; + } + + /** + * Log data to file + * @param mixed $data + * @param int $level + * @return void + * @throws RuntimeException If log directory not found or not writable + */ + protected function log( $data, $level ) { + $dir = $this->getDirectory(); + if ( $dir == false || !is_dir($dir) ) { + throw new RuntimeException("Log directory '$dir' invalid."); + } + if ( !is_writable($dir) ) { + throw new RuntimeException("Log directory '$dir' not writable."); + } + if ( $level <= $this->getLevel() ) { + $this->write(sprintf("[%s] %s - %s\r\n", $this->levels[$level], date('c'), (string)$data)); + } + } + + /** + * Persist data to log + * @param string Log message + * @return void + */ + protected function write( $data ) { + @file_put_contents($this->getFile(), $data, FILE_APPEND | LOCK_EX); + } + +}
\ No newline at end of file diff --git a/api/Slim/Route.php b/api/Slim/Route.php new file mode 100755 index 0000000..69923d4 --- /dev/null +++ b/api/Slim/Route.php @@ -0,0 +1,398 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Route + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Route { + + /** + * @var string The route pattern (ie. "/books/:id") + */ + protected $pattern; + + /** + * @var mixed The callable associated with this route + */ + protected $callable; + + /** + * @var array Conditions for this route's URL parameters + */ + protected $conditions = array(); + + /** + * @var array Default conditions applied to all Route instances + */ + protected static $defaultConditions = array(); + + /** + * @var string The name of this route (optional) + */ + protected $name; + + /** + * @var array Key-value array of URL parameters + */ + protected $params = array(); + + /** + * @var array HTTP methods supported by this Route + */ + protected $methods = array(); + + /** + * @var Slim_Router The Router to which this Route belongs + */ + protected $router; + + /** + * @var array[Callable] Middleware + */ + protected $middleware = array(); + + /** + * Constructor + * @param string $pattern The URL pattern (ie. "/books/:id") + * @param mixed $callable Anything that returns TRUE for is_callable() + */ + public function __construct( $pattern, $callable ) { + $this->setPattern($pattern); + $this->setCallable($callable); + $this->setConditions(self::getDefaultConditions()); + } + + /** + * Set default route conditions for all instances + * @param array $defaultConditions + * @return void + */ + public static function setDefaultConditions( array $defaultConditions ) { + self::$defaultConditions = $defaultConditions; + } + + /** + * Get default route conditions for all instances + * @return array + */ + public static function getDefaultConditions() { + return self::$defaultConditions; + } + + /** + * Get route pattern + * @return string + */ + public function getPattern() { + return $this->pattern; + } + + /** + * Set route pattern + * @param string $pattern + * @return void + */ + public function setPattern( $pattern ) { + $this->pattern = str_replace(')', ')?', (string)$pattern); + } + + /** + * Get route callable + * @return mixed + */ + public function getCallable() { + return $this->callable; + } + + /** + * Set route callable + * @param mixed $callable + * @return void + */ + public function setCallable($callable) { + $this->callable = $callable; + } + + /** + * Get route conditions + * @return array + */ + public function getConditions() { + return $this->conditions; + } + + /** + * Set route conditions + * @param array $conditions + * @return void + */ + public function setConditions( array $conditions ) { + $this->conditions = $conditions; + } + + /** + * Get route name + * @return string|null + */ + public function getName() { + return $this->name; + } + + /** + * Set route name + * @param string $name + * @return void + */ + public function setName( $name ) { + $this->name = (string)$name; + $this->router->cacheNamedRoute($this->name, $this); + } + + /** + * Get route parameters + * @return array + */ + public function getParams() { + return $this->params; + } + + /** + * Add supported HTTP method(s) + * @return void + */ + public function setHttpMethods() { + $args = func_get_args(); + $this->methods = $args; + } + + /** + * Get supported HTTP methods + * @return array + */ + public function getHttpMethods() { + return $this->methods; + } + + /** + * Append supported HTTP methods + * @return void + */ + public function appendHttpMethods() { + $args = func_get_args(); + $this->methods = array_merge($this->methods, $args); + } + + /** + * Append supported HTTP methods (alias for Route::appendHttpMethods) + * @return Slim_Route + */ + public function via() { + $args = func_get_args(); + $this->methods = array_merge($this->methods, $args); + return $this; + } + + /** + * Detect support for an HTTP method + * @return bool + */ + public function supportsHttpMethod( $method ) { + return in_array($method, $this->methods); + } + + /** + * Get router + * @return Slim_Router + */ + public function getRouter() { + return $this->router; + } + + /** + * Set router + * @param Slim_Router $router + * @return void + */ + public function setRouter( Slim_Router $router ) { + $this->router = $router; + } + + /** + * Get middleware + * @return array[Callable] + */ + public function getMiddleware() { + return $this->middleware; + } + + /** + * Set middleware + * + * This method allows middleware to be assigned to a specific Route. + * If the method argument `is_callable` (including callable arrays!), + * we directly append the argument to `$this->middleware`. Else, we + * assume the argument is an array of callables and merge the array + * with `$this->middleware`. Even if non-callables are included in the + * argument array, we still merge them; we lazily check each item + * against `is_callable` during Route::dispatch(). + * + * @param Callable|array[Callable] + * @return Slim_Route + * @throws InvalidArgumentException If argument is not callable or not an array + */ + public function setMiddleware( $middleware ) { + if ( is_callable($middleware) ) { + $this->middleware[] = $middleware; + } else if ( is_array($middleware) ) { + $this->middleware = array_merge($this->middleware, $middleware); + } else { + throw new InvalidArgumentException('Route middleware must be callable or an array of callables'); + } + return $this; + } + + /** + * Matches URI? + * + * Parse this route's pattern, and then compare it to an HTTP resource URI + * This method was modeled after the techniques demonstrated by Dan Sosedoff at: + * + * http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/ + * + * @param string $resourceUri A Request URI + * @return bool + */ + public function matches( $resourceUri ) { + //Extract URL params + preg_match_all('@:([\w]+)@', $this->pattern, $paramNames, PREG_PATTERN_ORDER); + $paramNames = $paramNames[0]; + + //Convert URL params into regex patterns, construct a regex for this route + $patternAsRegex = preg_replace_callback('@:[\w]+@', array($this, 'convertPatternToRegex'), $this->pattern); + if ( substr($this->pattern, -1) === '/' ) { + $patternAsRegex = $patternAsRegex . '?'; + } + $patternAsRegex = '@^' . $patternAsRegex . '$@'; + + //Cache URL params' names and values if this route matches the current HTTP request + if ( preg_match($patternAsRegex, $resourceUri, $paramValues) ) { + array_shift($paramValues); + foreach ( $paramNames as $index => $value ) { + $val = substr($value, 1); + if ( isset($paramValues[$val]) ) { + $this->params[$val] = urldecode($paramValues[$val]); + } + } + return true; + } else { + return false; + } + } + + /** + * Convert a URL parameter (ie. ":id") into a regular expression + * @param array URL parameters + * @return string Regular expression for URL parameter + */ + protected function convertPatternToRegex( $matches ) { + $key = str_replace(':', '', $matches[0]); + if ( array_key_exists($key, $this->conditions) ) { + return '(?P<' . $key . '>' . $this->conditions[$key] . ')'; + } else { + return '(?P<' . $key . '>[a-zA-Z0-9_\-\.\!\~\*\\\'\(\)\:\@\&\=\$\+,%]+)'; + } + } + + /** + * Set route name + * @param string $name The name of the route + * @return Slim_Route + */ + public function name( $name ) { + $this->setName($name); + return $this; + } + + /** + * Merge route conditions + * @param array $conditions Key-value array of URL parameter conditions + * @return Slim_Route + */ + public function conditions( array $conditions ) { + $this->conditions = array_merge($this->conditions, $conditions); + return $this; + } + + /** + * Dispatch route + * + * This method invokes this route's callable. If middleware is + * registered for this route, each callable middleware is invoked in + * the order specified. + * + * This method is smart about trailing slashes on the route pattern. + * If this route's pattern is defined with a trailing slash, and if the + * current request URI does not have a trailing slash but otherwise + * matches this route's pattern, a Slim_Exception_RequestSlash + * will be thrown triggering an HTTP 301 Permanent Redirect to the same + * URI _with_ a trailing slash. This Exception is caught in the + * `Slim::run` loop. If this route's pattern is defined without a + * trailing slash, and if the current request URI does have a trailing + * slash, this route will not be matched and a 404 Not Found + * response will be sent if no subsequent matching routes are found. + * + * @return bool Was route callable invoked successfully? + * @throws Slim_Exception_RequestSlash + */ + public function dispatch() { + if ( substr($this->pattern, -1) === '/' && substr($this->router->getRequest()->getResourceUri(), -1) !== '/' ) { + throw new Slim_Exception_RequestSlash(); + } + //Invoke middleware + foreach ( $this->middleware as $mw ) { + if ( is_callable($mw) ) { + call_user_func($mw); + } + } + //Invoke callable + if ( is_callable($this->getCallable()) ) { + call_user_func_array($this->callable, array_values($this->params)); + return true; + } + return false; + } + +}
\ No newline at end of file diff --git a/api/Slim/Router.php b/api/Slim/Router.php new file mode 100755 index 0000000..af41eb9 --- /dev/null +++ b/api/Slim/Router.php @@ -0,0 +1,203 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Router + * + * Responsible for registering route paths with associated callables. + * When a Slim application is run, the Router finds a matching Route for + * the current HTTP request, and if a matching route is found, executes + * the Route's associated callable passing it parameters from the Request URI. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_Router implements IteratorAggregate { + + /** + * @var Slim_Http_Request + */ + protected $request; + + /** + * @var array Lookup hash of routes, keyed by Request method + */ + protected $routes; + + /** + * @var array Lookup hash of named routes, keyed by route name + */ + protected $namedRoutes; + + /** + * @var array Array of routes that match the Request method and URL + */ + protected $matchedRoutes; + + /** + * @var mixed Callable to be invoked if no matching routes are found + */ + protected $notFound; + + /** + * @var mixed Callable to be invoked if application error + */ + protected $error; + + /** + * Constructor + * @param Slim_Http_Request $request The HTTP request object + */ + public function __construct( Slim_Http_Request $request ) { + $this->request = $request; + $this->routes = array(); + } + + /** + * Get Iterator + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->getMatchedRoutes()); + } + + /** + * Get Request + * @return Slim_Http_Request + */ + public function getRequest() { + return $this->request; + } + + /** + * Set Request + * @param Slim_Http_Request $req + * @return void + */ + public function setRequest( Slim_Http_Request $req ) { + $this->request = $req; + } + + /** + * Return routes that match the current request + * @return array[Slim_Route] + */ + public function getMatchedRoutes( $reload = false ) { + if ( $reload || is_null($this->matchedRoutes) ) { + $this->matchedRoutes = array(); + foreach ( $this->routes as $route ) { + if ( $route->matches($this->request->getResourceUri()) ) { + $this->matchedRoutes[] = $route; + } + } + } + return $this->matchedRoutes; + } + + /** + * Map a route to a callback function + * @param string $pattern The URL pattern (ie. "/books/:id") + * @param mixed $callable Anything that returns TRUE for is_callable() + * @return Slim_Route + */ + public function map( $pattern, $callable ) { + $route = new Slim_Route($pattern, $callable); + $route->setRouter($this); + $this->routes[] = $route; + return $route; + } + + /** + * Cache named route + * @param string $name The route name + * @param Slim_Route $route The route object + * @throws RuntimeException If a named route already exists with the same name + * @return void + */ + public function cacheNamedRoute( $name, Slim_Route $route ) { + if ( isset($this->namedRoutes[(string)$name]) ) { + throw new RuntimeException('Named route already exists with name: ' . $name); + } + $this->namedRoutes[$name] = $route; + } + + /** + * Get URL for named route + * @param string $name The name of the route + * @param array Associative array of URL parameter names and values + * @throws RuntimeException If named route not found + * @return string The URL for the given route populated with the given parameters + */ + public function urlFor( $name, $params = array() ) { + if ( !isset($this->namedRoutes[(string)$name]) ) { + throw new RuntimeException('Named route not found for name: ' . $name); + } + $pattern = $this->namedRoutes[(string)$name]->getPattern(); + $search = $replace = array(); + foreach ( $params as $key => $value ) { + $search[] = ':' . $key; + $replace[] = $value; + } + $pattern = str_replace($search, $replace, $pattern); + //Remove remnants of unpopulated, trailing optional pattern segments + return preg_replace(array( + '@\(\/?:.+\/??\)\??@', + '@\?|\(|\)@' + ), '', $this->request->getRootUri() . $pattern); + } + + /** + * Register a 404 Not Found callback + * @param mixed $callable Anything that returns TRUE for is_callable() + * @return mixed + */ + public function notFound( $callable = null ) { + if ( is_callable($callable) ) { + $this->notFound = $callable; + } + return $this->notFound; + } + + /** + * Register a 500 Error callback + * @param mixed $callable Anything that returns TRUE for is_callable() + * @return mixed + */ + public function error( $callable = null ) { + if ( is_callable($callable) ) { + $this->error = $callable; + } + return $this->error; + } + +}
\ No newline at end of file diff --git a/api/Slim/Session/Flash.php b/api/Slim/Session/Flash.php new file mode 100755 index 0000000..2f71deb --- /dev/null +++ b/api/Slim/Session/Flash.php @@ -0,0 +1,192 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Flash Messaging + * + * This class enables Flash messaging. Messages are persisted in $_SESSION + * with a user-defined key. + * + * USAGE: + * + * 1. Set Flash message to be shown on the next request + * + * Slim::flash('error', 'The object could not be saved'); + * + * 2. Set Flash message to be shown on the current request + * + * Slim::flashNow('error', 'The object could not be saved'); + * + * 3. Keep old Flash messages for the next request + * + * Slim::flashKeep(); + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.3 + */ +class Slim_Session_Flash implements ArrayAccess { + + /** + * @var string Key used to identify flash information in $_SESSION array + */ + protected $sessionKey = 'flash'; + + /** + * @var array[array] Storage for flash messages + */ + protected $messages = array( + 'prev' => array(), //flash messages from prev request + 'next' => array(), //flash messages for next request + 'now' => array() //flash messages for current request + ); + + /** + * Constructor + * + * Establishes Flash session key and loads existing + * Flash messages from the $_SESSION. + * + * @param string $sessionKey + * @return void + */ + public function __construct( $sessionKey = null ) { + if ( !is_null($sessionKey) ) { + $this->setSessionKey($sessionKey); + } + $this->load(); + } + + /** + * Set the $_SESSION key used to access Flash messages + * @param string $key + * @throws RuntimeException If session key is null + * @return Slim_Session_Flash + */ + public function setSessionKey( $key ) { + if ( is_null($key) ) { + throw new RuntimeException('Session key cannot be null'); + } + $this->sessionKey = (string)$key; + return $this; + } + + /** + * Get the $_SESSION key used to access Flash messages + * @return string + */ + public function getSessionKey() { + return $this->sessionKey; + } + + /** + * Set a Flash message for the current request + * @param string $key + * @param string $value + * @return Slim_Session_Flash + */ + public function now( $key, $value ) { + $this->messages['now'][(string)$key] = $value; + return $this->save(); + } + + /** + * Set a Flash message for the next request + * @param string $key + * @param string $value + * @return Slim_Session_Flash + */ + public function set( $key, $value ) { + $this->messages['next'][(string)$key] = $value; + return $this->save(); + } + + /** + * Get Flash messages intended for the current request's View + * @return array[String] + */ + public function getMessages() { + return array_merge($this->messages['prev'], $this->messages['now']); + } + + /** + * Load Flash messages from $_SESSION + * @return Slim_Session_Flash + */ + public function load() { + $this->messages['prev'] = isset($_SESSION[$this->sessionKey]) ? $_SESSION[$this->sessionKey] : array(); + return $this; + } + + /** + * Transfer Flash messages from the previous request + * so they are available to the next request. + * @return Slim_Session_Flash + */ + public function keep() { + foreach ( $this->messages['prev'] as $key => $val ) { + $this->messages['next'][$key] = $val; + } + return $this->save(); + } + + /** + * Save Flash messages to $_SESSION + * @return Slim_Session_Flash + */ + public function save() { + $_SESSION[$this->sessionKey] = $this->messages['next']; + return $this; + } + + /***** ARRAY ACCESS INTERFACE *****/ + + public function offsetExists( $offset ) { + $messages = $this->getMessages(); + return isset($messages[$offset]); + } + + public function offsetGet( $offset ) { + $messages = $this->getMessages(); + return isset($messages[$offset]) ? $messages[$offset] : null; + } + + public function offsetSet( $offset, $value ) { + $this->now($offset, $value); + } + + public function offsetUnset( $offset ) { + unset($this->messages['prev'][$offset]); + unset($this->messages['now'][$offset]); + } + +}
\ No newline at end of file diff --git a/api/Slim/Session/Handler.php b/api/Slim/Session/Handler.php new file mode 100755 index 0000000..1e76c3f --- /dev/null +++ b/api/Slim/Session/Handler.php @@ -0,0 +1,125 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart + * @link http://www.slimframework.com + * @copyright 2011 Josh Lockhart + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Abstract Session Handler + * + * This abstract class should be extended by each concrete + * session handler. This class defines the contractual class interface + * methods that must be implemented in concrete subclasses. This class + * also provides the final `register` method used by Slim itself to + * actually register the concrete session handler with PHP. + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.3 + */ +abstract class Slim_Session_Handler { + + /** + * @var Slim + */ + protected $app; + + /** + * Register session handler + * + * @return bool + */ + final public function register( Slim $app ) { + $this->app = $app; + return session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'read'), + array($this, 'write'), + array($this, 'destroy'), + array($this, 'gc') + ); + } + + /** + * Open session + * + * @param string $savePath + * @param string $sessionName + * @return mixed + */ + abstract public function open( $savePath, $sessionName ); + + /** + * Close session + * + * @return mixed + */ + abstract public function close(); + + /** + * Read session data with ID + * + * @param string $id The session identifier + * @return string + */ + abstract public function read( $id ); + + /** + * Write session data with ID + * + * The "write" handler is not executed until after the output stream is + * closed. Thus, output from debugging statements in the "write" handler + * will never be seen in the browser. If debugging output is necessary, it + * is suggested that the debug output be written to a file instead. + * + * @param string $id The session identifier + * @param mixed $sessionData The session data + * @return mixed + */ + abstract public function write( $id, $sessionData ); + + /** + * Destroy session with ID + * + * @param string $id The session identifier + * @return mixed + */ + abstract public function destroy( $id ); + + /** + * Session garbage collection + * + * Executed when the PHP session garbage collector is invoked; should + * remove all session data older than the `$maxLifetime`. + * + * @param int $maxLifetime + * @return mixed + */ + abstract public function gc( $maxLifetime ); + +}
\ No newline at end of file diff --git a/api/Slim/Session/Handler/Cookies.php b/api/Slim/Session/Handler/Cookies.php new file mode 100755 index 0000000..2358540 --- /dev/null +++ b/api/Slim/Session/Handler/Cookies.php @@ -0,0 +1,71 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart + * @link http://www.slimframework.com + * @copyright 2011 Josh Lockhart + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Session Cookie Handler + * + * This class is used as an adapter for PHP's $_SESSION handling. + * Session data will be written to and read from signed, encrypted + * cookies. If the current PHP installation does not have the `mcrypt` + * extension, session data will be written to signed but unencrypted + * cookies; however, the session cookies will still be secure and will + * become invalid if manually edited after set by PHP. + * + * @package Slim + * @author Josh Lockhart + * @since Version 1.3 + */ +class Slim_Session_Handler_Cookies extends Slim_Session_Handler { + + public function open( $savePath, $sessionName ) { + return true; + } + + public function close() { + return true; //Not used + } + + public function read( $id ) { + return $this->app->getEncryptedCookie($id); + } + + public function write( $id, $sessionData ) { + $this->app->setEncryptedCookie($id, $sessionData, 0); + } + + public function destroy( $id ) { + $this->app->deleteCookie($id); + } + + public function gc( $maxLifetime ) { + return true; //Not used + } + +}
\ No newline at end of file diff --git a/api/Slim/Slim.php b/api/Slim/Slim.php new file mode 100755 index 0000000..0268a76 --- /dev/null +++ b/api/Slim/Slim.php @@ -0,0 +1,1174 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +//Ensure PHP session IDs only use the characters [a-z0-9] +ini_set('session.hash_bits_per_character', 4); +ini_set('session.hash_function', 0); + +//Slim's Encryted Cookies rely on libmcyrpt and these two constants. +//If libmycrpt is unavailable, we ensure the expected constants +//are available to avoid errors. +if ( !defined('MCRYPT_RIJNDAEL_256') ) { + define('MCRYPT_RIJNDAEL_256', 0); +} +if ( !defined('MCRYPT_MODE_CBC') ) { + define('MCRYPT_MODE_CBC', 0); +} + +//This determines which errors are reported by PHP. By default, all +//errors (including E_STRICT) are reported. +error_reporting(E_ALL | E_STRICT); + +//This tells PHP to auto-load classes using Slim's autoloader; this will +//only auto-load a class file located in the same directory as Slim.php +//whose file name (excluding the final dot and extension) is the same +//as its class name (case-sensitive). For example, "View.php" will be +//loaded when Slim uses the "View" class for the first time. +spl_autoload_register(array('Slim', 'autoload')); + +//PHP 5.3 will complain if you don't set a timezone. If you do not +//specify your own timezone before requiring Slim, this tells PHP to use UTC. +if ( @date_default_timezone_set(date_default_timezone_get()) === false ) { + date_default_timezone_set('UTC'); +} + +/** + * Slim + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim { + + /** + * @var array[Slim] + */ + protected static $apps = array(); + + /** + * @var string + */ + protected $name; + + /** + * @var Slim_Http_Request + */ + protected $request; + + /** + * @var Slim_Http_Response + */ + protected $response; + + /** + * @var Slim_Router + */ + protected $router; + + /** + * @var Slim_View + */ + protected $view; + + /** + * @var Slim_Log + */ + protected $log; + + /** + * @var array Key-value array of application settings + */ + protected $settings; + + /** + * @var string The application mode + */ + protected $mode; + + /** + * @var array Plugin hooks + */ + protected $hooks = array( + 'slim.before' => array(array()), + 'slim.before.router' => array(array()), + 'slim.before.dispatch' => array(array()), + 'slim.after.dispatch' => array(array()), + 'slim.after.router' => array(array()), + 'slim.after' => array(array()) + ); + + /** + * Slim auto-loader + * + * This method lazy-loads class files when a given class if first used. + * Class files must exist in the same directory as this file and be named + * the same as its class definition (excluding the dot and extension). + * + * @return void + */ + public static function autoload( $class ) { + if ( strpos($class, 'Slim') !== 0 ) { + return; + } + $file = dirname(__FILE__) . '/' . str_replace('_', DIRECTORY_SEPARATOR, substr($class,5)) . '.php'; + if ( file_exists($file) ) { + require $file; + } + } + + /***** INITIALIZATION *****/ + + /** + * Constructor + * @param array $userSettings + * @return void + */ + public function __construct( $userSettings = array() ) { + //Merge application settings + $this->settings = array_merge(array( + //Mode + 'mode' => 'development', + //Logging + 'log.enable' => false, + 'log.logger' => null, + 'log.path' => './logs', + 'log.level' => 4, + //Debugging + 'debug' => true, + //View + 'templates.path' => './templates', + 'view' => 'Slim_View', + //Settings for all cookies + 'cookies.lifetime' => '20 minutes', + 'cookies.path' => '/', + 'cookies.domain' => '', + 'cookies.secure' => false, + 'cookies.httponly' => false, + //Settings for encrypted cookies + 'cookies.secret_key' => 'CHANGE_ME', + 'cookies.cipher' => MCRYPT_RIJNDAEL_256, + 'cookies.cipher_mode' => MCRYPT_MODE_CBC, + 'cookies.encrypt' => true, + 'cookies.user_id' => 'DEFAULT', + //Session handler + 'session.handler' => new Slim_Session_Handler_Cookies(), + 'session.flash_key' => 'flash', + //HTTP + 'http.version' => null + ), $userSettings); + + //Determine application mode + $this->getMode(); + + //Setup HTTP request and response handling + $this->request = new Slim_Http_Request(); + $this->response = new Slim_Http_Response($this->request); + $this->response->setCookieJar(new Slim_Http_CookieJar($this->settings['cookies.secret_key'], array( + 'high_confidentiality' => $this->settings['cookies.encrypt'], + 'mcrypt_algorithm' => $this->settings['cookies.cipher'], + 'mcrypt_mode' => $this->settings['cookies.cipher_mode'], + 'enable_ssl' => $this->settings['cookies.secure'] + ))); + $this->response->httpVersion($this->settings['http.version']); + $this->router = new Slim_Router($this->request); + + //Start session if not already started + if ( session_id() === '' ) { + $sessionHandler = $this->config('session.handler'); + if ( $sessionHandler instanceof Slim_Session_Handler ) { + $sessionHandler->register($this); + } + session_cache_limiter(false); + session_start(); + } + + //Setup view with flash messaging + $this->view($this->config('view'))->setData('flash', new Slim_Session_Flash($this->config('session.flash_key'))); + + //Set app name + if ( !isset(self::$apps['default']) ) { + $this->setName('default'); + } + + //Set global Error handler after Slim app instantiated + set_error_handler(array('Slim', 'handleErrors')); + } + + /** + * Get application mode + * @return string + */ + public function getMode() { + if ( !isset($this->mode) ) { + if ( isset($_ENV['SLIM_MODE']) ) { + $this->mode = (string)$_ENV['SLIM_MODE']; + } else { + $envMode = getenv('SLIM_MODE'); + if ( $envMode !== false ) { + $this->mode = $envMode; + } else { + $this->mode = (string)$this->config('mode'); + } + } + } + return $this->mode; + } + + /***** NAMING *****/ + + /** + * Get Slim application with name + * @param string $name The name of the Slim application to fetch + * @return Slim|null + */ + public static function getInstance( $name = 'default' ) { + return isset(self::$apps[(string)$name]) ? self::$apps[(string)$name] : null; + } + + /** + * Set Slim application name + * @param string $name The name of this Slim application + * @return void + */ + public function setName( $name ) { + $this->name = $name; + self::$apps[$name] = $this; + } + + /** + * Get Slim application name + * @return string|null + */ + public function getName() { + return $this->name; + } + + /***** LOGGING *****/ + + /** + * Get application Log (lazy-loaded) + * @return Slim_Log + */ + public function getLog() { + if ( !isset($this->log) ) { + $this->log = new Slim_Log(); + $this->log->setEnabled($this->config('log.enable')); + $logger = $this->config('log.logger'); + if ( $logger ) { + $this->log->setLogger($logger); + } else { + $this->log->setLogger(new Slim_Logger($this->config('log.path'), $this->config('log.level'))); + } + } + return $this->log; + } + + /***** CONFIGURATION *****/ + + /** + * Configure Slim for a given mode + * + * This method will immediately invoke the callable if + * the specified mode matches the current application mode. + * Otherwise, the callable is ignored. This should be called + * only _after_ you initialize your Slim app. + * + * @param string $mode + * @param mixed $callable + * @return void + */ + public function configureMode( $mode, $callable ) { + if ( $mode === $this->getMode() && is_callable($callable) ) { + call_user_func($callable); + } + } + + /** + * Configure Slim Settings + * + * This method defines application settings and acts as a setter and a getter. + * + * If only one argument is specified and that argument is a string, the value + * of the setting identified by the first argument will be returned, or NULL if + * that setting does not exist. + * + * If only one argument is specified and that argument is an associative array, + * the array will be merged into the existing application settings. + * + * If two arguments are provided, the first argument is the name of the setting + * to be created or updated, and the second argument is the setting value. + * + * @param string|array $name If a string, the name of the setting to set or retrieve. Else an associated array of setting names and values + * @param mixed $value If name is a string, the value of the setting identified by $name + * @return mixed The value of a setting if only one argument is a string + */ + public function config( $name, $value = null ) { + if ( func_num_args() === 1 ) { + if ( is_array($name) ) { + $this->settings = array_merge($this->settings, $name); + } else { + return in_array($name, array_keys($this->settings)) ? $this->settings[$name] : null; + } + } else { + $this->settings[$name] = $value; + } + } + + /***** ROUTING *****/ + + /** + * Add GET|POST|PUT|DELETE route + * + * Adds a new route to the router with associated callable. This + * route will only be invoked when the HTTP request's method matches + * this route's method. + * + * ARGUMENTS: + * + * First: string The URL pattern (REQUIRED) + * In-Between: mixed Anything that returns TRUE for `is_callable` (OPTIONAL) + * Last: mixed Anything that returns TRUE for `is_callable` (REQUIRED) + * + * The first argument is required and must always be the + * route pattern (ie. '/books/:id'). + * + * The last argument is required and must always be the callable object + * to be invoked when the route matches an HTTP request. + * + * You may also provide an unlimited number of in-between arguments; + * each interior argument must be callable and will be invoked in the + * order specified before the route's callable is invoked. + * + * USAGE: + * + * Slim::get('/foo'[, middleware, middleware, ...], callable); + * + * @param array (See notes above) + * @return Slim_Route + */ + protected function mapRoute($args) { + $pattern = array_shift($args); + $callable = array_pop($args); + $route = $this->router->map($pattern, $callable); + if ( count($args) > 0 ) { + $route->setMiddleware($args); + } + return $route; + } + + /** + * Add generic route without associated HTTP method + * @see Slim::mapRoute + * @return Slim_Route + */ + public function map() { + $args = func_get_args(); + return $this->mapRoute($args); + } + + /** + * Add GET route + * @see Slim::mapRoute + * @return Slim_Route + */ + public function get() { + $args = func_get_args(); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_GET, Slim_Http_Request::METHOD_HEAD); + } + + /** + * Add POST route + * @see Slim::mapRoute + * @return Slim_Route + */ + public function post() { + $args = func_get_args(); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST); + } + + /** + * Add PUT route + * @see Slim::mapRoute + * @return Slim_Route + */ + public function put() { + $args = func_get_args(); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT); + } + + /** + * Add DELETE route + * @see Slim::mapRoute + * @return Slim_Route + */ + public function delete() { + $args = func_get_args(); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE); + } + + /** + * Add OPTIONS route + * @see Slim::mapRoute + * @return Slim_Route + */ + public function options() { + $args = func_get_args(); + return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_OPTIONS); + } + + /** + * Not Found Handler + * + * This method defines or invokes the application-wide Not Found handler. + * There are two contexts in which this method may be invoked: + * + * 1. When declaring the handler: + * + * If the $callable parameter is not null and is callable, this + * method will register the callable to be invoked when no + * routes match the current HTTP request. It WILL NOT invoke the callable. + * + * 2. When invoking the handler: + * + * If the $callable parameter is null, Slim assumes you want + * to invoke an already-registered handler. If the handler has been + * registered and is callable, it is invoked and sends a 404 HTTP Response + * whose body is the output of the Not Found handler. + * + * @param mixed $callable Anything that returns true for is_callable() + * @return void + */ + public function notFound( $callable = null ) { + if ( !is_null($callable) ) { + $this->router->notFound($callable); + } else { + ob_start(); + $customNotFoundHandler = $this->router->notFound(); + if ( is_callable($customNotFoundHandler) ) { + call_user_func($customNotFoundHandler); + } else { + call_user_func(array($this, 'defaultNotFound')); + } + $this->halt(404, ob_get_clean()); + } + } + + /** + * Error Handler + * + * This method defines or invokes the application-wide Error handler. + * There are two contexts in which this method may be invoked: + * + * 1. When declaring the handler: + * + * If the $argument parameter is callable, this + * method will register the callable to be invoked when an uncaught + * Exception is detected, or when otherwise explicitly invoked. + * The handler WILL NOT be invoked in this context. + * + * 2. When invoking the handler: + * + * If the $argument parameter is not callable, Slim assumes you want + * to invoke an already-registered handler. If the handler has been + * registered and is callable, it is invoked and passed the caught Exception + * as its one and only argument. The error handler's output is captured + * into an output buffer and sent as the body of a 500 HTTP Response. + * + * @param mixed $argument Callable|Exception + * @return void + */ + public function error( $argument = null ) { + if ( is_callable($argument) ) { + //Register error handler + $this->router->error($argument); + } else { + //Invoke error handler + ob_start(); + $customErrorHandler = $this->router->error(); + if ( is_callable($customErrorHandler) ) { + call_user_func_array($customErrorHandler, array($argument)); + } else { + call_user_func_array(array($this, 'defaultError'), array($argument)); + } + $this->halt(500, ob_get_clean()); + } + } + + /***** ACCESSORS *****/ + + /** + * Get the Request object + * @return Slim_Http_Request + */ + public function request() { + return $this->request; + } + + /** + * Get the Response object + * @return Slim_Http_Response + */ + public function response() { + return $this->response; + } + + /** + * Get the Router object + * @return Slim_Router + */ + public function router() { + return $this->router; + } + + /** + * Get and/or set the View + * + * This method declares the View to be used by the Slim application. + * If the argument is a string, Slim will instantiate a new object + * of the same class. If the argument is an instance of View or a subclass + * of View, Slim will use the argument as the View. + * + * If a View already exists and this method is called to create a + * new View, data already set in the existing View will be + * transferred to the new View. + * + * @param string|Slim_View $viewClass The name of a Slim_View class; + * An instance of Slim_View; + * @return Slim_View + */ + public function view( $viewClass = null ) { + if ( !is_null($viewClass) ) { + $existingData = is_null($this->view) ? array() : $this->view->getData(); + if ( $viewClass instanceOf Slim_View ) { + $this->view = $viewClass; + } else { + $this->view = new $viewClass(); + } + $this->view->appendData($existingData); + } + return $this->view; + } + + /***** RENDERING *****/ + + /** + * Render a template + * + * Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR + * callable to render a template whose output is appended to the + * current HTTP response body. How the template is rendered is + * delegated to the current View. + * + * @param string $template The name of the template passed into the View::render method + * @param array $data Associative array of data made available to the View + * @param int $status The HTTP response status code to use (Optional) + * @return void + */ + public function render( $template, $data = array(), $status = null ) { + $templatesPath = $this->config('templates.path'); + //Legacy support + if ( is_null($templatesPath) ) { + $templatesPath = $this->config('templates_dir'); + } + $this->view->setTemplatesDirectory($templatesPath); + if ( !is_null($status) ) { + $this->response->status($status); + } + $this->view->appendData($data); + $this->view->display($template); + } + + /***** HTTP CACHING *****/ + + /** + * Set Last-Modified HTTP Response Header + * + * Set the HTTP 'Last-Modified' header and stop if a conditional + * GET request's `If-Modified-Since` header matches the last modified time + * of the resource. The `time` argument is a UNIX timestamp integer value. + * When the current request includes an 'If-Modified-Since' header that + * matches the specified last modified time, the application will stop + * and send a '304 Not Modified' response to the client. + * + * @param int $time The last modified UNIX timestamp + * @throws SlimException Returns HTTP 304 Not Modified response if resource last modified time matches `If-Modified-Since` header + * @throws InvalidArgumentException If provided timestamp is not an integer + * @return void + */ + public function lastModified( $time ) { + if ( is_integer($time) ) { + $this->response->header('Last-Modified', date(DATE_RFC1123, $time)); + if ( $time === strtotime($this->request->headers('IF_MODIFIED_SINCE')) ) $this->halt(304); + } else { + throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.'); + } + } + + /** + * Set ETag HTTP Response Header + * + * Set the etag header and stop if the conditional GET request matches. + * The `value` argument is a unique identifier for the current resource. + * The `type` argument indicates whether the etag should be used as a strong or + * weak cache validator. + * + * When the current request includes an 'If-None-Match' header with + * a matching etag, execution is immediately stopped. If the request + * method is GET or HEAD, a '304 Not Modified' response is sent. + * + * @param string $value The etag value + * @param string $type The type of etag to create; either "strong" or "weak" + * @throws InvalidArgumentException If provided type is invalid + * @return void + */ + public function etag( $value, $type = 'strong' ) { + + //Ensure type is correct + if ( !in_array($type, array('strong', 'weak')) ) { + throw new InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".'); + } + + //Set etag value + $value = '"' . $value . '"'; + if ( $type === 'weak' ) $value = 'W/'.$value; + $this->response->header('ETag', $value); + + //Check conditional GET + if ( $etagsHeader = $this->request->headers('IF_NONE_MATCH')) { + $etags = preg_split('@\s*,\s*@', $etagsHeader); + if ( in_array($value, $etags) || in_array('*', $etags) ) $this->halt(304); + } + + } + + /***** COOKIES *****/ + + /** + * Set a normal, unencrypted Cookie + * + * @param string $name The cookie name + * @param mixed $value The cookie value + * @param mixed $time The duration of the cookie; + * If integer, should be UNIX timestamp; + * If string, converted to UNIX timestamp with `strtotime`; + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param bool $secure Indicates that the cookie should only be transmitted over a secure + * HTTPS connection to/from the client + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + * @return void + */ + public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) { + $time = is_null($time) ? $this->config('cookies.lifetime') : $time; + $path = is_null($path) ? $this->config('cookies.path') : $path; + $domain = is_null($domain) ? $this->config('cookies.domain') : $domain; + $secure = is_null($secure) ? $this->config('cookies.secure') : $secure; + $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly; + $this->response->getCookieJar()->setClassicCookie($name, $value, $time, $path, $domain, $secure, $httponly); + } + + /** + * Get the value of a Cookie from the current HTTP Request + * + * Return the value of a cookie from the current HTTP request, + * or return NULL if cookie does not exist. Cookies created during + * the current request will not be available until the next request. + * + * @param string $name + * @return string|null + */ + public function getCookie( $name ) { + return $this->request->cookies($name); + } + + /** + * Set an encrypted Cookie + * + * @param string $name The cookie name + * @param mixed $value The cookie value + * @param mixed $time The duration of the cookie; + * If integer, should be UNIX timestamp; + * If string, converted to UNIX timestamp with `strtotime`; + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param bool $secure Indicates that the cookie should only be transmitted over a secure + * HTTPS connection from the client + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + * @return void + */ + public function setEncryptedCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) { + $time = is_null($time) ? $this->config('cookies.lifetime') : $time; + $path = is_null($path) ? $this->config('cookies.path') : $path; + $domain = is_null($domain) ? $this->config('cookies.domain') : $domain; + $secure = is_null($secure) ? $this->config('cookies.secure') : $secure; + $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly; + $userId = $this->config('cookies.user_id'); + $this->response->getCookieJar()->setCookie($name, $value, $userId, $time, $path, $domain, $secure, $httponly); + } + + /** + * Get the value of an encrypted Cookie from the current HTTP request + * + * Return the value of an encrypted cookie from the current HTTP request, + * or return NULL if cookie does not exist. Encrypted cookies created during + * the current request will not be available until the next request. + * + * @param string $name + * @return string|null + */ + public function getEncryptedCookie( $name ) { + $value = $this->response->getCookieJar()->getCookieValue($name); + return ($value === false) ? null : $value; + } + + /** + * Delete a Cookie (for both normal or encrypted Cookies) + * + * Remove a Cookie from the client. This method will overwrite an existing Cookie + * with a new, empty, auto-expiring Cookie. This method's arguments must match + * the original Cookie's respective arguments for the original Cookie to be + * removed. If any of this method's arguments are omitted or set to NULL, the + * default Cookie setting values (set during Slim::init) will be used instead. + * + * @param string $name The cookie name + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param bool $secure Indicates that the cookie should only be transmitted over a secure + * HTTPS connection from the client + * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol + * @return void + */ + public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) { + $path = is_null($path) ? $this->config('cookies.path') : $path; + $domain = is_null($domain) ? $this->config('cookies.domain') : $domain; + $secure = is_null($secure) ? $this->config('cookies.secure') : $secure; + $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly; + $this->response->getCookieJar()->deleteCookie( $name, $path, $domain, $secure, $httponly ); + } + + /***** HELPERS *****/ + + /** + * Get the Slim application's absolute directory path + * + * This method returns the absolute path to the Slim application's + * directory. If the Slim application is installed in a public-accessible + * sub-directory, the sub-directory path will be included. This method + * will always return an absolute path WITH a trailing slash. + * + * @return string + */ + public function root() { + return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/'; + } + + /** + * Stop + * + * Send the current Response as is and stop executing the Slim + * application. The thrown exception will be caught by the Slim + * custom exception handler which exits this script. + * + * @throws Slim_Exception_Stop + * @return void + */ + public function stop() { + $flash = $this->view->getData('flash'); + if ( $flash ) { + $flash->save(); + } + session_write_close(); + $this->response->send(); + throw new Slim_Exception_Stop(); + } + + /** + * Halt + * + * Halt the application and immediately send an HTTP response with a + * specific status code and body. This may be used to send any type of + * response: info, success, redirect, client error, or server error. + * If you need to render a template AND customize the response status, + * you should use Slim::render() instead. + * + * @param int $status The HTTP response status + * @param string $message The HTTP response body + * @return void + */ + public function halt( $status, $message = '') { + if ( ob_get_level() !== 0 ) { + ob_clean(); + } + $this->response->status($status); + $this->response->body($message); + $this->stop(); + } + + /** + * Pass + * + * This method will cause the Router::dispatch method to ignore + * the current route and continue to the next matching route in the + * dispatch loop. If no subsequent mathing routes are found, + * a 404 Not Found response will be sent to the client. + * + * @throws Slim_Exception_Pass + * @return void + */ + public function pass() { + if ( ob_get_level() !== 0 ) { + ob_clean(); + } + throw new Slim_Exception_Pass(); + } + + /** + * Set the HTTP response Content-Type + * @param string $type The Content-Type for the Response (ie. text/html) + * @return void + */ + public function contentType( $type ) { + $this->response->header('Content-Type', $type); + } + + /** + * Set the HTTP response status code + * @param int $status The HTTP response status code + * @return void + */ + public function status( $code ) { + $this->response->status($code); + } + + /** + * Get the URL for a named Route + * @param string $name The route name + * @param array $params Key-value array of URL parameters + * @throws RuntimeException If named route does not exist + * @return string + */ + public function urlFor( $name, $params = array() ) { + return $this->router->urlFor($name, $params); + } + + /** + * Redirect + * + * This method immediately redirects to a new URL. By default, + * this issues a 302 Found response; this is considered the default + * generic redirect response. You may also specify another valid + * 3xx status code if you want. This method will automatically set the + * HTTP Location header for you using the URL parameter and place the + * destination URL into the response body. + * + * @param string $url The destination URL + * @param int $status The HTTP redirect status code (Optional) + * @throws InvalidArgumentException If status parameter is not a valid 3xx status code + * @return void + */ + public function redirect( $url, $status = 302 ) { + if ( $status >= 300 && $status <= 307 ) { + $this->response->header('Location', (string)$url); + $this->halt($status, (string)$url); + } else { + throw new InvalidArgumentException('Slim::redirect only accepts HTTP 300-307 status codes.'); + } + } + + /***** FLASH *****/ + + /** + * Set flash message for subsequent request + * @param string $key + * @param mixed $value + * @return void + */ + public function flash( $key, $value ) { + $this->view->getData('flash')->set($key, $value); + } + + /** + * Set flash message for current request + * @param string $key + * @param mixed $value + * @return void + */ + public function flashNow( $key, $value ) { + $this->view->getData('flash')->now($key, $value); + } + + /** + * Keep flash messages from previous request for subsequent request + * @return void + */ + public function flashKeep() { + $this->view->getData('flash')->keep(); + } + + /***** HOOKS *****/ + + /** + * Assign hook + * @param string $name The hook name + * @param mixed $callable A callable object + * @param int $priority The hook priority; 0 = high, 10 = low + * @return void + */ + public function hook( $name, $callable, $priority = 10 ) { + if ( !isset($this->hooks[$name]) ) { + $this->hooks[$name] = array(array()); + } + if ( is_callable($callable) ) { + $this->hooks[$name][(int)$priority][] = $callable; + } + } + + /** + * Invoke hook + * @param string $name The hook name + * @param mixed $hookArgs (Optional) Argument for hooked functions + * @return mixed + */ + public function applyHook( $name, $hookArg = null ) { + if ( !isset($this->hooks[$name]) ) { + $this->hooks[$name] = array(array()); + } + if( !empty($this->hooks[$name]) ) { + // Sort by priority, low to high, if there's more than one priority + if ( count($this->hooks[$name]) > 1 ) { + ksort($this->hooks[$name]); + } + foreach( $this->hooks[$name] as $priority ) { + if( !empty($priority) ) { + foreach($priority as $callable) { + $hookArg = call_user_func($callable, $hookArg); + } + } + } + return $hookArg; + } + } + + /** + * Get hook listeners + * + * Return an array of registered hooks. If `$name` is a valid + * hook name, only the listeners attached to that hook are returned. + * Else, all listeners are returned as an associative array whose + * keys are hook names and whose values are arrays of listeners. + * + * @param string $name A hook name (Optional) + * @return array|null + */ + public function getHooks( $name = null ) { + if ( !is_null($name) ) { + return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null; + } else { + return $this->hooks; + } + } + + /** + * Clear hook listeners + * + * Clear all listeners for all hooks. If `$name` is + * a valid hook name, only the listeners attached + * to that hook will be cleared. + * + * @param string $name A hook name (Optional) + * @return void + */ + public function clearHooks( $name = null ) { + if ( !is_null($name) && isset($this->hooks[(string)$name]) ) { + $this->hooks[(string)$name] = array(array()); + } else { + foreach( $this->hooks as $key => $value ) { + $this->hooks[$key] = array(array()); + } + } + } + + /***** RUN SLIM *****/ + + /** + * Run the Slim application + * + * This method is the "meat and potatoes" of Slim and should be the last + * method called. This fires up Slim, invokes the Route that matches + * the current request, and returns the response to the client. + * + * This method will invoke the Not Found handler if no matching + * routes are found. + * + * This method will also catch any unexpected Exceptions thrown by this + * application; the Exceptions will be logged to this application's log + * and rethrown to the global Exception handler. + * + * @return void + */ + public function run() { + try { + try { + $this->applyHook('slim.before'); + ob_start(); + $this->applyHook('slim.before.router'); + $dispatched = false; + $httpMethod = $this->request()->getMethod(); + $httpMethodsAllowed = array(); + foreach ( $this->router as $route ) { + if ( $route->supportsHttpMethod($httpMethod) ) { + try { + $this->applyHook('slim.before.dispatch'); + $dispatched = $route->dispatch(); + $this->applyHook('slim.after.dispatch'); + if ( $dispatched ) { + break; + } + } catch ( Slim_Exception_Pass $e ) { + continue; + } + } else { + $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods()); + } + } + if ( !$dispatched ) { + if ( $httpMethodsAllowed ) { + $this->response()->header('Allow', implode(' ', $httpMethodsAllowed)); + $this->halt(405); + } else { + $this->notFound(); + } + } + $this->response()->write(ob_get_clean()); + $this->applyHook('slim.after.router'); + $this->view->getData('flash')->save(); + session_write_close(); + $this->response->send(); + $this->applyHook('slim.after'); + } catch ( Slim_Exception_RequestSlash $e ) { + $this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301); + } catch ( Exception $e ) { + if ( $e instanceof Slim_Exception_Stop ) throw $e; + $this->getLog()->error($e); + if ( $this->config('debug') === true ) { + $this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString())); + } else { + $this->error($e); + } + } + } catch ( Slim_Exception_Stop $e ) { + //Exit application context + } + } + + /***** EXCEPTION AND ERROR HANDLING *****/ + + /** + * Handle errors + * + * This is the global Error handler that will catch reportable Errors + * and convert them into ErrorExceptions that are caught and handled + * by each Slim application. + * + * @param int $errno The numeric type of the Error + * @param string $errstr The error message + * @param string $errfile The absolute path to the affected file + * @param int $errline The line number of the error in the affected file + * @return true + * @throws ErrorException + */ + public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) { + if ( error_reporting() & $errno ) { + throw new ErrorException($errstr, $errno, 0, $errfile, $errline); + } + return true; + } + + /** + * Generate markup for error message + * + * This method accepts details about an error or exception and + * generates HTML markup for the 500 response body that will + * be sent to the client. + * + * @param string $message The error message + * @param string $file The absolute file path to the affected file + * @param int $line The line number in the affected file + * @param string $trace A stack trace of the error + * @return string + */ + protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) { + $body = '<p>The application could not run because of the following error:</p>'; + $body .= "<h2>Details:</h2><strong>Message:</strong> $message<br/>"; + if ( $file !== '' ) $body .= "<strong>File:</strong> $file<br/>"; + if ( $line !== '' ) $body .= "<strong>Line:</strong> $line<br/>"; + if ( $trace !== '' ) $body .= '<h2>Stack Trace:</h2>' . nl2br($trace); + return self::generateTemplateMarkup('Slim Application Error', $body); + } + + /** + * Generate default template markup + * + * This method accepts a title and body content to generate + * an HTML page. This is primarily used to generate the layout markup + * for Error handlers and Not Found handlers. + * + * @param string $title The title of the HTML template + * @param string $body The body content of the HTML template + * @return string + */ + protected static function generateTemplateMarkup( $title, $body ) { + $html = "<html><head><title>$title</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body>"; + $html .= "<h1>$title</h1>"; + $html .= $body; + $html .= '</body></html>'; + return $html; + } + + /** + * Default Not Found handler + * @return void + */ + protected function defaultNotFound() { + echo self::generateTemplateMarkup('404 Page Not Found', '<p>The page you are looking for could not be found. Check the address bar to ensure your URL is spelled correctly. If all else fails, you can visit our home page at the link below.</p><a href="' . $this->request->getRootUri() . '">Visit the Home Page</a>'); + } + + /** + * Default Error handler + * @return void + */ + protected function defaultError() { + echo self::generateTemplateMarkup('Error', '<p>A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>'); + } + +}
\ No newline at end of file diff --git a/api/Slim/View.php b/api/Slim/View.php new file mode 100755 index 0000000..b72472e --- /dev/null +++ b/api/Slim/View.php @@ -0,0 +1,167 @@ +<?php +/** + * Slim - a micro PHP 5 framework + * + * @author Josh Lockhart <info@joshlockhart.com> + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 1.5.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Slim View + * + * The View is responsible for rendering and/or displaying a template. + * It is recommended that you subclass View and re-implement the + * `View::render` method to use a custom templating engine such as + * Smarty, Twig, Mustache, etc. It is important that `View::render` + * `return` the final template output. Do not `echo` the output. + * + * @package Slim + * @author Josh Lockhart <info@joshlockhart.com> + * @since Version 1.0 + */ +class Slim_View { + + /** + * @var array Key-value array of data available to the template + */ + protected $data = array(); + + /** + * @var string Absolute or relative path to the templates directory + */ + protected $templatesDirectory; + + /** + * Constructor + * + * This is empty but may be overridden in a subclass + */ + public function __construct() {} + + /** + * Get data + * @param string $key + * @return array|mixed|null All View data if no $key, value of datum + * if $key, or NULL if $key but datum + * does not exist. + */ + public function getData( $key = null ) { + if ( !is_null($key) ) { + return isset($this->data[$key]) ? $this->data[$key] : null; + } else { + return $this->data; + } + } + + /** + * Set data + * + * This method is overloaded to accept two different method signatures. + * You may use this to set a specific key with a specfic value, + * or you may use this to set all data to a specific array. + * + * USAGE: + * + * View::setData('color', 'red'); + * View::setData(array('color' => 'red', 'number' => 1)); + * + * @param string|array + * @param mixed Optional. Only use if first argument is a string. + * @return void + * @throws InvalidArgumentException If incorrect method signature + */ + public function setData() { + $args = func_get_args(); + if ( count($args) === 1 && is_array($args[0]) ) { + $this->data = $args[0]; + } else if ( count($args) === 2 ) { + $this->data[(string)$args[0]] = $args[1]; + } else { + throw new InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`'); + } + } + + /** + * Append data to existing View data + * @param array $data + * @return void + */ + public function appendData( array $data ) { + $this->data = array_merge($this->data, $data); + } + + /** + * Get templates directory + * @return string|null Path to templates directory without trailing slash + */ + public function getTemplatesDirectory() { + return $this->templatesDirectory; + } + + /** + * Set templates directory + * @param string $dir + * @return void + * @throws RuntimeException If directory is not a directory or does not exist + */ + public function setTemplatesDirectory( $dir ) { + if ( !is_dir($dir) ) { + throw new RuntimeException('Cannot set View templates directory to: ' . $dir . '. Directory does not exist.'); + } + $this->templatesDirectory = rtrim($dir, '/'); + } + + /** + * Display template + * + * This method echoes the rendered template to the current output buffer + * + * @param string $template Path to template file relative to templates directoy + * @return void + */ + public function display( $template ) { + echo $this->render($template); + } + + /** + * Render template + * @param string $template Path to template file relative to templates directory + * @return string Rendered template + * @throws RuntimeException If template does not exist + */ + public function render( $template ) { + extract($this->data); + $templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/'); + if ( !file_exists($templatePath) ) { + throw new RuntimeException('View cannot render template `' . $templatePath . '`. Template does not exist.'); + } + ob_start(); + require $templatePath; + return ob_get_clean(); + } + +}
\ No newline at end of file diff --git a/api/index.php b/api/index.php new file mode 100755 index 0000000..3b4d2f9 --- /dev/null +++ b/api/index.php @@ -0,0 +1,130 @@ +<?php + +require 'Slim/Slim.php'; + +$app = new Slim(); + +$app->get('/wines', 'getWines'); +$app->get('/wines/:id', 'getWine'); +$app->get('/wines/search/:query', 'findByName'); +$app->post('/wines', 'addWine'); +$app->put('/wines/:id', 'updateWine'); +$app->delete('/wines/:id', 'deleteWine'); + +$app->run(); + +function getWines() { + $sql = "select * FROM wine ORDER BY name"; + try { + $db = getConnection(); + $stmt = $db->query($sql); + $wines = $stmt->fetchAll(PDO::FETCH_OBJ); + $db = null; + echo '{"wine": ' . json_encode($wines) . '}'; + } catch(PDOException $e) { + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function getWine($id) { + $sql = "SELECT * FROM wine WHERE id=:id"; + try { + $db = getConnection(); + $stmt = $db->prepare($sql); + $stmt->bindParam("id", $id); + $stmt->execute(); + $wine = $stmt->fetchObject(); + $db = null; + echo json_encode($wine); + } catch(PDOException $e) { + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function addWine() { + error_log('addWine\n', 3, '/var/tmp/php.log'); + $request = Slim::getInstance()->request(); + $wine = json_decode($request->getBody()); + $sql = "INSERT INTO wine (name, grapes, country, region, year, description) VALUES (:name, :grapes, :country, :region, :year, :description)"; + try { + $db = getConnection(); + $stmt = $db->prepare($sql); + $stmt->bindParam("name", $wine->name); + $stmt->bindParam("grapes", $wine->grapes); + $stmt->bindParam("country", $wine->country); + $stmt->bindParam("region", $wine->region); + $stmt->bindParam("year", $wine->year); + $stmt->bindParam("description", $wine->description); + $stmt->execute(); + $wine->id = $db->lastInsertId(); + $db = null; + echo json_encode($wine); + } catch(PDOException $e) { + error_log($e->getMessage(), 3, '/var/tmp/php.log'); + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function updateWine($id) { + $request = Slim::getInstance()->request(); + $body = $request->getBody(); + $wine = json_decode($body); + $sql = "UPDATE wine SET name=:name, grapes=:grapes, country=:country, region=:region, year=:year, description=:description WHERE id=:id"; + try { + $db = getConnection(); + $stmt = $db->prepare($sql); + $stmt->bindParam("name", $wine->name); + $stmt->bindParam("grapes", $wine->grapes); + $stmt->bindParam("country", $wine->country); + $stmt->bindParam("region", $wine->region); + $stmt->bindParam("year", $wine->year); + $stmt->bindParam("description", $wine->description); + $stmt->bindParam("id", $id); + $stmt->execute(); + $db = null; + echo json_encode($wine); + } catch(PDOException $e) { + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function deleteWine($id) { + $sql = "DELETE FROM wine WHERE id=:id"; + try { + $db = getConnection(); + $stmt = $db->prepare($sql); + $stmt->bindParam("id", $id); + $stmt->execute(); + $db = null; + } catch(PDOException $e) { + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function findByName($query) { + $sql = "SELECT * FROM wine WHERE UPPER(name) LIKE :query ORDER BY name"; + try { + $db = getConnection(); + $stmt = $db->prepare($sql); + $query = "%".$query."%"; + $stmt->bindParam("query", $query); + $stmt->execute(); + $wines = $stmt->fetchAll(PDO::FETCH_OBJ); + $db = null; + echo '{"wine": ' . json_encode($wines) . '}'; + } catch(PDOException $e) { + echo '{"error":{"text":'. $e->getMessage() .'}}'; + } +} + +function getConnection() { + $dbhost="127.0.0.1"; + $dbuser="root"; + $dbpass=""; + $dbname="cellar"; + $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $dbh; +} + +?>
\ No newline at end of file diff --git a/cellar.sql b/cellar.sql new file mode 100644 index 0000000..4e00914 --- /dev/null +++ b/cellar.sql @@ -0,0 +1,57 @@ +-- MySQL dump 10.13 Distrib 5.5.15, for osx10.6 (i386) +-- +-- Host: localhost Database: cellar +-- ------------------------------------------------------ +-- Server version 5.5.15 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `wine` +-- + +DROP TABLE IF EXISTS `wine`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wine` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(45) DEFAULT NULL, + `year` varchar(45) DEFAULT NULL, + `grapes` varchar(45) DEFAULT NULL, + `country` varchar(45) DEFAULT NULL, + `region` varchar(45) DEFAULT NULL, + `description` blob, + `picture` varchar(256) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `wine` +-- + +LOCK TABLES `wine` WRITE; +/*!40000 ALTER TABLE `wine` DISABLE KEYS */; +INSERT INTO `wine` VALUES (1,'CHATEAU DE SAINT COSME','2009','Grenache / Syrah','France','Southern Rhone / Gigondas','The aromas of fruit and spice give one a hint of the light drinkability of this lovely wine, which makes an excellent complement to fish dishes.','saint_cosme.jpg'),(2,'LAN RIOJA CRIANZA','2006','Tempranillo','Spain','Rioja','A resurgence of interest in boutique vineyards has opened the door for this excellent foray into the dessert wine market. Light and bouncy, with a hint of black truffle, this wine will not fail to tickle the taste buds.','lan_rioja.jpg'),(3,'MARGERUM SYBARITE','2010','Sauvignon Blanc','USA','California Central Cosat','The cache of a fine Cabernet in ones wine cellar can now be replaced with a childishly playful wine bubbling over with tempting tastes of\nblack cherry and licorice. This is a taste sure to transport you back in time.','margerum.jpg'),(4,'OWEN ROE \"EX UMBRIS\"','2009','Syrah','USA','Washington','A one-two punch of black pepper and jalapeno will send your senses reeling, as the orange essence snaps you back to reality. Don\'t miss\nthis award-winning taste sensation.','ex_umbris.jpg'),(5,'REX HILL','2009','Pinot Noir','USA','Oregon','One cannot doubt that this will be the wine served at the Hollywood award shows, because it has undeniable star power. Be the first to catch\nthe debut that everyone will be talking about tomorrow.','rex_hill.jpg'),(6,'VITICCIO CLASSICO RISERVA','2007','Sangiovese Merlot','Italy','Tuscany','Though soft and rounded in texture, the body of this wine is full and rich and oh-so-appealing. This delivery is even more impressive when one takes note of the tender tannins that leave the taste buds wholly satisfied.','viticcio.jpg'),(7,'CHATEAU LE DOYENNE','2005','Merlot','France','Bordeaux','Though dense and chewy, this wine does not overpower with its finely balanced depth and structure. It is a truly luxurious experience for the\nsenses.','le_doyenne.jpg'),(8,'DOMAINE DU BOUSCAT','2009','Merlot','France','Bordeaux','The light golden color of this wine belies the bright flavor it holds. A true summer wine, it begs for a picnic lunch in a sun-soaked vineyard.','bouscat.jpg'),(9,'BLOCK NINE','2009','Pinot Noir','USA','California','With hints of ginger and spice, this wine makes an excellent complement to light appetizer and dessert fare for a holiday gathering.','block_nine.jpg'),(10,'DOMAINE SERENE','2007','Pinot Noir','USA','Oregon','Though subtle in its complexities, this wine is sure to please a wide range of enthusiasts. Notes of pomegranate will delight as the nutty finish completes the picture of a fine sipping experience.','domaine_serene.jpg'),(11,'BODEGA LURTON','2011','Pinot Gris','Argentina','Mendoza','Solid notes of black currant blended with a light citrus make this wine an easy pour for varied palates.','bodega_lurton.jpg'),(12,'LES MORIZOTTES','2009','Chardonnay','France','Burgundy','Breaking the mold of the classics, this offering will surprise and undoubtedly get tongues wagging with the hints of coffee and tobacco in\nperfect alignment with more traditional notes. Breaking the mold of the classics, this offering will surprise and\nundoubtedly get tongues wagging with the hints of coffee and tobacco in\nperfect alignment with more traditional notes. Sure to please the late-night crowd with the slight jolt of adrenaline it brings.','morizottes.jpg'); +/*!40000 ALTER TABLE `wine` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2011-12-01 9:22:24 diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..c672171 --- /dev/null +++ b/css/styles.css @@ -0,0 +1,87 @@ +* { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 18px; +} + +.header { + padding-top: 5px; +} + +.leftArea { + position: absolute; + left: 10px; + top: 70px; + bottom: 20px; + width: 260px; + border:solid 1px #CCCCCC; + overflow-y: scroll; +} + +.mainArea { + position: absolute; + top: 70px; + bottom: 20px; + left:300px; + overflow-y: scroll; + width:300px; +} + +.rightArea { + position: absolute; + top: 70px; + bottom: 20px; + left:650px; + overflow-y: scroll; + width:270px; +} + +ul { + list-style-type: none; + padding-left: 0px; + margin-top: 0px; +} + +li a { + text-decoration:none; + display: block; + color: #000000; + border-bottom:solid 1px #CCCCCC; + padding: 8px; +} + +li a:hover { + background-color: #4B0A1E; + color: #BA8A92; +} + +input, textarea { + border:1px solid #ccc; + min-height:30px; + outline: none; +} + +.mainArea input { + margin-bottom:15px; + margin-top:5px; + width:280px; +} + +textarea { + margin-bottom:15px; + margin-top:5px; + height: 200px; + width:250px; +} + +label { + display:block; +} + +button { + padding:6px; +} + + +#searchKey { + width:160px; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..47db36c --- /dev/null +++ b/index.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Cellar</title> +<link rel="stylesheet" href="css/styles.css" /> +</head> + +<body> + +<div class="header"> + <input type="text" id="searchKey"/> + <button id="btnSearch">Search</button> + <button id="btnAdd">New Wine</button> +</div> + + +<div class="leftArea"> +<ul id="wineList"></ul> +</div> + +<form id="wineForm"> + +<div class="mainArea"> + +<label>Id:</label> +<input id="wineId" name="id" type="text" disabled /> + +<label>Name:</label> +<input type="text" id="name" name="name" required> + +<label>Grapes:</label> +<input type="text" id="grapes" name="grapes"/> + +<label>Country:</label> +<input type="text" id="country" name="country"/> + +<label>Region:</label> +<input type="text" id="region" name="region"/> + +<label>Year:</label> +<input type="text" id="year" name="year"/> + +<button id="btnSave">Save</button> +<button id="btnDelete">Delete</button> + +</div> + +<div class="rightArea"> + +<img id="pic" height="300"/> + +<label>Notes:</label> +<textarea id="description" name="description"></textarea> +</div> + +</form> + +<script src="js/jquery-1.7.1.min.js"></script> +<script src="js/main.js"></script> + +</body> +</html>
\ No newline at end of file diff --git a/js/jquery-1.7.1.min.js b/js/jquery-1.7.1.min.js new file mode 100644 index 0000000..198b3ff --- /dev/null +++ b/js/jquery-1.7.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test("Â ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..50c822e --- /dev/null +++ b/js/main.js @@ -0,0 +1,186 @@ +// The root URL for the RESTful services +var rootURL = "http://localhost/cellar/api/wines"; + +var currentWine; + +// Retrieve wine list when application starts +findAll(); + +// Nothing to delete in initial application state +$('#btnDelete').hide(); + +// Register listeners +$('#btnSearch').click(function() { + search($('#searchKey').val()); + return false; +}); + +// Trigger search when pressing 'Return' on search key input field +$('#searchKey').keypress(function(e){ + if(e.which == 13) { + search($('#searchKey').val()); + e.preventDefault(); + return false; + } +}); + +$('#btnAdd').click(function() { + newWine(); + return false; +}); + +$('#btnSave').click(function() { + if ($('#wineId').val() == '') + addWine(); + else + updateWine(); + return false; +}); + +$('#btnDelete').click(function() { + deleteWine(); + return false; +}); + +$('#wineList a').live('click', function() { + findById($(this).data('identity')); +}); + +// Replace broken images with generic wine bottle +$("img").error(function(){ + $(this).attr("src", "pics/generic.jpg"); + +}); + +function search(searchKey) { + if (searchKey == '') + findAll(); + else + findByName(searchKey); +} + +function newWine() { + $('#btnDelete').hide(); + currentWine = {}; + renderDetails(currentWine); // Display empty form +} + +function findAll() { + console.log('findAll'); + $.ajax({ + type: 'GET', + url: rootURL, + dataType: "json", // data type of response + success: renderList + }); +} + +function findByName(searchKey) { + console.log('findByName: ' + searchKey); + $.ajax({ + type: 'GET', + url: rootURL + '/search/' + searchKey, + dataType: "json", + success: renderList + }); +} + +function findById(id) { + console.log('findById: ' + id); + $.ajax({ + type: 'GET', + url: rootURL + '/' + id, + dataType: "json", + success: function(data){ + $('#btnDelete').show(); + console.log('findById success: ' + data.name); + currentWine = data; + renderDetails(currentWine); + } + }); +} + +function addWine() { + console.log('addWine'); + $.ajax({ + type: 'POST', + contentType: 'application/json', + url: rootURL, + dataType: "json", + data: formToJSON(), + success: function(data, textStatus, jqXHR){ + alert('Wine created successfully'); + $('#btnDelete').show(); + $('#wineId').val(data.id); + }, + error: function(jqXHR, textStatus, errorThrown){ + alert('addWine error: ' + textStatus); + } + }); +} + +function updateWine() { + console.log('updateWine'); + $.ajax({ + type: 'PUT', + contentType: 'application/json', + url: rootURL + '/' + $('#wineId').val(), + dataType: "json", + data: formToJSON(), + success: function(data, textStatus, jqXHR){ + alert('Wine updated successfully'); + }, + error: function(jqXHR, textStatus, errorThrown){ + alert('updateWine error: ' + textStatus); + } + }); +} + +function deleteWine() { + console.log('deleteWine'); + $.ajax({ + type: 'DELETE', + url: rootURL + '/' + $('#wineId').val(), + success: function(data, textStatus, jqXHR){ + alert('Wine deleted successfully'); + }, + error: function(jqXHR, textStatus, errorThrown){ + alert('deleteWine error'); + } + }); +} + +function renderList(data) { + // JAX-RS serializes an empty list as null, and a 'collection of one' as an object (not an 'array of one') + var list = data == null ? [] : (data.wine instanceof Array ? data.wine : [data.wine]); + + $('#wineList li').remove(); + $.each(list, function(index, wine) { + $('#wineList').append('<li><a href="#" data-identity="' + wine.id + '">'+wine.name+'</a></li>'); + }); +} + +function renderDetails(wine) { + $('#wineId').val(wine.id); + $('#name').val(wine.name); + $('#grapes').val(wine.grapes); + $('#country').val(wine.country); + $('#region').val(wine.region); + $('#year').val(wine.year); + $('#pic').attr('src', 'pics/' + wine.picture); + $('#description').val(wine.description); +} + +// Helper function to serialize all the form fields into a JSON string +function formToJSON() { + return JSON.stringify({ + "id": $('#wineId').val(), + "name": $('#name').val(), + "grapes": $('#grapes').val(), + "country": $('#country').val(), + "region": $('#region').val(), + "year": $('#year').val(), + "picture": currentWine.picture, + "description": $('#description').val() + }); +} diff --git a/pics/block_nine.jpg b/pics/block_nine.jpg Binary files differnew file mode 100644 index 0000000..34710e1 --- /dev/null +++ b/pics/block_nine.jpg diff --git a/pics/bodega_lurton.jpg b/pics/bodega_lurton.jpg Binary files differnew file mode 100644 index 0000000..731f3ac --- /dev/null +++ b/pics/bodega_lurton.jpg diff --git a/pics/bouscat.jpg b/pics/bouscat.jpg Binary files differnew file mode 100644 index 0000000..c24c7ce --- /dev/null +++ b/pics/bouscat.jpg diff --git a/pics/domaine_serene.jpg b/pics/domaine_serene.jpg Binary files differnew file mode 100644 index 0000000..492ee94 --- /dev/null +++ b/pics/domaine_serene.jpg diff --git a/pics/ex_umbris.jpg b/pics/ex_umbris.jpg Binary files differnew file mode 100644 index 0000000..4569197 --- /dev/null +++ b/pics/ex_umbris.jpg diff --git a/pics/generic.jpg b/pics/generic.jpg Binary files differnew file mode 100644 index 0000000..33f2c22 --- /dev/null +++ b/pics/generic.jpg diff --git a/pics/lan_rioja.jpg b/pics/lan_rioja.jpg Binary files differnew file mode 100644 index 0000000..c2bce57 --- /dev/null +++ b/pics/lan_rioja.jpg diff --git a/pics/le_doyenne.jpg b/pics/le_doyenne.jpg Binary files differnew file mode 100644 index 0000000..da5b5b3 --- /dev/null +++ b/pics/le_doyenne.jpg diff --git a/pics/lurton-pinot-gris.jpg b/pics/lurton-pinot-gris.jpg Binary files differnew file mode 100644 index 0000000..dc4f638 --- /dev/null +++ b/pics/lurton-pinot-gris.jpg diff --git a/pics/margerum.jpg b/pics/margerum.jpg Binary files differnew file mode 100644 index 0000000..4f59af8 --- /dev/null +++ b/pics/margerum.jpg diff --git a/pics/morizottes.jpg b/pics/morizottes.jpg Binary files differnew file mode 100644 index 0000000..9b08196 --- /dev/null +++ b/pics/morizottes.jpg diff --git a/pics/rex_hill.jpg b/pics/rex_hill.jpg Binary files differnew file mode 100644 index 0000000..cb3b469 --- /dev/null +++ b/pics/rex_hill.jpg diff --git a/pics/saint_cosme.jpg b/pics/saint_cosme.jpg Binary files differnew file mode 100644 index 0000000..72ae561 --- /dev/null +++ b/pics/saint_cosme.jpg diff --git a/pics/viticcio.jpg b/pics/viticcio.jpg Binary files differnew file mode 100644 index 0000000..f0f405c --- /dev/null +++ b/pics/viticcio.jpg diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d26559a --- /dev/null +++ b/readme.md @@ -0,0 +1,20 @@ +# Wine Cellar Application (PHP Version) # + +The Wine Cellar application is documented [here](http://coenraets.org). + +This application provides an example of: + +1. Building a complete RESTful API in PHP using the Slim framework. +2. Consuming these services using jQuery + +Set Up: + +1. Create a MySQL database name "cellar". +2. Execute cellar.sql to create and populate the "wine" table: + + mysql cellar -uroot < cellar.sql + +3. Deploy the webapp included in this repository. +4. Open api/index.php. In the getConnection() function at the bottom of the page, make sure the connection parameters match your database configuration. +5. Open main.js and make sure the rootURL variable matches your deployment configuration. +6. Access the application in your browser. For example: http://localhost/cellar.
\ No newline at end of file |