diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..5d609ac7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @chriskacerguis diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..34ee3d3c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Please provide either a cleanly formatted code snippet or a link to repo / gist with code that I can use to reproduce: + +```php + public function set_response($data = null, $http_code = null) + { + $this->response($data, $http_code, true); + } +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots / Error Messages** +If applicable, add screenshots and/or error messages to help explain your problem. + +**Environment (please complete the following information):** + - PHP Version: [e.g. 7.2.1] + - CodeIgniter Version [e.g. 4.0.1] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a761a8b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +vendor +.idea \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a482d6ee..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,49 +0,0 @@ -Changelog: -=========== - -### 2.7.0 - -* Added Blacklist IP option -* Added controller based access controls -* Added support for OPTIONS, PATCH, and HEAD (from boh1996) -* Added logging of the time it takes for a request (rtime column in DB) -* Changed DB schemas to use InnoDB, not MyISAM -* Updated Readme to reflect new developer (Chris Kacerguis) - -### 2.6.2 - -* Update CodeIgniter files to 2.1.3 -* Fixed issue #165 - -### 2.6.1 - -* Update CodeIgniter files to 2.1.2 -* Log Table support for IPv6 & NULL parameters -* Abstract out the processes of firing a controller method within _remap() to an separate method -* Moved GET, POST, PUT, and DELETE parsing to separate methods, allowing them to be overridden as needed -* Small bugfix for a PHP 5.3 strlen error -* Fixed some PHP 5.4 warnings -* Fix for bug in Format.php's to_html() which failed to detect if $data was really a multidimensional array. -* Fix for empty node on XML output format, for false = 0, true = 1. - -### 2.6.0 - -* Added loads of PHPDoc comments. -* Response where method doesn't exist is now "HTTP 405 Method Not Allowed", not "HTTP 404 Not Found". -* Compatible with PHP 5.4. -* Added support for gzip compression. -* Fix the apache\_request\_header function with CGI. -* Fixed up correctly .foo extensions to work when get arguments provided. -* Allows method emulation via X-HTTP-Method-Override -* Support for Backbone.emulateHTTP improved. -* Combine both URI segment and GET params instead of using one or the other -* Separate each piece of the WWW-Authenticate header for digest requests with a comma. -* Added IP whitelist option. - -### 2.5 - -* Instead of just seeing item, item, item, the singular version of the basenode will be used if possible. [Example](http://d.pr/RS46). -* Re-factored to use the Format library, which will soon be merged with CodeIgniter. -* Fixed Limit bug (limit of 5 would allow 6 requests). -* Added logging for invalid API key requests. -* Changed serialize to serialized. diff --git a/LICENSE.txt b/LICENSE similarity index 94% rename from LICENSE.txt rename to LICENSE index 77a4a2ef..f9121e51 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2012 - 2014 Phil Sturgeon, Chris Kacerguis +Copyright (c) 2012 - 2015 Phil Sturgeon, Chris Kacerguis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ 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. - diff --git a/README.md b/README.md index ebadd4aa..59776fe9 100644 --- a/README.md +++ b/README.md @@ -1,190 +1,164 @@ -# CodeIgniter Rest Server +# CodeIgniter RestServer -A fully RESTful server implementation for CodeIgniter using one library, one -config file and one controller. +A fully RESTful server implementation for CodeIgniter 3 using one library, one config file and one controller. -## Requirements +> [!IMPORTANT] +> I have published the first "beta" of codeigniter-restserver 4. See the "development" branch. Please be sure to note the system requirments. -1. PHP 5.4 or greater -2. CodeIgniter 3.0+ +## Requirements -_Note: for 1.7.x support download v2.2 from Downloads tab_ +- PHP 7.2 or greater +- CodeIgniter 3.1.11+ ## Installation -Drag and drop the **application/libraries/Format.php** and **application/libraries/REST_Controller.php** files into your application's directories. To use `require_once` it at the top of your controllers to load it into the scope. Additionally, copy the **rest.php** file from **application/config** in your application's configuration directory. - -## Handling Requests - -When your controller extends from `REST_Controller`, the method names will be appended with the HTTP method used to access the request. If you're making an HTTP `GET` call to `/books`, for instance, it would call a `Books#index_get()` method. - -This allows you to implement a RESTful interface easily: - -```php -class Books extends REST_Controller -{ - public function index_get() - { - // Display all books - } - - public function index_post() - { - // Create a new book - } -} -``` - -`REST_Controller` also supports `PUT` and `DELETE` methods, allowing you to support a truly RESTful interface. - - -Accessing parameters is also easy. Simply use the name of the HTTP verb as a method: - -```php -$this->get('blah'); // GET param -$this->post('blah'); // POST param -$this->put('blah'); // PUT param +```sh +composer require chriskacerguis/codeigniter-restserver ``` -The HTTP spec for DELETE requests precludes the use of parameters. For delete requests, you can add items to the URL - -```php -public function index_delete($id) -{ - $this->response(array( - 'returned from delete:' => $id, - )); -} -``` +## Usage -## Content Types +CodeIgniter Rest Server is available on [Packagist](https://packagist.org/packages/chriskacerguis/codeigniter-restserver) (using semantic versioning), and installation via composer is the recommended way to install Codeigniter Rest Server. Just add this line to your `composer.json` file: -`REST_Controller` supports a bunch of different request/response formats, including XML, JSON and serialised PHP. By default, the class will check the URL and look for a format either as an extension or as a separate segment. - -This means your URLs can look like this: -``` -http://example.com/books.json -http://example.com/books?format=json +```json +"chriskacerguis/codeigniter-restserver": "^3.1" ``` -This can be flaky with URI segments, so the recommend approach is using the HTTP `Accept` header: +or run -```bash -$ curl -H "Accept: application/json" http://example.com +```sh +composer require chriskacerguis/codeigniter-restserver ``` -Any responses you make from the class (see [responses](#responses) for more on this) will be serialised in the designated format. - -## Responses +Note that you will need to copy `rest.php` to your `config` directory (e.g. `application/config`) -The class provides a `response()` method that allows you to return data in the user's requested response format. - -Returning any object / array / string / whatever is easy: +Step 1: Add this to your controller (should be before any of your code) ```php -public function index_get() -{ - $this->response($this->db->get('books')->result()); -} +use chriskacerguis\RestServer\RestController; ``` -This will automatically return an `HTTP 200 OK` response. You can specify the status code in the second parameter: +Step 2: Extend your controller ```php -public function index_post() - { - // ...create new book - $this->response($book, 201); // Send an HTTP 201 Created - } +class Example extends RestController ``` -If you don't specify a response code, and the data you respond with `== FALSE` (an empty array or string, for instance), the response code will automatically be set to `404 Not Found`: +## Basic GET example -```php -$this->response(array()); // HTTP 404 Not Found -``` +Here is a basic example. This controller, which should be saved as `Api.php`, can be called in two ways: -## Multilingual Support - -If your application uses language files to support multiple locales, `REST_Controller` will automatically parse the HTTP `Accept-Language` header and provide the language(s) in your actions. This information can be found in the `$this->response->lang` object: +* `http://domain/api/users/` will return the list of all users +* `http://domain/api/users/id/1` will only return information about the user with id = 1 ```php -public function __construct() -{ - parent::__construct(); - - if (is_array($this->response->lang)) - { - $this->load->language('application', $this->response->lang[0]); - } - else - { - $this->load->language('application', $this->response->lang); - } + 0, 'name' => 'John', 'email' => 'john@example.com'], + ['id' => 1, 'name' => 'Jim', 'email' => 'jim@example.com'], + ]; + + $id = $this->get( 'id' ); + + if ( $id === null ) + { + // Check if the users data store contains users + if ( $users ) + { + // Set the response and exit + $this->response( $users, 200 ); + } + else + { + // Set the response and exit + $this->response( [ + 'status' => false, + 'message' => 'No users were found' + ], 404 ); + } + } + else + { + if ( array_key_exists( $id, $users ) ) + { + $this->response( $users[$id], 200 ); + } + else + { + $this->response( [ + 'status' => false, + 'message' => 'No such user found' + ], 404 ); + } + } + } } ``` -## Authentication +## Extending supported formats -This class also provides rudimentary support for HTTP basic authentication and/or the securer HTTP digest access authentication. - -You can enable basic authentication by setting the `$config['rest_auth']` to `'basic'`. The `$config['rest_valid_logins']` directive can then be used to set the usernames and passwords able to log in to your system. The class will automatically send all the correct headers to trigger the authentication dialogue: +If you need to be able to support more formats for replies, you can extend the +`Format` class to add the required `to_...` methods +1. Extend the `RestController` class (in `libraries/MY_REST_Controller.php`) ```php -$config['rest_valid_logins'] = array( 'username' => 'password', 'other_person' => 'secure123' ); -``` - -Enabling digest auth is similarly easy. Configure your desired logins in the config file like above, and set `$config['rest_auth']` to `'digest'`. The class will automatically send out the headers to enable digest auth. +format = new Format(); + } +} ``` -Your localhost IPs (`127.0.0.1` and `0.0.0.0`) are allowed by default. - -## API Keys - -In addition to the authentication methods above, the `REST_Controller` class also supports the use of API keys. Enabling API keys is easy. Turn it on in your **config/rest.php** file: +2. Extend the `Format` class (can be created as a CodeIgniter library in `libraries/Format.php`). +Following is an example to add support for PDF output ```php -$config['rest_enable_keys'] = TRUE; -``` - -You'll need to create a new database table to store and access the keys. `REST_Controller` will automatically assume you have a table that looks like this: - -```sql -CREATE TABLE `keys` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `key` varchar(40) NOT NULL, - `level` int(2) NOT NULL, - `ignore_limits` tinyint(1) NOT NULL DEFAULT '0', - `date_created` int(11) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -``` - -The class will look for an HTTP header with the API key on each request. An invalid or missing API key will result in an `HTTP 403 Forbidden`. +_data; + } + + if (is_array($data) || substr($data, 0, 4) != '%PDF') { + $html = $this->to_html($data); + + // Use your PDF lib of choice. For example mpdf + $mpdf = new \Mpdf\Mpdf(); + $mpdf->WriteHTML($html); + return $mpdf->Output('', 'S'); + } + + return $data; + } +} ``` - -## Other Documentation / Tutorials - -* [NetTuts: Working with RESTful Services in CodeIgniter](http://net.tutsplus.com/tutorials/php/working-with-restful-services-in-codeigniter-2/) - -## Contributions - -This project was originally written by Phil Sturgeon, however his involvment has shifted -as he is no longer using it. As of 11/20/2013 further developement and support will be done by Chris Kacerguis. - -Pull Requests are the best way to fix bugs or add features. I know loads of you use this, so please -contribute if you have improvements to be made and I'll keep releasing versions over time. diff --git a/application/config/rest.php b/application/config/rest.php deleted file mode 100644 index 1e70532c..00000000 --- a/application/config/rest.php +++ /dev/null @@ -1,462 +0,0 @@ -function($username, $password) -| In other cases override the function _perform_library_auth in your controller -| -| For digest authentication the library function should return already stored md5(username:restrealm:password) for that username -| E.g: md5('admin:REST API:1234') = '1e957ebc35631ab22d5bd6526bd14ea2' -| -*/ -$config['auth_library_class'] = ''; -$config['auth_library_function'] = ''; - -/* -|-------------------------------------------------------------------------- -| Override auth types for specific class/method -|-------------------------------------------------------------------------- -| -| Set specific authentication types for methods within a class (controller) -| -| Set as many config entries as needed. Any methods not set will use the default 'rest_auth' config value. -| -| example: -| -| $config['auth_override_class_method']['deals']['view'] = 'none'; -| $config['auth_override_class_method']['deals']['insert'] = 'digest'; -| $config['auth_override_class_method']['accounts']['user'] = 'basic'; -| $config['auth_override_class_method']['dashboard']['*'] = 'none|digest|basic'; -| -| Here 'deals', 'accounts' and 'dashboard' are controller names, 'view', 'insert' and 'user' are methods within. An asterisk may also be used to specify an authentication method for an entire classes methods. Ex: $config['auth_override_class_method']['dashboard']['*'] = 'basic'; (NOTE: leave off the '_get' or '_post' from the end of the method name) -| Acceptable values are; 'none', 'digest' and 'basic'. -| -*/ -// $config['auth_override_class_method']['deals']['view'] = 'none'; -// $config['auth_override_class_method']['deals']['insert'] = 'digest'; -// $config['auth_override_class_method']['accounts']['user'] = 'basic'; -// $config['auth_override_class_method']['dashboard']['*'] = 'basic'; - - -//---Uncomment list line for the wildard unit test -//$config['auth_override_class_method']['wildcard_test_cases']['*'] = 'basic'; -/* -|-------------------------------------------------------------------------- -| REST Login usernames -|-------------------------------------------------------------------------- -| -| Array of usernames and passwords for login, if ldap is configured this is ignored -| -| array('admin' => '1234') -| -*/ -$config['rest_valid_logins'] = array('admin' => '1234'); - -/* -|-------------------------------------------------------------------------- -| Global IP Whitelisting -|-------------------------------------------------------------------------- -| -| Limit connections to your REST server to whitelisted IP addresses. -| -| Usage: -| 1. Set to true *and* select an auth option for extreme security (client's IP -| address must be in whitelist and they must also log in) -| 2. Set to true with auth set to false to allow whitelisted IPs access with no login. -| 3. Set to false here but set 'auth_override_class_method' to 'whitelist' to -| restrict certain methods to IPs in your whitelist -| -*/ -$config['rest_ip_whitelist_enabled'] = false; - -/* -|-------------------------------------------------------------------------- -| REST IP Whitelist -|-------------------------------------------------------------------------- -| -| Limit connections to your REST server to a comma separated -| list of IP addresses -| -| Example: $config['rest_ip_whitelist'] = '123.456.789.0, 987.654.32.1'; -| -| 127.0.0.1 and 0.0.0.0 are allowed by default. -| -*/ -$config['rest_ip_whitelist'] = ''; - -/* -|-------------------------------------------------------------------------- -| Global IP Blacklisting -|-------------------------------------------------------------------------- -| -| Prevent connections to your REST server from blacklisted IP addresses. -| -| Usage: -| 1. Set to true *and* add any IP address to "rest_ip_blacklist" option -| -*/ -$config['rest_ip_blacklist_enabled'] = false; - -/* -|-------------------------------------------------------------------------- -| REST IP Blacklist -|-------------------------------------------------------------------------- -| -| Block connections from these IP addresses. -| -| Example: $config['rest_ip_blacklist'] = '123.456.789.0, 987.654.32.1'; -| -| -*/ -$config['rest_ip_blacklist'] = ''; - -/* -|-------------------------------------------------------------------------- -| REST Database Group -|-------------------------------------------------------------------------- -| -| Connect to a database group for keys, logging, etc. It will only connect -| if you have any of these features enabled. -| -| 'default' -| -*/ -$config['rest_database_group'] = 'default'; - -/* -|-------------------------------------------------------------------------- -| REST API Keys Table Name -|-------------------------------------------------------------------------- -| -| The table name in your database that stores API Keys. -| -| 'keys' -| -*/ -$config['rest_keys_table'] = 'keys'; - -/* -|-------------------------------------------------------------------------- -| REST Enable Keys -|-------------------------------------------------------------------------- -| -| When set to true REST_Controller will look for a key and match it to the DB. -| If no key is provided, the request will return an error. -| -| FALSE - - CREATE TABLE `keys` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `key` varchar(40) NOT NULL, - `level` int(2) NOT NULL, - `ignore_limits` tinyint(1) NOT NULL DEFAULT '0', - `is_private_key` tinyint(1) NOT NULL DEFAULT '0', - `ip_addresses` TEXT NULL DEFAULT NULL, - `date_created` int(11) NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -| -*/ -$config['rest_enable_keys'] = FALSE; - -/* -|-------------------------------------------------------------------------- -| REST Table Key Column Name -|-------------------------------------------------------------------------- -| -| If you are not using the default table schema as shown above, what is the -| name of the db column that holds the api key value? -| -*/ -$config['rest_key_column'] = 'key'; - -/* -|-------------------------------------------------------------------------- -| REST Key Length -|-------------------------------------------------------------------------- -| -| How long should created keys be? Double check this in your db schema. -| -| Default: 32 -| Max: 40 -| -*/ -$config['rest_key_length'] = 40; - -/* -|-------------------------------------------------------------------------- -| REST API Key Variable -|-------------------------------------------------------------------------- -| -| Which variable will provide us the API Key -| -| Default: X-API-KEY -| -*/ -$config['rest_key_name'] = 'X-API-KEY'; - -/* -|-------------------------------------------------------------------------- -| REST API Logs Table Name -|-------------------------------------------------------------------------- -| -| The table name in your database that stores logs. -| -| 'logs' -| -*/ -$config['rest_logs_table'] = 'logs'; - -/* -|-------------------------------------------------------------------------- -| REST Enable Logging -|-------------------------------------------------------------------------- -| -| When set to true REST_Controller will log actions based on key, date, -| time and IP address. This is a general rule that can be overridden in the -| $this->method array in each controller. -| -| FALSE -| - CREATE TABLE `logs` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uri` varchar(255) NOT NULL, - `method` varchar(6) NOT NULL, - `params` text DEFAULT NULL, - `api_key` varchar(40) NOT NULL, - `ip_address` varchar(45) NOT NULL, - `time` int(11) NOT NULL, - `rtime` float DEFAULT NULL, - `authorized` tinyint(1) NOT NULL, - `response_code` smallint(3) NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -| -*/ -$config['rest_enable_logging'] = FALSE; - - -/* -|-------------------------------------------------------------------------- -| REST API Access Table Name -|-------------------------------------------------------------------------- -| -| The table name in your database that stores the access controls. -| -| 'access' -| -*/ -$config['rest_access_table'] = 'access'; - -/* -|-------------------------------------------------------------------------- -| REST Method Access Control -|-------------------------------------------------------------------------- -| -| When set to true REST_Controller will check the access table to see if -| the API KEY can access that controller. rest_enable_keys *must* be enabled -| to use this. -| -| FALSE -| -CREATE TABLE `access` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `key` varchar(40) NOT NULL DEFAULT '', - `controller` varchar(50) NOT NULL DEFAULT '', - `date_created` datetime DEFAULT NULL, - `date_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -| -*/ -$config['rest_enable_access'] = FALSE; - - -/* -|-------------------------------------------------------------------------- -| REST API Param Log Format -|-------------------------------------------------------------------------- -| -| When set to true API log params will be stored in the database as JSON, -| when false they will be php serialized. -| -*/ -$config['rest_logs_json_params'] = FALSE; - -/* -|-------------------------------------------------------------------------- -| REST API Limits Table Name -|-------------------------------------------------------------------------- -| -| The table name in your database that stores limits. -| -| 'limits' -| -*/ -$config['rest_limits_table'] = 'limits'; - -/* -|-------------------------------------------------------------------------- -| REST Enable Limits -|-------------------------------------------------------------------------- -| -| When set to true REST_Controller will count the number of uses of each method -| by an API key each hour. This is a general rule that can be overridden in the -| $this->method array in each controller. -| -| FALSE -| - CREATE TABLE `limits` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `uri` varchar(255) NOT NULL, - `count` int(10) NOT NULL, - `hour_started` int(11) NOT NULL, - `api_key` varchar(40) NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -| -| To specify limits, within your Controller __construct() method add per-method -| limits with: - - $this->method['METHOD_NAME']['limit'] = [NUM_REQUESTS_PER_HOUR]; - -| See application/controllers/api/example.php for examples. -*/ -$config['rest_enable_limits'] = FALSE; - -/* -|-------------------------------------------------------------------------- -| REST Ignore HTTP Accept -|-------------------------------------------------------------------------- -| -| Set to TRUE to ignore the HTTP Accept and speed up each request a little. -| Only do this if you are using the $this->rest_format or /format/xml in URLs -| -| FALSE -| -*/ -$config['rest_ignore_http_accept'] = FALSE; - -/* -|-------------------------------------------------------------------------- -| REST AJAX Only -|-------------------------------------------------------------------------- -| -| Set to TRUE to only allow AJAX requests. If TRUE and the request is not -| coming from AJAX, a 505 response with the error message "Only AJAX -| requests are accepted." will be returned. This is good for production -| environments. Set to FALSE to also accept HTTP requests. -| -| FALSE -| -*/ -$config['rest_ajax_only'] = FALSE; - -/* End of file config.php */ -/* Location: ./system/application/config/rest.php */ diff --git a/application/controllers/api/example.php b/application/controllers/api/example.php deleted file mode 100644 index c483ca83..00000000 --- a/application/controllers/api/example.php +++ /dev/null @@ -1,108 +0,0 @@ -methods['user_get']['limit'] = 500; //500 requests per hour per user/key - $this->methods['user_post']['limit'] = 100; //100 requests per hour per user/key - $this->methods['user_delete']['limit'] = 50; //50 requests per hour per user/key - } - - function user_get() - { - if(!$this->get('id')) - { - $this->response(NULL, 400); - } - - // $user = $this->some_model->getSomething( $this->get('id') ); - $users = array( - 1 => array('id' => 1, 'name' => 'Some Guy', 'email' => 'example1@example.com', 'fact' => 'Loves swimming'), - 2 => array('id' => 2, 'name' => 'Person Face', 'email' => 'example2@example.com', 'fact' => 'Has a huge face'), - 3 => array('id' => 3, 'name' => 'Scotty', 'email' => 'example3@example.com', 'fact' => 'Is a Scott!', array('hobbies' => array('fartings', 'bikes'))), - ); - - $user = @$users[$this->get('id')]; - - if($user) - { - $this->response($user, 200); // 200 being the HTTP response code - } - - else - { - $this->response(array('error' => 'User could not be found'), 404); - } - } - - function user_post() - { - //$this->some_model->updateUser( $this->get('id') ); - $message = array('id' => $this->get('id'), 'name' => $this->post('name'), 'email' => $this->post('email'), 'message' => 'ADDED!'); - - $this->response($message, 200); // 200 being the HTTP response code - } - - function user_delete() - { - //$this->some_model->deletesomething( $this->get('id') ); - $message = array('id' => $this->get('id'), 'message' => 'DELETED!'); - - $this->response($message, 200); // 200 being the HTTP response code - } - - function users_get() - { - //$users = $this->some_model->getSomething( $this->get('limit') ); - $users = array( - array('id' => 1, 'name' => 'Some Guy', 'email' => 'example1@example.com'), - array('id' => 2, 'name' => 'Person Face', 'email' => 'example2@example.com'), - 3 => array('id' => 3, 'name' => 'Scotty', 'email' => 'example3@example.com', 'fact' => array('hobbies' => array('fartings', 'bikes'))), - ); - - if($users) - { - $this->response($users, 200); // 200 being the HTTP response code - } - - else - { - $this->response(array('error' => 'Couldn\'t find any users!'), 404); - } - } - - - public function send_post() - { - var_dump($this->request->body); - } - - - public function send_put() - { - var_dump($this->put('foo')); - } -} \ No newline at end of file diff --git a/application/controllers/api/key.php b/application/controllers/api/key.php deleted file mode 100644 index 33d8049a..00000000 --- a/application/controllers/api/key.php +++ /dev/null @@ -1,251 +0,0 @@ - array('level' => 10, 'limit' => 10), - 'index_delete' => array('level' => 10), - 'level_post' => array('level' => 10), - 'regenerate_post' => array('level' => 10), - ); - - /** - * Key Create - * - * Insert a key into the database. - * - * @access public - * @return void - */ - public function index_put() - { - // Build a new key - $key = self::_generate_key(); - - // If no key level provided, give them a rubbish one - $level = $this->put('level') ? $this->put('level') : 1; - $ignore_limits = $this->put('ignore_limits') ? $this->put('ignore_limits') : 1; - - // Insert the new key - if (self::_insert_key($key, array('level' => $level, 'ignore_limits' => $ignore_limits))) - { - $this->response(array('status' => 1, 'key' => $key), 201); // 201 = Created - } - - else - { - $this->response(array('status' => 0, 'error' => 'Could not save the key.'), 500); // 500 = Internal Server Error - } - } - - // -------------------------------------------------------------------- - - /** - * Key Delete - * - * Remove a key from the database to stop it working. - * - * @access public - * @return void - */ - public function index_delete() - { - $key = $this->delete('key'); - - // Does this key even exist? - if ( ! self::_key_exists($key)) - { - // NOOOOOOOOO! - $this->response(array('status' => 0, 'error' => 'Invalid API Key.'), 400); - } - - // Kill it - self::_delete_key($key); - - // Tell em we killed it - $this->response(array('status' => 1, 'success' => 'API Key was deleted.'), 200); - } - - // -------------------------------------------------------------------- - - /** - * Update Key - * - * Change the level - * - * @access public - * @return void - */ - public function level_post() - { - $key = $this->post('key'); - $new_level = $this->post('level'); - - // Does this key even exist? - if ( ! self::_key_exists($key)) - { - // NOOOOOOOOO! - $this->response(array('error' => 'Invalid API Key.'), 400); - } - - // Update the key level - if (self::_update_key($key, array('level' => $new_level))) - { - $this->response(array('status' => 1, 'success' => 'API Key was updated.'), 200); // 200 = OK - } - - else - { - $this->response(array('status' => 0, 'error' => 'Could not update the key level.'), 500); // 500 = Internal Server Error - } - } - - // -------------------------------------------------------------------- - - /** - * Update Key - * - * Change the level - * - * @access public - * @return void - */ - public function suspend_post() - { - $key = $this->post('key'); - - // Does this key even exist? - if ( ! self::_key_exists($key)) - { - // NOOOOOOOOO! - $this->response(array('error' => 'Invalid API Key.'), 400); - } - - // Update the key level - if (self::_update_key($key, array('level' => 0))) - { - $this->response(array('status' => 1, 'success' => 'Key was suspended.'), 200); // 200 = OK - } - - else - { - $this->response(array('status' => 0, 'error' => 'Could not suspend the user.'), 500); // 500 = Internal Server Error - } - } - - // -------------------------------------------------------------------- - - /** - * Regenerate Key - * - * Remove a key from the database to stop it working. - * - * @access public - * @return void - */ - public function regenerate_post() - { - $old_key = $this->post('key'); - $key_details = self::_get_key($old_key); - - // The key wasnt found - if ( ! $key_details) - { - // NOOOOOOOOO! - $this->response(array('status' => 0, 'error' => 'Invalid API Key.'), 400); - } - - // Build a new key - $new_key = self::_generate_key(); - - // Insert the new key - if (self::_insert_key($new_key, array('level' => $key_details->level, 'ignore_limits' => $key_details->ignore_limits))) - { - // Suspend old key - self::_update_key($old_key, array('level' => 0)); - - $this->response(array('status' => 1, 'key' => $new_key), 201); // 201 = Created - } - - else - { - $this->response(array('status' => 0, 'error' => 'Could not save the key.'), 500); // 500 = Internal Server Error - } - } - - // -------------------------------------------------------------------- - - /* Helper Methods */ - - private function _generate_key() - { - //$this->load->helper('security'); - - do - { - $salt = do_hash(time().mt_rand()); - $new_key = substr($salt, 0, config_item('rest_key_length')); - } - - // Already in the DB? Fail. Try again - while (self::_key_exists($new_key)); - - return $new_key; - } - - // -------------------------------------------------------------------- - - /* Private Data Methods */ - - private function _get_key($key) - { - return $this->db->where(config_item('rest_key_column'), $key)->get(config_item('rest_keys_table'))->row(); - } - - // -------------------------------------------------------------------- - - private function _key_exists($key) - { - return $this->db->where(config_item('rest_key_column'), $key)->count_all_results(config_item('rest_keys_table')) > 0; - } - - // -------------------------------------------------------------------- - - private function _insert_key($key, $data) - { - - $data[config_item('rest_key_column')] = $key; - $data['date_created'] = function_exists('now') ? now() : time(); - - return $this->db->set($data)->insert(config_item('rest_keys_table')); - } - - // -------------------------------------------------------------------- - - private function _update_key($key, $data) - { - return $this->db->where(config_item('rest_key_column'), $key)->update(config_item('rest_keys_table'), $data); - } - - // -------------------------------------------------------------------- - - private function _delete_key($key) - { - return $this->db->where(config_item('rest_key_column'), $key)->delete(config_item('rest_keys_table')); - } -} diff --git a/application/libraries/Format.php b/application/libraries/Format.php deleted file mode 100644 index 13df0cf0..00000000 --- a/application/libraries/Format.php +++ /dev/null @@ -1,380 +0,0 @@ -format->factory(array('foo' => 'bar'))->to_xml(); - * - * @access public - * @param $data, mixed general date to be converted - * @param $from_type, string data format the file was provided in - * @return Factory - */ - public function factory($data, $from_type = NULL) - { - // Stupid stuff to emulate the "new static()" stuff in this libraries PHP 5.3 equivalent - $class = __CLASS__; - return new $class($data, $from_type); - } - - /** - * Do not use this directly, call factory() - * - * @access public - * @param $data, bool - * @param $from_type, bool - */ - public function __construct($data = NULL, $from_type = NULL) - { - get_instance()->load->helper('inflector'); - - // If the provided data is already formatted we should probably convert it to an array - if ($from_type !== NULL) - { - if (method_exists($this, '_from_' . $from_type)) - { - $data = call_user_func([$this, '_from_' . $from_type], $data); - } - - else - { - throw new Exception('Format class does not support conversion from "' . $from_type . '".'); - } - } - - $this->_data = $data; - } - - // FORMATING OUTPUT --------------------------------------------------------- - - /** - * to_array - * - * @access public - * @param $data - */ - public function to_array($data = NULL) - { - // If not just NULL, but nothing is provided - if ($data === NULL && ! func_num_args()) - { - $data = $this->_data; - } - - $array = []; - - foreach ((array) $data as $key => $value) - { - if (is_object($value) || is_array($value)) - { - $array[$key] = $this->to_array($value); - } - - else - { - $array[$key] = $value; - } - } - - return $array; - } - - /** - * Format XML for output - * - * @access public - * @param $data - * @param $structure - * @param $basenode - */ - public function to_xml($data = NULL, $structure = NULL, $basenode = 'xml') - { - if ($data === NULL && ! func_num_args()) - { - $data = $this->_data; - } - - // turn off compatibility mode as simple xml throws a wobbly if you don't. - if (ini_get('zend.ze1_compatibility_mode') == 1) - { - ini_set('zend.ze1_compatibility_mode', 0); - } - - if ($structure === NULL) - { - $structure = simplexml_load_string("<$basenode />"); - } - - // Force it to be something useful - if ( ! is_array($data) && ! is_object($data)) - { - $data = (array) $data; - } - - foreach ($data as $key => $value) - { - - //change false/true to 0/1 - if(is_bool($value)) - { - $value = (int) $value; - } - - // no numeric keys in our xml please! - if (is_numeric($key)) - { - // make string key... - $key = (singular($basenode) != $basenode) ? singular($basenode) : 'item'; - } - - // replace anything not alpha numeric - $key = preg_replace('/[^a-z_\-0-9]/i', '', $key); - - if ($key === '_attributes' && (is_array($value) || is_object($value))) - { - $attributes = $value; - if (is_object($attributes)) $attributes = get_object_vars($attributes); - - foreach ($attributes as $attributeName => $attributeValue) - { - $structure->addAttribute($attributeName, $attributeValue); - } - } - // if there is another array found recursively call this function - elseif (is_array($value) || is_object($value)) - { - $node = $structure->addChild($key); - - // recursive call. - $this->to_xml($value, $node, $key); - } - else - { - // add single node. - $value = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, "UTF-8"); - - $structure->addChild($key, $value); - } - } - - return $structure->asXML(); - } - - /** - * Format HTML for output - * - * @access public - */ - public function to_html() - { - $data = (array)$this->_data; - - // Multi-dimensional array - if (isset($data[0]) && is_array($data[0])) - { - $headings = array_keys($data[0]); - } - - // Single array - else - { - $headings = array_keys($data); - $data = [$data]; - } - - $ci = get_instance(); - $ci->load->library('table'); - - $ci->table->set_heading($headings); - - foreach ($data as &$row) - { - $ci->table->add_row($row); - } - - return $ci->table->generate(); - } - - /** - * Format CSV for output - * - * @access public - */ - public function to_csv() - { - $data = (array)$this->_data; - - // Multi-dimensional array - if (isset($data[0]) && is_array($data[0])) - { - $headings = array_keys($data[0]); - } - - // Single array - else - { - $headings = array_keys($data); - $data = [$data]; - } - - $output = '"'.implode('","', $headings).'"'.PHP_EOL; - foreach ($data as &$row) - { - $row = str_replace('"', '""', $row); // Escape dbl quotes per RFC 4180 - $output .= '"'.implode('","', $row).'"'.PHP_EOL; - } - - return $output; - } - - /** - * Encode as JSON - * - * @access public - */ - public function to_json() - { - $callback = isset($_GET['callback']) ? $_GET['callback'] : ''; - if ($callback === '') - { - return json_encode($this->_data); - - /* Had to take out this code, it doesn't work on Objects. - $str = $this->_data; - array_walk_recursive($str, function(&$item, $key) - { - if(!mb_detect_encoding($item, 'utf-8', true)) - { - $item = utf8_encode($item); - } - }); - - return json_encode($str); - */ - } - - // we only honour jsonp callback which are valid javascript identifiers - elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback)) - { - // this is a jsonp request, the content-type must be updated to be text/javascript - header("Content-Type: application/javascript"); - return $callback . "(" . json_encode($this->_data) . ");"; - } - else - { - // we have an invalid jsonp callback identifier, we'll return plain json with a warning field - $this->_data['warning'] = "invalid jsonp callback provided: ".$callback; - return json_encode($this->_data); - } - } - - /** - * Encode as Serialized array - * - * @access public - */ - public function to_serialized() - { - return serialize($this->_data); - } - - /** - * Output as a string representing the PHP structure - * - * @access public - */ - public function to_php() - { - return var_export($this->_data, TRUE); - } - - /** - * Format XML for output - * - * @access protected - * @param $string - */ - protected function _from_xml($string) - { - return $string ? (array) simplexml_load_string($string, 'SimpleXMLElement', LIBXML_NOCDATA) : []; - } - - /** - * Format CSV for output - * This function is DODGY! Not perfect CSV support but works with my REST_Controller - * - * @access protected - * @param $string - */ - protected function _from_csv($string) - { - $data = []; - - // Splits - $rows = explode("\n", trim($string)); - $headings = explode(',', array_shift($rows)); - foreach ($rows as $row) - { - // The substr removes " from start and end - $data_fields = explode('","', trim(substr($row, 1, -1))); - - if (count($data_fields) == count($headings)) - { - $data[] = array_combine($headings, $data_fields); - } - } - - return $data; - } - - /** - * Encode as JSON - * - * @access private - * @param string - */ - private function _from_json($string) - { - return json_decode(trim($string)); - } - - /** - * Encode as Serialized array - * - * @access private - * @param $string - * - */ - private function _from_serialize($string) - { - return unserialize(trim($string)); - } - - - /** - * If you provide text/plain value on the Content-type header on a request - * just return the string - * - * @access private - * @param $string - */ - private function _from_php($string) - { - return trim($string); - } - -} - -/* End of file format.php */ diff --git a/application/libraries/REST_Controller.php b/application/libraries/REST_Controller.php deleted file mode 100644 index 9b3e945b..00000000 --- a/application/libraries/REST_Controller.php +++ /dev/null @@ -1,1623 +0,0 @@ - 'application/xml', - 'json' => 'application/json', - 'jsonp' => 'application/javascript', - 'serialized' => 'application/vnd.php.serialized', - 'php' => 'text/plain', - 'html' => 'text/html', - 'csv' => 'application/csv' - ]; - - /** - * Information about the current API user - * - * @var object - */ - protected $_apiuser; - - /** - * Developers can extend this class and add a check in here. - */ - protected function early_checks() - { - - } - - /** - * Constructor function - * @todo Document more please. - * @access public - */ - public function __construct($config = 'rest') - { - parent::__construct(); - - // Start the timer for how long the request takes - $this->_start_rtime = microtime(TRUE); - - // Lets grab the config and get ready to party - $this->load->config($config); - - // This library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter itself - $this->load->library('format'); - - // init objects - $this->response = new stdClass(); - $this->rest = new stdClass(); - - $this->_zlib_oc = @ini_get('zlib.output_compression'); - - // let's learn about the request - $this->request = new stdClass(); - - // Check to see if this IP is Blacklisted - if ($this->config->item('rest_ip_blacklist_enabled')) { - $this->_check_blacklist_auth(); - } - - // Is it over SSL? - $this->request->ssl = $this->_detect_ssl(); - - // How is this request being made? POST, DELETE, GET, PUT? - $this->request->method = $this->_detect_method(); - - // Create argument container, if nonexistent - if (!isset($this->{'_'.$this->request->method.'_args'})) { - $this->{'_'.$this->request->method.'_args'} = []; - } - - // Set up our GET variables - $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc()); - - // Try to find a format for the request (means we have a request body) - $this->request->format = $this->_detect_input_format(); - - // Some Methods cant have a body - $this->request->body = NULL; - - $this->{'_parse_' . $this->request->method}(); - - // Now we know all about our request, let's try and parse the body if it exists - if ($this->request->format && $this->request->body) { - $this->request->body = $this->format->factory($this->request->body, $this->request->format)->to_array(); - // Assign payload arguments to proper method container - $this->{'_'.$this->request->method.'_args'} = $this->request->body; - } - - // Merge both for one mega-args variable - $this->_args = array_merge($this->_get_args, - $this->_options_args, - $this->_patch_args, - $this->_head_args , - $this->_put_args, - $this->_post_args, - $this->_delete_args, - $this->{'_'.$this->request->method.'_args'} - ); - - // Which format should the data be returned in? - $this->response = new stdClass(); - $this->response->format = $this->_detect_output_format(); - - // Which format should the data be returned in? - $this->response->lang = $this->_detect_lang(); - - // Developers can extend this class and add a check in here - $this->early_checks(); - - $this->rest = new StdClass(); - - // Load DB if its enabled - if (config_item('rest_database_group') && (config_item('rest_enable_keys') || config_item('rest_enable_logging'))) { - $this->rest->db = $this->load->database(config_item('rest_database_group'), TRUE); - } - - // Use whatever database is in use (isset returns FALSE) - elseif (property_exists($this, "db")) { - $this->rest->db = $this->db; - } - - // Check if there is a specific auth type for the current class/method - // _auth_override_check could exit so we need $this->rest->db initialized before - $this->auth_override = $this->_auth_override_check(); - - // Checking for keys? GET TO WorK! - // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none' - if (config_item('rest_enable_keys') && $this->auth_override !== TRUE) { - $this->_allow = $this->_detect_api_key(); - } - - // only allow ajax requests - if (!$this->input->is_ajax_request() && config_item('rest_ajax_only')) { - $response = [config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Only AJAX requests are accepted.']; - $this->response($response, 406); // Set status to 406 NOT ACCEPTABLE - } - - // When there is no specific override for the current class/method, use the default auth value set in the config - if ($this->auth_override !== TRUE && !(config_item('rest_enable_keys') && $this->_allow === TRUE)) { - $rest_auth = strtolower($this->config->item('rest_auth')); - switch ($rest_auth) { - case 'basic': - $this->_prepare_basic_auth(); - break; - case 'digest': - $this->_prepare_digest_auth(); - break; - case 'session': - $this->_check_php_session(); - break; - } - if ($this->config->item('rest_ip_whitelist_enabled')) { - $this->_check_whitelist_auth(); - } - } - } - - /** - * Destructor function - * @author Chris Kacerguis - * - * @access public - */ - public function __destruct() - { - // Record the "stop" time of the request - $this->_end_rtime = microtime(TRUE); - // CK: if, we are logging, log the access time here, as we are done! - if (config_item('rest_enable_logging')) { - $this->_log_access_time(); - } - - } - - /** - * Remap - * - * Requests are not made to methods directly, the request will be for - * an "object". This simply maps the object and method to the correct - * Controller method. - * - * @access public - * @param string $object_called - * @param array $arguments The arguments passed to the controller method. - */ - public function _remap($object_called, $arguments) - { - // Should we answer if not over SSL? - if (config_item('force_https') && !$this->_detect_ssl()) { - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Unsupported protocol'], 403); - } - - $pattern = '/^(.*)\.('.implode('|', array_keys($this->_supported_formats)).')$/'; - $matches = []; - if (preg_match($pattern, $object_called, $matches)) { - $object_called = $matches[1]; - } - - $controller_method = $object_called.'_'.$this->request->method; - - // Do we want to log this method (if allowed by config)? - $log_method = !(isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] == FALSE); - - // Use keys for this method? - $use_key = !(isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] == FALSE); - - // They provided a key, but it wasn't valid, so get them out of here. - if (config_item('rest_enable_keys') && $use_key && $this->_allow === FALSE) { - if (config_item('rest_enable_logging') && $log_method) { - $this->_log_request(); - } - - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Invalid API Key '.$this->rest->key], 403); - } - - // Check to see if this key has access to the requested controller. - if (config_item('rest_enable_keys') && $use_key && !empty($this->rest->key) && !$this->_check_access()) { - if (config_item('rest_enable_logging') && $log_method) { - $this->_log_request(); - } - - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key does not have access to the requested controller.'], 401); - } - - // Sure it exists, but can they do anything with it? - if ( ! method_exists($this, $controller_method)) { - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Unknown method.'], 404); - } - - // Doing key related stuff? Can only do it if they have a key right? - if (config_item('rest_enable_keys') && !empty($this->rest->key)) { - // Check the limit - if (config_item('rest_enable_limits') && !$this->_check_limit($controller_method)) { - $response = [config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key has reached the hourly limit for this method.']; - $this->response($response, 401); - } - - // If no level is set use 0, they probably aren't using permissions - $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0; - - // If no level is set, or it is lower than/equal to the key's level - $authorized = $level <= $this->rest->level; - - // IM TELLIN! - if (config_item('rest_enable_logging') && $log_method) { - $this->_log_request($authorized); - } - - // They don't have good enough perms - $response = [config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'This API key does not have enough permissions.']; - $authorized || $this->response($response, 401); - } - - // No key stuff, but record that stuff is happening - elseif (config_item('rest_enable_logging') && $log_method) { - $this->_log_request($authorized = TRUE); - } - - // and...... GO! - $this->_fire_method([$this, $controller_method], $arguments); - } - - /** - * Fire Method - * - * Fires the designated controller method with the given arguments. - * - * @access protected - * @param array $method The controller method to fire - * @param array $args The arguments to pass to the controller method - */ - protected function _fire_method($method, $args) - { - call_user_func_array($method, $args); - } - - /** - * Response - * - * Takes pure data and optionally a status code, then creates the response. - * Set $continue to TRUE to flush the response to the client and continue running the script. - * - * @access public - * @param array $data - * @param NULL|int $http_code - * @param bool $continue - */ - public function response($data = NULL, $http_code = NULL, $continue = FALSE) - { - // If data is NULL and not code provide, error and bail - if ($data === NULL && $http_code === NULL) { - $http_code = 404; - - // create the output variable here in the case of $this->response(array()); - $output = NULL; - } - - // If data is NULL but http code provided, keep the output empty - elseif ($data === NULL && is_numeric($http_code)) { - $output = NULL; - } - - // Otherwise (if no data but 200 provided) or some data, carry on camping! - else { - // Is compression requested? - if ($this->config->item('compress_output') === TRUE && $this->_zlib_oc == FALSE) { - if (extension_loaded('zlib')) { - if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { - ob_start('ob_gzhandler'); - } - } - } - - is_numeric($http_code) || $http_code = 200; - - // @deprecated the following statement can be deleted. - // If the format method exists, call and return the output in that format - if (method_exists($this, '_format_'.$this->response->format)) { - // Set the correct format header - header('Content-Type: '.$this->_supported_formats[$this->response->format] . '; charset=' . strtolower($this->config->item('charset'))); - - $output = $this->{'_format_'.$this->response->format}($data); - } - - // If the format method exists, call and return the output in that format - elseif (method_exists($this->format, 'to_'.$this->response->format)) { - // Set the correct format header - header('Content-Type: '.$this->_supported_formats[$this->response->format] . '; charset=' . strtolower($this->config->item('charset'))); - - $output = $this->format->factory($data)->{'to_'.$this->response->format}(); - } - - // Format not supported, output directly - else { - $output = $data; - } - } - - set_status_header($http_code); - - // JC: Log response code only if rest logging enabled - if (config_item('rest_enable_logging')) { - $this->_log_response_code($http_code); - } - - // If zlib.output_compression is enabled it will compress the output, - // but it will not modify the content-length header to compensate for - // the reduction, causing the browser to hang waiting for more data. - // We'll just skip content-length in those cases. - if ( ! $this->_zlib_oc && ! $this->config->item('compress_output')) { - header('Content-Length: ' . strlen($output)); - } - - if($continue){ - echo($output); - ob_end_flush(); - ob_flush(); - flush(); - }else{ - exit($output); - } - } - - /** - * Detect SSL use - * - * Detect whether SSL is being used or not. - * - * @access protected - */ - protected function _detect_ssl() - { - // $_SERVER['HTTPS'] (http://php.net/manual/en/reserved.variables.server.php) - // Set to a non-empty value if the script was queried through the HTTPS protocol - return (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS'])); - } - - - /** - * Detect input format - * - * Detect which format the HTTP Body is provided in - * - * @access protected - */ - protected function _detect_input_format() - { - if ($this->input->server('CONTENT_TYPE')) { - // Check all formats against the HTTP_ACCEPT header - foreach ($this->_supported_formats as $format => $mime) { - if (strpos($match = $this->input->server('CONTENT_TYPE'), ';')) { - $match = current(explode(';', $match)); - } - - if ($match == $mime) { - return $format; - } - } - } - - return NULL; - } - - /** - * Detect format - * - * Detect which format should be used to output the data. - * - * @access protected - * @return string The output format. - */ - protected function _detect_output_format() - { - $pattern = '/\.('.implode('|', array_keys($this->_supported_formats)).')$/'; - - // Check if a file extension is used when no get arguments provided - $matches = []; - if (!$this->_get_args && preg_match($pattern, $this->uri->uri_string(), $matches)) { - return $matches[1]; - } - - // Check if a file extension is used - elseif ($this->_get_args && !is_array(end($this->_get_args)) && preg_match($pattern, end($this->_get_args), $matches)) { - //elseif ($this->_get_args and !is_array(end($this->_get_args)) and preg_match($pattern, end(array_keys($this->_get_args)), $matches)) { - // The key of the last argument - $arg_keys = array_keys($this->_get_args); - $last_key = end($arg_keys); - - // Remove the extension from arguments too - $this->_get_args[$last_key] = preg_replace($pattern, '', $this->_get_args[$last_key]); - $this->_args[$last_key] = preg_replace($pattern, '', $this->_args[$last_key]); - - return $matches[1]; - } - - // A format has been passed as an argument in the URL and it is supported - if (isset($this->_get_args['format']) && array_key_exists($this->_get_args['format'], $this->_supported_formats)) { - return $this->_get_args['format']; - } - - // Otherwise, check the HTTP_ACCEPT (if it exists and we are allowed) - if ($this->config->item('rest_ignore_http_accept') === FALSE && $this->input->server('HTTP_ACCEPT')) { - // Check all formats against the HTTP_ACCEPT header - foreach (array_keys($this->_supported_formats) as $format) { - // Has this format been requested? - if (strpos($this->input->server('HTTP_ACCEPT'), $format) !== FALSE) { - // If not HTML or XML assume its right and send it on its way - if ($format != 'html' && $format != 'xml') { - return $format; - } - - // HTML or XML have shown up as a match - else { - // If it is truly HTML, it wont want any XML - if ($format == 'html' && strpos($this->input->server('HTTP_ACCEPT'), 'xml') === FALSE) { - return $format; - } - - // If it is truly XML, it wont want any HTML - elseif ($format == 'xml' && strpos($this->input->server('HTTP_ACCEPT'), 'html') === FALSE) { - return $format; - } - } - } - } - } // End HTTP_ACCEPT checking - - // Well, none of that has worked! Let's see if the controller has a default - if ( ! empty($this->rest_format)) { - return $this->rest_format; - } - - // Just use the default format - return config_item('rest_default_format'); - } - - /** - * Detect method - * - * Detect which HTTP method is being used - * - * @access protected - * @return string - */ - protected function _detect_method() - { - $method = strtolower($this->input->server('REQUEST_METHOD')); - - if ($this->config->item('enable_emulate_request')) { - if ($this->input->post('_method')) { - $method = strtolower($this->input->post('_method')); - } elseif ($this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { - $method = strtolower($this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE')); - } - } - - if (in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method)) { - return $method; - } - - return 'get'; - } - - /** - * Detect API Key - * - * See if the user has provided an API key - * - * @access protected - * @return boolean - */ - protected function _detect_api_key() - { - // Get the api key name variable set in the rest config file - $api_key_variable = config_item('rest_key_name'); - - // Work out the name of the SERVER entry based on config - $key_name = 'HTTP_'.strtoupper(str_replace('-', '_', $api_key_variable)); - - $this->rest->key = NULL; - $this->rest->level = NULL; - $this->rest->user_id = NULL; - $this->rest->ignore_limits = FALSE; - - // Find the key from server or arguments - if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name))) { - if ( ! ($row = $this->rest->db->where(config_item('rest_key_column'), $key)->get(config_item('rest_keys_table'))->row())) { - return FALSE; - } - - $this->rest->key = $row->{config_item('rest_key_column')}; - - isset($row->user_id) && $this->rest->user_id = $row->user_id; - isset($row->level) && $this->rest->level = $row->level; - isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits; - - $this->_apiuser = $row; - - /* - * If "is private key" is enabled, compare the ip address with the list - * of valid ip addresses stored in the database. - */ - if (!empty($row->is_private_key)) { - // Check for a list of valid ip addresses - if (isset($row->ip_addresses)) { - // multiple ip addresses must be separated using a comma, explode and loop - $list_ip_addresses = explode(",", $row->ip_addresses); - $found_address = FALSE; - - foreach ($list_ip_addresses as $ip_address) { - if ($this->input->ip_address() == trim($ip_address)) { - // there is a match, set the the value to TRUE and break out of the loop - $found_address = TRUE; - break; - } - } - - return $found_address; - } else { - // There should be at least one IP address for this private key. - return FALSE; - } - } - - return $row; - } - - // No key has been sent - return FALSE; - } - - /** - * Detect language(s) - * - * What language do they want it in? - * - * @access protected - * @return NULL|string The language code. - */ - protected function _detect_lang() - { - if ( ! $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE')) { - return NULL; - } - - // They might have sent a few, make it an array - if (strpos($lang, ',') !== FALSE) { - $langs = explode(',', $lang); - - $return_langs = []; - foreach ($langs as $lang) { - // Remove weight and strip space - list($lang) = explode(';', $lang); - $return_langs[] = trim($lang); - } - - return $return_langs; - } - - // Nope, just return the string - return $lang; - } - - /** - * Log request - * - * Record the entry for awesomeness purposes - * - * @access protected - * @param boolean $authorized - * @return object - */ - protected function _log_request($authorized = FALSE) - { - $status = $this->rest->db->insert(config_item('rest_logs_table'), [ - 'uri' => $this->uri->uri_string(), - 'method' => $this->request->method, - 'params' => $this->_args ? (config_item('rest_logs_json_params') ? json_encode($this->_args) : serialize($this->_args)) : NULL, - 'api_key' => isset($this->rest->key) ? $this->rest->key : '', - 'ip_address' => $this->input->ip_address(), - 'time' => function_exists('now') ? now() : time(), - 'authorized' => $authorized - ]); - - $this->_insert_id = $this->rest->db->insert_id(); - - return $status; - } - - /** - * Limiting requests - * - * Check if the requests are coming in a tad too fast. - * - * @access protected - * @param string $controller_method The method being called. - * @return boolean - */ - protected function _check_limit($controller_method) - { - // They are special, or it might not even have a limit - if ( ! empty($this->rest->ignore_limits) || !isset($this->methods[$controller_method]['limit'])) { - // On your way sonny-jim. - return TRUE; - } - - // How many times can you get to this method an hour? - $limit = $this->methods[$controller_method]['limit']; - - // Get data on a keys usage - $result = $this->rest->db - ->where('uri', $this->uri->uri_string()) - ->where('api_key', $this->rest->key) - ->get(config_item('rest_limits_table')) - ->row(); - - // No calls yet for this key - if ( ! $result ) { - // Right, set one up from scratch - $this->rest->db->insert(config_item('rest_limits_table'), [ - 'uri' => $this->uri->uri_string(), - 'api_key' => isset($this->rest->key) ? $this->rest->key : '', - 'count' => 1, - 'hour_started' => time() - ]); - } - - // Been an hour since they called - elseif ($result->hour_started < time() - (60 * 60)) { - // Reset the started period - $this->rest->db - ->where('uri', $this->uri->uri_string()) - ->where('api_key', isset($this->rest->key) ? $this->rest->key : '') - ->set('hour_started', time()) - ->set('count', 1) - ->update(config_item('rest_limits_table')); - } - - // They have called within the hour, so lets update - else { - // Your luck is out, you've called too many times! - if ($result->count >= $limit) { - return FALSE; - } - - $this->rest->db - ->where('uri', $this->uri->uri_string()) - ->where('api_key', $this->rest->key) - ->set('count', 'count + 1', FALSE) - ->update(config_item('rest_limits_table')); - } - - return TRUE; - } - - /** - * Auth override check - * - * Check if there is a specific auth type set for the current class/method - * being called. - * - * @access protected - * @return boolean - */ - protected function _auth_override_check() - { - - // Assign the class/method auth type override array from the config - $this->overrides_array = $this->config->item('auth_override_class_method'); - - // Check to see if the override array is even populated, otherwise return FALSE - if (empty($this->overrides_array)) { - return FALSE; - } - - // check for wildcard flag for rules for classes - if(!empty($this->overrides_array[$this->router->class]['*'])){//check for class overides - // None auth override found, prepare nothing but send back a TRUE override flag - if ($this->overrides_array[$this->router->class]['*'] == 'none') - { - return TRUE; - } - - // Basic auth override found, prepare basic - if ($this->overrides_array[$this->router->class]['*'] == 'basic') - { - $this->_prepare_basic_auth(); - return TRUE; - } - - // Digest auth override found, prepare digest - if ($this->overrides_array[$this->router->class]['*'] == 'digest') - { - $this->_prepare_digest_auth(); - return TRUE; - } - - // Whitelist auth override found, check client's ip against config whitelist - if ($this->overrides_array[$this->router->class]['*'] == 'whitelist') - { - $this->_check_whitelist_auth(); - return TRUE; - } - } - - // Check to see if there's an override value set for the current class/method being called - if (empty($this->overrides_array[$this->router->class][$this->router->method])) { - return FALSE; - } - - // None auth override found, prepare nothing but send back a TRUE override flag - if ($this->overrides_array[$this->router->class][$this->router->method] == 'none') { - return TRUE; - } - - // Basic auth override found, prepare basic - if ($this->overrides_array[$this->router->class][$this->router->method] == 'basic') { - $this->_prepare_basic_auth(); - - return TRUE; - } - - // Digest auth override found, prepare digest - if ($this->overrides_array[$this->router->class][$this->router->method] == 'digest') { - $this->_prepare_digest_auth(); - - return TRUE; - } - - // Whitelist auth override found, check client's ip against config whitelist - if ($this->overrides_array[$this->router->class][$this->router->method] == 'whitelist') { - $this->_check_whitelist_auth(); - - return TRUE; - } - - // Return FALSE when there is an override value set but it does not match - // 'basic', 'digest', or 'none'. (the value was misspelled) - return FALSE; - } - - /** - * Parse GET - * - * @access protected - */ - protected function _parse_get() - { - // Fix for Issue #247 - if (is_cli()) { - $args = $_SERVER['argv']; - unset($args[0]); - $_SERVER['QUERY_STRING'] = $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = '/' . implode('/', $args) . '/'; - } - - // Grab proper GET variables - parse_str(parse_url(http://github.com/$_SERVER['REQUEST_URI'],%20PHP_URL_QUERY), $get); - - // Merge both the URI segments and GET params - $this->_get_args = array_merge($this->_get_args, $get); - } - - /** - * Parse POST - * - * @access protected - */ - protected function _parse_post() - { - $this->_post_args = $_POST; - - $this->request->format && $this->request->body = file_get_contents('php://input'); - } - - /** - * Parse PUT - * - * @access protected - */ - protected function _parse_put() - { - // It might be a HTTP body - if ($this->request->format) { - $this->request->body = file_get_contents('php://input'); - } - - // If no file type is provided, this is probably just arguments - else { - if ($this->input->method() == 'put') { - $this->_put_args = $this->input->input_stream(); - } - } - - } - - /** - * Parse HEAD - * - * @access protected - */ - protected function _parse_head() - { - // Grab proper HEAD variables - parse_str(parse_url(http://github.com/$_SERVER['REQUEST_URI'],%20PHP_URL_QUERY), $head); - - // Merge both the URI segments and HEAD params - $this->_head_args = array_merge($this->_head_args, $head); - } - - /** - * Parse OPTIONS - * - * @access protected - */ - protected function _parse_options() - { - // Grab proper OPTIONS variables - parse_str(parse_url(http://github.com/$_SERVER['REQUEST_URI'],%20PHP_URL_QUERY), $options); - - // Merge both the URI segments and OPTIONS params - $this->_options_args = array_merge($this->_options_args, $options); - } - - /** - * Parse PATCH - * - * @access protected - */ - protected function _parse_patch() - { - // It might be a HTTP body - if ($this->request->format) { - $this->request->body = file_get_contents('php://input'); - } - - // If no file type is provided, this is probably just arguments - else { - if ($this->input->method() == 'patch') { - $this->_patch_args = $this->input->input_stream(); - } - } - } - - /** - * Parse DELETE - * - * @access protected - */ - protected function _parse_delete() - { - // Set up out DELETE variables (which shouldn't really exist, but sssh!) - if ($this->input->method() == 'delete') { - $this->_delete_args = $this->input->input_stream(); - } - } - - // INPUT FUNCTION -------------------------------------------------------------- - - /** - * Retrieve a value from the GET request arguments. - * - * @access public - * @param string $key The key for the GET request argument to retrieve - * @param boolean $xss_clean Whether the value should be XSS cleaned or not. - * @return string The GET argument value. - */ - public function get($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_get_args; - } - - return array_key_exists($key, $this->_get_args) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : FALSE; - } - - /** - * This function retrieves a values from the OPTIONS request arguments - * - * @access public - * @param string $key The OPTIONS/GET argument key - * @param boolean $xss_clean Whether the value should be XSS cleaned or not - * @return string The OPTIONS/GET argument value - */ - public function options($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_options_args; - } - - return array_key_exists($key, $this->_options_args) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : FALSE; - } - - /** - * This function retrieves a values from the HEAD request arguments - * - * @access public - * @param string $key The HEAD/GET argument key - * @param boolean $xss_clean Whether the value should be XSS cleaned or not - * @return string The HEAD/GET argument value - */ - public function head($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->head_args; - } - - return array_key_exists($key, $this->head_args) ? $this->_xss_clean($this->head_args[$key], $xss_clean) : FALSE; - } - - /** - * Retrieve a value from the POST request arguments. - * - * @access public - * @param string $key The key for the POST request argument to retrieve - * @param boolean $xss_clean Whether the value should be XSS cleaned or not. - * @return string The POST argument value. - */ - public function post($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_post_args; - } - - return array_key_exists($key, $this->_post_args) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : FALSE; - } - - /** - * Retrieve a value from the PUT request arguments. - * - * @access public - * @param string $key The key for the PUT request argument to retrieve - * @param boolean $xss_clean Whether the value should be XSS cleaned or not. - * @return string The PUT argument value. - */ - public function put($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_put_args; - } - - return array_key_exists($key, $this->_put_args) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : FALSE; - } - - /** - * Retrieve a value from the DELETE request arguments. - * - * @access public - * @param string $key The key for the DELETE request argument to retrieve - * @param boolean $xss_clean Whether the value should be XSS cleaned or not. - * @return string The DELETE argument value. - */ - public function delete($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_delete_args; - } - - return array_key_exists($key, $this->_delete_args) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : FALSE; - } - - /** - * Retrieve a value from the PATCH request arguments. - * - * @access public - * @param string $key The key for the PATCH request argument to retrieve - * @param boolean $xss_clean Whether the value should be XSS cleaned or not. - * @return string The PATCH argument value. - */ - public function patch($key = NULL, $xss_clean = TRUE) - { - if ($key === NULL) { - return $this->_patch_args; - } - - return array_key_exists($key, $this->_patch_args) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : FALSE; - } - - /** - * Process to protect from XSS attacks. - * - * @access protected - * @param string $val The input. - * @param boolean $process Do clean or note the input. - * @return string - */ - protected function _xss_clean($val, $process) - { - if (CI_VERSION < 2) { - return $process ? $this->input->xss_clean($val) : $val; - } - - return $process ? $this->security->xss_clean($val) : $val; - } - - /** - * Retrieve the validation errors. - * - * @access public - * @return array - */ - public function validation_errors() - { - $string = strip_tags($this->form_validation->error_string()); - - return explode("\n", trim($string, "\n")); - } - - // SECURITY FUNCTIONS --------------------------------------------------------- - - /** - * Perform LDAP Authentication - * - * @access protected - * @param string $username The username to validate - * @param string $password The password to validate - * @return boolean - */ - protected function _perform_ldap_auth($username = '', $password = NULL) - { - if (empty($username)) { - log_message('debug', 'LDAP Auth: failure, empty username'); - - return FALSE; - } - - log_message('debug', 'LDAP Auth: Loading Config'); - - $this->config->load('ldap.php', TRUE); - - $ldap = [ - 'timeout' => $this->config->item('timeout', 'ldap'), - 'host' => $this->config->item('server', 'ldap'), - 'port' => $this->config->item('port', 'ldap'), - 'rdn' => $this->config->item('binduser', 'ldap'), - 'pass' => $this->config->item('bindpw', 'ldap'), - 'basedn' => $this->config->item('basedn', 'ldap'), - ]; - - log_message('debug', 'LDAP Auth: Connect to ' . $ldaphost); - - $ldapconfig['authrealm'] = $this->config->item('domain', 'ldap'); - - // connect to ldap server - $ldapconn = ldap_connect($ldap['host'], $ldap['port']); - - if ($ldapconn) { - - log_message('debug', 'Setting timeout to ' . $ldap['timeout'] . ' seconds'); - - ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']); - - log_message('debug', 'LDAP Auth: Binding to ' . $ldap['host'] . ' with dn ' . $ldap['rdn']); - - // binding to ldap server - $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']); - - // verify binding - if ($ldapbind) { - log_message('debug', 'LDAP Auth: bind successful'); - } else { - log_message('error', 'LDAP Auth: bind unsuccessful'); - - return FALSE; - } - - } - - // search for user - if (($res_id = ldap_search( $ldapconn, $ldap['basedn'], "uid=$username")) == FALSE) { - log_message('error', 'LDAP Auth: User ' . $username . ' not found in search'); - - return FALSE; - } - - if (ldap_count_entries($ldapconn, $res_id) != 1) { - log_message('error', 'LDAP Auth: failure, username ' . $username . 'found more than once'); - - return FALSE; - } - - if (( $entry_id = ldap_first_entry($ldapconn, $res_id))== FALSE) { - log_message('error', 'LDAP Auth: failure, entry of searchresult could not be fetched'); - - return FALSE; - } - - if (( $user_dn = ldap_get_dn($ldapconn, $entry_id)) == FALSE) { - log_message('error', 'LDAP Auth: failure, user-dn could not be fetched'); - - return FALSE; - } - - // User found, could not authenticate as user - if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) == FALSE) { - log_message('error', 'LDAP Auth: failure, username/password did not match: ' . $user_dn); - - return FALSE; - } - - log_message('debug', 'LDAP Auth: Success ' . $user_dn . ' authenticated successfully'); - - $this->_user_ldap_dn = $user_dn; - ldap_close($ldapconn); - - return TRUE; - } - - /** - * Perform Library Authentication - Override this function to change the way the library is called - * - * @access protected - * @param string $username The username to validate - * @param string $password The password to validate - * @return boolean - */ - protected function _perform_library_auth($username = '', $password = NULL) - { - if (empty($username)) { - log_message('error', 'Library Auth: failure, empty username'); - return FALSE; - } - - $auth_library_class = strtolower($this->config->item('auth_library_class')); - $auth_library_function = strtolower($this->config->item('auth_library_function')); - - if (empty($auth_library_class)) { - log_message('debug', 'Library Auth: failure, empty auth_library_class'); - return FALSE; - } - - if (empty($auth_library_function)) { - log_message('debug', 'Library Auth: failure, empty auth_library_function'); - return FALSE; - } - - if (!is_callable([$auth_library_class, $auth_library_function])) { - $this->load->library($auth_library_class); - } - - return $this->{$auth_library_class}->$auth_library_function($username, $password); - } - - /** - * Check if the user is logged in. - * - * @access protected - * @param string $username The user's name - * @param string $password The user's password - * @return boolean - */ - protected function _check_login($username = '', $password = FALSE) - { - if (empty($username)) { - return FALSE; - } - - $auth_source = strtolower($this->config->item('auth_source')); - $rest_auth = strtolower($this->config->item('rest_auth')); - $valid_logins = $this->config->item('rest_valid_logins'); - - if (!$this->config->item('auth_source') && $rest_auth == 'digest') { // for digest we do not have a password passed as argument - return md5($username.':'.$this->config->item('rest_realm').':'.(isset($valid_logins[$username])?$valid_logins[$username]:'')); - } - - if ($password === FALSE) { - return FALSE; - } - - if ($auth_source == 'ldap') { - log_message('debug', 'performing LDAP authentication for $username'); - - return $this->_perform_ldap_auth($username, $password); - } - - if ($auth_source == 'library') { - log_message('debug', 'performing Library authentication for '.$username); - - return $this->_perform_library_auth($username, $password); - } - - if (!array_key_exists($username, $valid_logins)) { - return FALSE; - } - - if ($valid_logins[$username] != $password) { - return FALSE; - } - - return TRUE; - } - - /** - * Check to see if the user is logged into the web app with a php session key. - * - * @access protected - */ - protected function _check_php_session() - { - $key = $this->config->item('auth_source'); - if (!$this->session->userdata($key)) { - $this->response(['status' => FALSE, 'error' => 'Not Authorized'], 401); - } - } - - /** - * @todo document this. - * - * @access protected - */ - protected function _prepare_basic_auth() - { - // If whitelist is enabled it has the first chance to kick them out - if (config_item('rest_ip_whitelist_enabled')) { - $this->_check_whitelist_auth(); - } - - $username = NULL; - $password = NULL; - - // mod_php - if ($this->input->server('PHP_AUTH_USER')) { - $username = $this->input->server('PHP_AUTH_USER'); - $password = $this->input->server('PHP_AUTH_PW'); - } - - // most other servers - elseif ($this->input->server('HTTP_AUTHENTICATION')) { - if (strpos(strtolower($this->input->server('HTTP_AUTHENTICATION')), 'basic') === 0) { - list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6))); - } - } - - if ( ! $this->_check_login($username, $password)) { - $this->_force_login(); - } - } - - /** - * @todo Document this. - * - * @access protected - */ - protected function _prepare_digest_auth() - { - // If whitelist is enabled it has the first chance to kick them out - if (config_item('rest_ip_whitelist_enabled')) { - $this->_check_whitelist_auth(); - } - - $uniqid = uniqid(""); // Empty argument for backward compatibility - // We need to test which server authentication variable to use - // because the PHP ISAPI module in IIS acts different from CGI - if ($this->input->server('PHP_AUTH_DIGEST')) { - $digest_string = $this->input->server('PHP_AUTH_DIGEST'); - } elseif ($this->input->server('HTTP_AUTHORIZATION')) { - $digest_string = $this->input->server('HTTP_AUTHORIZATION'); - } else { - $digest_string = ""; - } - - // The $_SESSION['error_prompted'] variable is used to ask the password - // again if none given or if the user enters wrong auth information. - if (empty($digest_string)) { - $this->_force_login($uniqid); - } - - // We need to retrieve authentication informations from the $auth_data variable - $matches = []; - preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches); - $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]); - - // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username @see rest.php::auth_library_function config - $A1 = $this->_check_login($digest['username'], TRUE); - if ( ! array_key_exists('username', $digest) || ! $A1 ) { - $this->_force_login($uniqid); - } - - $A2 = md5(strtoupper($this->request->method).':'.$digest['uri']); - $valid_response = md5($A1.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$A2); - - if ($digest['response'] != $valid_response) { - $this->response([config_item('rest_status_field_name') => 0, config_item('rest_message_field_name') => 'Invalid credentials'], 401); - exit; - } - } - - /** - * Check if the client's ip is in the 'rest_ip_blacklist' config - * - * @access protected - */ - protected function _check_blacklist_auth() - { - $blacklist = explode(',', config_item('rest_ip_blacklist')); - - foreach ($blacklist AS &$ip) { - $ip = trim($ip); - } - - if (in_array($this->input->ip_address(), $blacklist)) { - $this->response(['status' => FALSE, 'error' => 'IP Denied'], 401); - } - } - - /** - * Check if the client's ip is in the 'rest_ip_whitelist' config - * - * @access protected - */ - protected function _check_whitelist_auth() - { - $whitelist = explode(',', config_item('rest_ip_whitelist')); - - array_push($whitelist, '127.0.0.1', '0.0.0.0'); - - foreach ($whitelist AS &$ip) { - $ip = trim($ip); - } - - if ( ! in_array($this->input->ip_address(), $whitelist)) { - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'IP not authorized'], 401); - } - } - - /** - * @todo Document this. - * - * @access protected - * @param string $nonce - */ - protected function _force_login($nonce = '') - { - if (strtolower( $this->config->item('rest_auth') ) == 'basic') { - header('WWW-Authenticate: Basic realm="'.$this->config->item('rest_realm').'"'); - } elseif (strtolower( $this->config->item('rest_auth') ) == 'digest') { - header('WWW-Authenticate: Digest realm="'.$this->config->item('rest_realm').'", qop="auth", nonce="'.$nonce.'", opaque="'.md5($this->config->item('rest_realm')).'"'); - } - - $this->response([config_item('rest_status_field_name') => FALSE, config_item('rest_message_field_name') => 'Not authorized'], 401); - } - - /** - * Force it into an array - * - * @access protected - * @param object|array $data - * @return array - */ - protected function _force_loopable($data) - { - // Force it to be something useful - if ( ! is_array($data) && !is_object($data)) { - $data = (array) $data; - } - - return $data; - } - - /** - * updates the log with the access time - * - * @access protected - * @author Chris Kacerguis - * @return boolean - */ - - protected function _log_access_time() - { - $payload['rtime'] = $this->_end_rtime - $this->_start_rtime; - - return $this->rest->db->update(config_item('rest_logs_table'), $payload, ['id' => $this->_insert_id]); - } - - /** - * updates the log with response code result - * - * @author Justin Chen - * @return boolean - */ - - protected function _log_response_code($http_code) - { - $payload['response_code'] = $http_code; - return $this->rest->db->update(config_item('rest_logs_table'), $payload, ['id' => $this->_insert_id]); - } - - /** - * Check to see if the API key has access to the controller and methods - * - * @access protected - * @return boolean - */ - protected function _check_access() - { - // if we don't want to check acccess, just return TRUE - if (config_item('rest_enable_access') === FALSE) { - return TRUE; - } - - // Fetch controller based on path and controller name - $controller = implode( '/', [$this->router->directory, $this->router->class] ); - - // Remove any double slashes for safety - $controller = str_replace('//', '/', $controller); - - // Build access table query - $this->rest->db->select(); - $this->rest->db->where('key', $this->rest->key); - $this->rest->db->where('controller', $controller); - - $query = $this->rest->db->get(config_item('rest_access_table')); - - if ($query->num_rows() > 0) { - return TRUE; - } - - return FALSE; - } - -} diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..329d3b46 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "chriskacerguis/codeigniter-restserver", + "description": "CI Rest Server", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Chris Kacerguis", + "email": "chriskacerguis@gmail.com" + } + ], + "minimum-stability": "dev", + "autoload": { + "psr-4": {"chriskacerguis\\RestServer\\": "src/"} + }, + "require": {} +} diff --git a/documentation/404.html b/documentation/404.html deleted file mode 100644 index 762f8381..00000000 --- a/documentation/404.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - Page not found - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/class-Example.html b/documentation/class-Example.html deleted file mode 100644 index 5d10d511..00000000 --- a/documentation/class-Example.html +++ /dev/null @@ -1,477 +0,0 @@ - - - - - - Class Example - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/class-Format.html b/documentation/class-Format.html deleted file mode 100644 index 762585cd..00000000 --- a/documentation/class-Format.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - Class Format - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/class-Key.html b/documentation/class-Key.html deleted file mode 100644 index ae46fc61..00000000 --- a/documentation/class-Key.html +++ /dev/null @@ -1,454 +0,0 @@ - - - - - - Class Key - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/class-REST_Controller.html b/documentation/class-REST_Controller.html deleted file mode 100644 index 74d8de5b..00000000 --- a/documentation/class-REST_Controller.html +++ /dev/null @@ -1,2292 +0,0 @@ - - - - - - Class REST_Controller - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/elementlist.js b/documentation/elementlist.js deleted file mode 100644 index 7941e8a2..00000000 --- a/documentation/elementlist.js +++ /dev/null @@ -1,3 +0,0 @@ - -var ApiGen = ApiGen || {}; -ApiGen.elements = [["f","_perform_ldap_auth()"],["c","Example"],["c","Format"],["c","Key"],["c","REST_Controller"]]; diff --git a/documentation/function-_perform_ldap_auth.html b/documentation/function-_perform_ldap_auth.html deleted file mode 100644 index af6e32cc..00000000 --- a/documentation/function-_perform_ldap_auth.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - Function _perform_ldap_auth - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/index.html b/documentation/index.html deleted file mode 100644 index 7790a6fe..00000000 --- a/documentation/index.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - Overview - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/package-CodeIgniter.Libraries.html b/documentation/package-CodeIgniter.Libraries.html deleted file mode 100644 index c79d4217..00000000 --- a/documentation/package-CodeIgniter.Libraries.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - Package CodeIgniter\Libraries - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/package-CodeIgniter.html b/documentation/package-CodeIgniter.html deleted file mode 100644 index 0b48c862..00000000 --- a/documentation/package-CodeIgniter.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - Package CodeIgniter - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/package-None.html b/documentation/package-None.html deleted file mode 100644 index 9909f104..00000000 --- a/documentation/package-None.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - No package - - - - - - -
- -
- -
- - - - - - diff --git a/documentation/resources/collapsed.png b/documentation/resources/collapsed.png deleted file mode 100644 index 56e73239..00000000 Binary files a/documentation/resources/collapsed.png and /dev/null differ diff --git a/documentation/resources/combined.js b/documentation/resources/combined.js deleted file mode 100644 index 45cd2fe1..00000000 --- a/documentation/resources/combined.js +++ /dev/null @@ -1,1315 +0,0 @@ - -var ApiGen = ApiGen || {}; -ApiGen.config = {"options":{"elementDetailsCollapsed":true,"elementsOrder":"natural"},"name":"ApiGen theme","templatesPath":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default","resources":{"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/resources":"resources"},"templates":{"overview":{"filename":"index.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/overview.latte"},"combined":{"filename":"resources\/combined.js","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/combined.js.latte"},"elementlist":{"filename":"elementlist.js","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/elementlist.js.latte"},"404":{"filename":"404.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/404.latte"},"package":{"filename":"package-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/package.latte"},"namespace":{"filename":"namespace-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/namespace.latte"},"class":{"filename":"class-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/class.latte"},"constant":{"filename":"constant-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/constant.latte"},"function":{"filename":"function-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/function.latte"},"source":{"filename":"source-%s.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/source.latte"},"tree":{"filename":"tree.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/tree.latte"},"deprecated":{"filename":"deprecated.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/deprecated.latte"},"todo":{"filename":"todo.html","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/todo.latte"},"sitemap":{"filename":"sitemap.xml","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/sitemap.xml.latte"},"opensearch":{"filename":"opensearch.xml","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/opensearch.xml.latte"},"robots":{"filename":"robots.txt","template":"phar:\/\/\/usr\/local\/bin\/apigen\/src\/templates\/default\/robots.txt.latte"}}}; - - - /*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license -*/ -(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t -}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); -u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("