From 90dff61bb391ae12d2a232a0a279e88d5449ac80 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Sat, 26 Nov 2011 10:45:31 +0100 Subject: [PATCH] initial import --- .gitignore | 8 + app/.htaccess | 5 + app/README.txt | 29 + app/app_controller.php | 272 +++++++++ app/app_error.php | 27 + app/config/acl.ini.php | 70 +++ app/config/bootstrap.php | 58 ++ app/config/core.php | 316 ++++++++++ app/config/database.php | 86 +++ app/config/routes.php | 35 ++ app/config/schema/db_acl.php | 73 +++ app/config/schema/i18n.php | 50 ++ app/config/schema/sessions.php | 47 ++ app/controllers/components/empty | 0 app/controllers/events_controller.php | 559 ++++++++++++++++++ app/controllers/groups_controller.php | 63 ++ app/controllers/relations_controller.php | 37 ++ app/controllers/signatures_controller.php | 163 +++++ app/controllers/users_controller.php | 209 +++++++ app/index.php | 18 + app/libs/empty | 0 app/locale/eng/LC_MESSAGES/empty | 0 app/models/behaviors/empty | 0 app/models/datasources/empty | 0 app/models/event.php | 76 +++ app/models/group.php | 38 ++ app/models/relation.php | 33 ++ app/models/signature.php | 174 ++++++ app/models/user.php | 186 ++++++ app/plugins/empty | 0 .../controllers/components/recaptcha.php | 143 +++++ app/plugins/recaptcha/license.txt | 25 + .../locale/fre/LC_MESSAGES/recaptcha.po | 28 + app/plugins/recaptcha/locale/recaptcha.pot | 39 ++ .../recaptcha/models/behaviors/recaptcha.php | 71 +++ app/plugins/recaptcha/readme.md | 57 ++ .../tests/cases/behaviors/recaptcha.test.php | 73 +++ .../tests/cases/components/recaptcha.test.php | 84 +++ .../tests/cases/helpers/recaptcha.test.php | 67 +++ .../tests/fixtures/article_fixture.php | 45 ++ .../recaptcha/views/helpers/recaptcha.php | 215 +++++++ app/tests/cases/behaviors/empty | 0 app/tests/cases/components/empty | 0 app/tests/cases/controllers/empty | 0 app/tests/cases/helpers/empty | 0 app/tests/cases/models/empty | 0 app/tests/fixtures/empty | 0 app/tests/groups/empty | 0 app/vendors/shells/tasks/empty | 0 app/vendors/shells/templates/empty | 0 app/views/elements/actions_menu.ctp | 15 + app/views/elements/email/html/empty | 0 app/views/elements/email/text/body.ctp | 1 + app/views/elements/email/text/empty | 0 app/views/elements/email/text/new_event.ctp | 17 + app/views/elements/empty | 0 app/views/errors/empty | 0 app/views/errors/error403.ctp | 5 + app/views/events/add.ctp | 22 + app/views/events/edit.ctp | 25 + app/views/events/index.ctp | 60 ++ app/views/events/snort.ctp | 0 app/views/events/view.ctp | 95 +++ app/views/events/xml.ctp | 17 + app/views/groups/add.ctp | 19 + app/views/groups/edit.ctp | 21 + app/views/groups/index.ctp | 49 ++ app/views/groups/view.ctp | 72 +++ app/views/helpers/empty | 0 app/views/layouts/default.ctp | 74 +++ app/views/layouts/email/html/default.ctp | 29 + app/views/layouts/email/html/empty | 0 app/views/layouts/email/text/default.ctp | 20 + app/views/layouts/email/text/empty | 0 app/views/layouts/js/empty | 0 app/views/layouts/rss/empty | 0 app/views/layouts/xml/empty | 0 app/views/layouts/xml/xml.ctp | 1 + app/views/pages/empty | 0 app/views/scaffolds/empty | 0 app/views/signatures/add.ctp | 18 + app/views/signatures/edit.ctp | 22 + app/views/signatures/index.ctp | 54 ++ app/views/signatures/search.ctp | 16 + app/views/signatures/view.ctp | 36 ++ app/views/users/add.ctp | 21 + app/views/users/edit.ctp | 26 + app/views/users/index.ctp | 59 ++ app/views/users/login.ctp | 12 + app/views/users/view.ctp | 99 ++++ app/webroot/.htaccess | 6 + app/webroot/css.php | 96 +++ app/webroot/css/cake.generic.css | 555 +++++++++++++++++ app/webroot/favicon.ico | Bin 0 -> 372 bytes app/webroot/files/empty | 0 app/webroot/gpg.asc | 31 + app/webroot/img/cake.icon.png | Bin 0 -> 943 bytes app/webroot/img/cake.power.gif | Bin 0 -> 201 bytes app/webroot/img/test-error-icon.png | Bin 0 -> 3358 bytes app/webroot/img/test-fail-icon.png | Bin 0 -> 496 bytes app/webroot/img/test-pass-icon.png | Bin 0 -> 783 bytes app/webroot/img/test-skip-icon.png | Bin 0 -> 1207 bytes app/webroot/index.php | 84 +++ app/webroot/js/empty | 0 app/webroot/js/jquery.js | 18 + app/webroot/robots.txt | 2 + app/webroot/test.php | 94 +++ 107 files changed, 5270 insertions(+) create mode 100644 .gitignore create mode 100644 app/.htaccess create mode 100644 app/README.txt create mode 100755 app/app_controller.php create mode 100755 app/app_error.php create mode 100644 app/config/acl.ini.php create mode 100755 app/config/bootstrap.php create mode 100755 app/config/core.php create mode 100755 app/config/database.php create mode 100755 app/config/routes.php create mode 100644 app/config/schema/db_acl.php create mode 100644 app/config/schema/i18n.php create mode 100644 app/config/schema/sessions.php create mode 100644 app/controllers/components/empty create mode 100755 app/controllers/events_controller.php create mode 100755 app/controllers/groups_controller.php create mode 100644 app/controllers/relations_controller.php create mode 100755 app/controllers/signatures_controller.php create mode 100755 app/controllers/users_controller.php create mode 100644 app/index.php create mode 100644 app/libs/empty create mode 100644 app/locale/eng/LC_MESSAGES/empty create mode 100644 app/models/behaviors/empty create mode 100644 app/models/datasources/empty create mode 100755 app/models/event.php create mode 100755 app/models/group.php create mode 100644 app/models/relation.php create mode 100755 app/models/signature.php create mode 100755 app/models/user.php create mode 100644 app/plugins/empty create mode 100755 app/plugins/recaptcha/controllers/components/recaptcha.php create mode 100755 app/plugins/recaptcha/license.txt create mode 100755 app/plugins/recaptcha/locale/fre/LC_MESSAGES/recaptcha.po create mode 100755 app/plugins/recaptcha/locale/recaptcha.pot create mode 100755 app/plugins/recaptcha/models/behaviors/recaptcha.php create mode 100755 app/plugins/recaptcha/readme.md create mode 100755 app/plugins/recaptcha/tests/cases/behaviors/recaptcha.test.php create mode 100755 app/plugins/recaptcha/tests/cases/components/recaptcha.test.php create mode 100755 app/plugins/recaptcha/tests/cases/helpers/recaptcha.test.php create mode 100755 app/plugins/recaptcha/tests/fixtures/article_fixture.php create mode 100755 app/plugins/recaptcha/views/helpers/recaptcha.php create mode 100644 app/tests/cases/behaviors/empty create mode 100644 app/tests/cases/components/empty create mode 100644 app/tests/cases/controllers/empty create mode 100644 app/tests/cases/helpers/empty create mode 100644 app/tests/cases/models/empty create mode 100644 app/tests/fixtures/empty create mode 100644 app/tests/groups/empty create mode 100644 app/vendors/shells/tasks/empty create mode 100644 app/vendors/shells/templates/empty create mode 100644 app/views/elements/actions_menu.ctp create mode 100644 app/views/elements/email/html/empty create mode 100755 app/views/elements/email/text/body.ctp create mode 100644 app/views/elements/email/text/empty create mode 100755 app/views/elements/email/text/new_event.ctp create mode 100644 app/views/elements/empty create mode 100644 app/views/errors/empty create mode 100755 app/views/errors/error403.ctp create mode 100755 app/views/events/add.ctp create mode 100755 app/views/events/edit.ctp create mode 100755 app/views/events/index.ctp create mode 100755 app/views/events/snort.ctp create mode 100755 app/views/events/view.ctp create mode 100755 app/views/events/xml.ctp create mode 100755 app/views/groups/add.ctp create mode 100755 app/views/groups/edit.ctp create mode 100755 app/views/groups/index.ctp create mode 100755 app/views/groups/view.ctp create mode 100644 app/views/helpers/empty create mode 100755 app/views/layouts/default.ctp create mode 100755 app/views/layouts/email/html/default.ctp create mode 100644 app/views/layouts/email/html/empty create mode 100755 app/views/layouts/email/text/default.ctp create mode 100644 app/views/layouts/email/text/empty create mode 100644 app/views/layouts/js/empty create mode 100644 app/views/layouts/rss/empty create mode 100644 app/views/layouts/xml/empty create mode 100755 app/views/layouts/xml/xml.ctp create mode 100644 app/views/pages/empty create mode 100644 app/views/scaffolds/empty create mode 100755 app/views/signatures/add.ctp create mode 100755 app/views/signatures/edit.ctp create mode 100755 app/views/signatures/index.ctp create mode 100755 app/views/signatures/search.ctp create mode 100755 app/views/signatures/view.ctp create mode 100755 app/views/users/add.ctp create mode 100755 app/views/users/edit.ctp create mode 100755 app/views/users/index.ctp create mode 100755 app/views/users/login.ctp create mode 100755 app/views/users/view.ctp create mode 100644 app/webroot/.htaccess create mode 100644 app/webroot/css.php create mode 100644 app/webroot/css/cake.generic.css create mode 100644 app/webroot/favicon.ico create mode 100644 app/webroot/files/empty create mode 100644 app/webroot/gpg.asc create mode 100644 app/webroot/img/cake.icon.png create mode 100644 app/webroot/img/cake.power.gif create mode 100644 app/webroot/img/test-error-icon.png create mode 100644 app/webroot/img/test-fail-icon.png create mode 100644 app/webroot/img/test-pass-icon.png create mode 100644 app/webroot/img/test-skip-icon.png create mode 100644 app/webroot/index.php create mode 100644 app/webroot/js/empty create mode 100644 app/webroot/js/jquery.js create mode 100644 app/webroot/robots.txt create mode 100644 app/webroot/test.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..824dfebe4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/app/tmp +/plugins +/vendors +/.project +.DS_Store +/cake +/index.php +/README diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 000000000..0ed8662ea --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,5 @@ + + RewriteEngine on + RewriteRule ^$ webroot/ [L] + RewriteRule (.*) webroot/$1 [L] + \ No newline at end of file diff --git a/app/README.txt b/app/README.txt new file mode 100644 index 000000000..8d2419784 --- /dev/null +++ b/app/README.txt @@ -0,0 +1,29 @@ + +TODOs +----- +Contact reporter +- allow custom message + +Signature +- add "no-ids-signature" option + +implement auditing/logging system +- add / edit events and signatures +- failed / success logins (with source IP, headers,...) + + + +INSTALLATION INSTRUCTIONS +------------------------- +First you need to edit the files in the /app/config directory. +# (or copy your local config settings including the salts and passwords) +# cp app/config/* /Users/chri/tmp/sshfs/sig/app/config/ + +Then set the permissions correctly using the following commands as root: +chown -R chri:www-data sig +chmod -R 750 sig +chmod -R g+s sig +cd sig/app/ +chmod -R g+w tmp + + diff --git a/app/app_controller.php b/app/app_controller.php new file mode 100755 index 000000000..0d4bf0f16 --- /dev/null +++ b/app/app_controller.php @@ -0,0 +1,272 @@ +Auth->authorize = 'actions'; + $this->Auth->userModel = 'User'; + $this->Auth->fields = array('username' => 'email', 'password' => 'password'); + $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login'); + $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login'); + $this->Auth->loginRedirect = array('controller' => 'events', 'action' => 'index'); + + $this->Auth->actionPath = 'controllers/'; + + $this->Auth->allowedActions = array('build_acl', 'initDB'); // FIXME remove build_acl + + } + + + /** + * Convert an array to the same array but with the values also as index instead of an interface_exists + */ + function arrayToValuesIndexArray($old_array) { + $new_array = Array(); + foreach ($old_array as $value) + $new_array[$value] = $value; + return $new_array; + } + + /** + * checks if the currently logged user is an administrator + */ + function isAdmin() { + // TODO group membership should be checked correctly. + // but as quick workaround I check if the group_id = 1 = admin + $user = $this->Auth->user(); + + if (1 == $user['User']['group_id']) + return true; + else + return false; + } + + + + + + /** + * These functions will look at every controller in your application. + * It will add any non-private, non Controller methods to the Acl table, + * nicely nested underneath the owning controller. You can add and run this + * in your AppController or any controller for that matter, just be sure to + * remove it before putting your application into production. + * Now run the action in your browser, eg. http://localhost/groups/build_acl, + * This will build your ACO table. + */ + + function build_acl() { + if (!Configure::read('debug')) { + return $this->_stop(); + } + $log = array(); + + $aco =& $this->Acl->Aco; + $root = $aco->node('controllers'); + if (!$root) { + $aco->create(array('parent_id' => null, 'model' => null, 'alias' => 'controllers')); + $root = $aco->save(); + $root['Aco']['id'] = $aco->id; + $log[] = 'Created Aco node for controllers'; + } else { + $root = $root[0]; + } + + App::import('Core', 'File'); + $Controllers = App::objects('controller'); + $appIndex = array_search('App', $Controllers); + if ($appIndex !== false ) { + unset($Controllers[$appIndex]); + } + $baseMethods = get_class_methods('Controller'); + $baseMethods[] = 'build_acl'; + + $Plugins = $this->_getPluginControllerNames(); + $Controllers = array_merge($Controllers, $Plugins); + + // look at each controller in app/controllers + foreach ($Controllers as $ctrlName) { + $methods = $this->_getClassMethods($this->_getPluginControllerPath($ctrlName)); + + // Do all Plugins First + if ($this->_isPlugin($ctrlName)){ + $pluginNode = $aco->node('controllers/'.$this->_getPluginName($ctrlName)); + if (!$pluginNode) { + $aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginName($ctrlName))); + $pluginNode = $aco->save(); + $pluginNode['Aco']['id'] = $aco->id; + $log[] = 'Created Aco node for ' . $this->_getPluginName($ctrlName) . ' Plugin'; + } + } + // find / make controller node + $controllerNode = $aco->node('controllers/'.$ctrlName); + if (!$controllerNode) { + if ($this->_isPlugin($ctrlName)){ + $pluginNode = $aco->node('controllers/' . $this->_getPluginName($ctrlName)); + $aco->create(array('parent_id' => $pluginNode['0']['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginControllerName($ctrlName))); + $controllerNode = $aco->save(); + $controllerNode['Aco']['id'] = $aco->id; + $log[] = 'Created Aco node for ' . $this->_getPluginControllerName($ctrlName) . ' ' . $this->_getPluginName($ctrlName) . ' Plugin Controller'; + } else { + $aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $ctrlName)); + $controllerNode = $aco->save(); + $controllerNode['Aco']['id'] = $aco->id; + $log[] = 'Created Aco node for ' . $ctrlName; + } + } else { + $controllerNode = $controllerNode[0]; + } + + //clean the methods. to remove those in Controller and private actions. + foreach ($methods as $k => $method) { + if (strpos($method, '_', 0) === 0) { + unset($methods[$k]); + continue; + } + if (in_array($method, $baseMethods)) { + unset($methods[$k]); + continue; + } + $methodNode = $aco->node('controllers/'.$ctrlName.'/'.$method); + if (!$methodNode) { + $aco->create(array('parent_id' => $controllerNode['Aco']['id'], 'model' => null, 'alias' => $method)); + $methodNode = $aco->save(); + $log[] = 'Created Aco node for '. $method; + } + } + } + if(count($log)>0) { + debug($log); + } + } + + function _getClassMethods($ctrlName = null) { + App::import('Controller', $ctrlName); + if (strlen(strstr($ctrlName, '.')) > 0) { + // plugin's controller + $num = strpos($ctrlName, '.'); + $ctrlName = substr($ctrlName, $num+1); + } + $ctrlclass = $ctrlName . 'Controller'; + $methods = get_class_methods($ctrlclass); + + // Add scaffold defaults if scaffolds are being used + $properties = get_class_vars($ctrlclass); + if (array_key_exists('scaffold',$properties)) { + if($properties['scaffold'] == 'admin') { + $methods = array_merge($methods, array('admin_add', 'admin_edit', 'admin_index', 'admin_view', 'admin_delete')); + } else { + $methods = array_merge($methods, array('add', 'edit', 'index', 'view', 'delete')); + } + } + return $methods; + } + + function _isPlugin($ctrlName = null) { + $arr = String::tokenize($ctrlName, '/'); + if (count($arr) > 1) { + return true; + } else { + return false; + } + } + + function _getPluginControllerPath($ctrlName = null) { + $arr = String::tokenize($ctrlName, '/'); + if (count($arr) == 2) { + return $arr[0] . '.' . $arr[1]; + } else { + return $arr[0]; + } + } + + function _getPluginName($ctrlName = null) { + $arr = String::tokenize($ctrlName, '/'); + if (count($arr) == 2) { + return $arr[0]; + } else { + return false; + } + } + + function _getPluginControllerName($ctrlName = null) { + $arr = String::tokenize($ctrlName, '/'); + if (count($arr) == 2) { + return $arr[1]; + } else { + return false; + } + } + + /** + * Get the names of the plugin controllers ... + * + * This function will get an array of the plugin controller names, and + * also makes sure the controllers are available for us to get the + * method names by doing an App::import for each plugin controller. + * + * @return array of plugin names. + * + */ + function _getPluginControllerNames() { + App::import('Core', 'File', 'Folder'); + $paths = Configure::getInstance(); + $folder =& new Folder(); + $folder->cd(APP . 'plugins'); + + // Get the list of plugins + $Plugins = $folder->read(); + $Plugins = $Plugins[0]; + $arr = array(); + + // Loop through the plugins + foreach($Plugins as $pluginName) { + // Change directory to the plugin + $didCD = $folder->cd(APP . 'plugins'. DS . $pluginName . DS . 'controllers'); + // Get a list of the files that have a file name that ends + // with controller.php + $files = $folder->findRecursive('.*_controller\.php'); + + // Loop through the controllers we found in the plugins directory + foreach($files as $fileName) { + // Get the base file name + $file = basename($fileName); + + // Get the controller name + $file = Inflector::camelize(substr($file, 0, strlen($file)-strlen('_controller.php'))); + if (!preg_match('/^'. Inflector::humanize($pluginName). 'App/', $file)) { + if (!App::import('Controller', $pluginName.'.'.$file)) { + debug('Error importing '.$file.' for plugin '.$pluginName); + } else { + /// Now prepend the Plugin name ... + // This is required to allow us to fetch the method names. + $arr[] = Inflector::humanize($pluginName) . "/" . $file; + } + } + } + } + return $arr; + } + + + + + + + + + + + + + + +} +?> diff --git a/app/app_error.php b/app/app_error.php new file mode 100755 index 000000000..63ecc68f9 --- /dev/null +++ b/app/app_error.php @@ -0,0 +1,27 @@ +controller->here; + } + $url = Router::normalize($url); + $this->controller->header("HTTP/1.0 403 Forbidden"); + $this->controller->set(array( + 'code' => '403', + 'name' => __('Forbidden', true), + 'message' => $message, + 'base' => $this->controller->base + )); + $this->_outputMessage('error403'); + } +} +?> diff --git a/app/config/acl.ini.php b/app/config/acl.ini.php new file mode 100644 index 000000000..2c68cb27e --- /dev/null +++ b/app/config/acl.ini.php @@ -0,0 +1,70 @@ +; +; SVN FILE: $Id$ +;/** +; * ACL configuration +; * +; * +; * PHP versions 4 and 5 +; * +; * CakePHP(tm) : Rapid Development Framework http://cakephp.org +; * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) +; * +; * Licensed under The MIT License +; * Redistributions of files must retain the above copyright notice. +; * +; * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) +; * @link http://cakephp.org CakePHP(tm) Project +; * @package cake +; * @subpackage cake.app.config +; * @since CakePHP(tm) v 0.10.0.1076 +; * @license MIT License (http://www.opensource.org/licenses/mit-license.php) +; */ + +; acl.ini.php - Cake ACL Configuration +; --------------------------------------------------------------------- +; Use this file to specify user permissions. +; aco = access control object (something in your application) +; aro = access request object (something requesting access) +; +; User records are added as follows: +; +; [uid] +; groups = group1, group2, group3 +; allow = aco1, aco2, aco3 +; deny = aco4, aco5, aco6 +; +; Group records are added in a similar manner: +; +; [gid] +; allow = aco1, aco2, aco3 +; deny = aco4, aco5, aco6 +; +; The allow, deny, and groups sections are all optional. +; NOTE: groups names *cannot* ever be the same as usernames! +; +; ACL permissions are checked in the following order: +; 1. Check for user denies (and DENY if specified) +; 2. Check for user allows (and ALLOW if specified) +; 3. Gather user's groups +; 4. Check group denies (and DENY if specified) +; 5. Check group allows (and ALLOW if specified) +; 6. If no aro, aco, or group information is found, DENY +; +; --------------------------------------------------------------------- + +;------------------------------------- +;Users +;------------------------------------- + +[username-goes-here] +groups = group1, group2 +deny = aco1, aco2 +allow = aco3, aco4 + +;------------------------------------- +;Groups +;------------------------------------- + +[groupname-goes-here] +deny = aco5, aco6 +allow = aco7, aco8 \ No newline at end of file diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php new file mode 100755 index 000000000..4c292fb87 --- /dev/null +++ b/app/config/bootstrap.php @@ -0,0 +1,58 @@ + array('/full/path/to/plugins/', '/next/full/path/to/plugins/'), + * 'models' => array('/full/path/to/models/', '/next/full/path/to/models/'), + * 'views' => array('/full/path/to/views/', '/next/full/path/to/views/'), + * 'controllers' => array('/full/path/to/controllers/', '/next/full/path/to/controllers/'), + * 'datasources' => array('/full/path/to/datasources/', '/next/full/path/to/datasources/'), + * 'behaviors' => array('/full/path/to/behaviors/', '/next/full/path/to/behaviors/'), + * 'components' => array('/full/path/to/components/', '/next/full/path/to/components/'), + * 'helpers' => array('/full/path/to/helpers/', '/next/full/path/to/helpers/'), + * 'vendors' => array('/full/path/to/vendors/', '/next/full/path/to/vendors/'), + * 'shells' => array('/full/path/to/shells/', '/next/full/path/to/shells/'), + * 'locales' => array('/full/path/to/locale/', '/next/full/path/to/locale/') + * )); + * + */ + +/** + * As of 1.3, additional rules for the inflector are added below + * + * Inflector::rules('singular', array('rules' => array(), 'irregular' => array(), 'uninflected' => array())); + * Inflector::rules('plural', array('rules' => array(), 'irregular' => array(), 'uninflected' => array())); + * + */ + + +Configure::write('Recaptcha.publicKey', ''); +Configure::write('Recaptcha.privateKey', '); + +Configure::write('GnuPG.email', 'sig@cyber-defence.be'); +Configure::write('GnuPG.password', ''); +// Configure::write('GnuPG.homedir', ''); // LATER let the user chose the gnupg homedir using putenv('GNUPGHOME=/home/sender/.gnupg'); diff --git a/app/config/core.php b/app/config/core.php new file mode 100755 index 000000000..c6e350b96 --- /dev/null +++ b/app/config/core.php @@ -0,0 +1,316 @@ +cacheAction = true. + * + */ + //Configure::write('Cache.check', true); + +/** + * Defines the default error type when using the log() function. Used for + * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG. + */ + define('LOG_ERROR', 2); + +/** + * The preferred session handling method. Valid values: + * + * 'php' Uses settings defined in your php.ini. + * 'cake' Saves session files in CakePHP's /tmp directory. + * 'database' Uses CakePHP's database sessions. + * + * To define a custom session handler, save it at /app/config/.php. + * Set the value of 'Session.save' to to utilize it in CakePHP. + * + * To use database sessions, run the app/config/schema/sessions.php schema using + * the cake shell command: cake schema create Sessions + * + */ + Configure::write('Session.save', 'cake'); + +/** + * The model name to be used for the session model. + * + * 'Session.save' must be set to 'database' in order to utilize this constant. + * + * The model name set here should *not* be used elsewhere in your application. + */ + //Configure::write('Session.model', 'Session'); + +/** + * The name of the table used to store CakePHP database sessions. + * + * 'Session.save' must be set to 'database' in order to utilize this constant. + * + * The table name set here should *not* include any table prefix defined elsewhere. + * + * Please note that if you set a value for Session.model (above), any value set for + * Session.table will be ignored. + * + * [Note: Session.table is deprecated as of CakePHP 1.3] + */ + //Configure::write('Session.table', 'cake_sessions'); + +/** + * The DATABASE_CONFIG::$var to use for database session handling. + * + * 'Session.save' must be set to 'database' in order to utilize this constant. + */ + //Configure::write('Session.database', 'default'); + +/** + * The name of CakePHP's session cookie. + * + * Note the guidelines for Session names states: "The session name references + * the session id in cookies and URLs. It should contain only alphanumeric + * characters." + * @link http://php.net/session_name + */ + Configure::write('Session.cookie', 'CYDEFSIG_SESS'); + +/** + * Session time out time (in seconds). + * Actual value depends on 'Security.level' setting. + */ + Configure::write('Session.timeout', '120'); + +/** + * If set to false, sessions are not automatically started. + */ + Configure::write('Session.start', true); + +/** + * When set to false, HTTP_USER_AGENT will not be checked + * in the session. You might want to set the value to false, when dealing with + * older versions of IE, Chrome Frame or certain web-browsing devices and AJAX + */ + Configure::write('Session.checkAgent', true); + +/** + * The level of CakePHP security. The session timeout time defined + * in 'Session.timeout' is multiplied according to the settings here. + * Valid values: + * + * 'high' Session timeout in 'Session.timeout' x 10 + * 'medium' Session timeout in 'Session.timeout' x 100 + * 'low' Session timeout in 'Session.timeout' x 300 + * + * CakePHP session IDs are also regenerated between requests if + * 'Security.level' is set to 'high'. + */ + Configure::write('Security.level', 'medium'); + +/** + * A random string used in security hashing methods. + */ + Configure::write('Security.salt', 'Rooraenietu8Eeyo 0. Set to 'force' to always enable + * timestamping regardless of debug value. + */ + //Configure::write('Asset.timestamp', true); +/** + * Compress CSS output by removing comments, whitespace, repeating tags, etc. + * This requires a/var/cache directory to be writable by the web server for caching. + * and /vendors/csspp/csspp.php + * + * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use HtmlHelper::css(). + */ + //Configure::write('Asset.filter.css', 'css.php'); + +/** + * Plug in your own custom JavaScript compressor by dropping a script in your webroot to handle the + * output, and setting the config below to the name of the script. + * + * To use, prefix your JavaScript link URLs with '/cjs/' instead of '/js/' or use JavaScriptHelper::link(). + */ + //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php'); + +/** + * The classname and database used in CakePHP's + * access control lists. + */ + Configure::write('Acl.classname', 'DbAcl'); + Configure::write('Acl.database', 'default'); + +/** + * If you are on PHP 5.3 uncomment this line and correct your server timezone + * to fix the date & time related errors. + */ + //date_default_timezone_set('UTC'); + +/** + * + * Cache Engine Configuration + * Default settings provided below + * + * File storage engine. + * + * Cache::config('default', array( + * 'engine' => 'File', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'path' => CACHE, //[optional] use system tmp directory - remember to use absolute path + * 'prefix' => 'cake_', //[optional] prefix every cache file with this string + * 'lock' => false, //[optional] use file locking + * 'serialize' => true, [optional] + * )); + * + * + * APC (http://pecl.php.net/package/APC) + * + * Cache::config('default', array( + * 'engine' => 'Apc', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * )); + * + * Xcache (http://xcache.lighttpd.net/) + * + * Cache::config('default', array( + * 'engine' => 'Xcache', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * 'user' => 'user', //user from xcache.admin.user settings + * 'password' => 'password', //plaintext password (xcache.admin.pass) + * )); + * + * + * Memcache (http://www.danga.com/memcached/) + * + * Cache::config('default', array( + * 'engine' => 'Memcache', //[required] + * 'duration'=> 3600, //[optional] + * 'probability'=> 100, //[optional] + * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string + * 'servers' => array( + * '127.0.0.1:11211' // localhost, default port 11211 + * ), //[optional] + * 'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory) + * 'persistent' => true, // [optional] set this to false for non-persistent connections + * )); + * + */ + Cache::config('default', array('engine' => 'File')); diff --git a/app/config/database.php b/app/config/database.php new file mode 100755 index 000000000..ccd641e51 --- /dev/null +++ b/app/config/database.php @@ -0,0 +1,86 @@ + The name of a supported driver; valid options are as follows: + * mysql - MySQL 4 & 5, + * mysqli - MySQL 4 & 5 Improved Interface (PHP5 only), + * sqlite - SQLite (PHP5 only), + * postgres - PostgreSQL 7 and higher, + * mssql - Microsoft SQL Server 2000 and higher, + * db2 - IBM DB2, Cloudscape, and Apache Derby (http://php.net/ibm-db2) + * oracle - Oracle 8 and higher + * firebird - Firebird/Interbase + * sybase - Sybase ASE + * adodb-[drivername] - ADOdb interface wrapper (see below), + * odbc - ODBC DBO driver + * + * You can add custom database drivers (or override existing drivers) by adding the + * appropriate file to app/models/datasources/dbo. Drivers should be named 'dbo_x.php', + * where 'x' is the name of the database. + * + * persistent => true / false + * Determines whether or not the database should use a persistent connection + * + * connect => + * ADOdb set the connect to one of these + * (http://phplens.com/adodb/supported.databases.html) and + * append it '|p' for persistent connection. (mssql|p for example, or just mssql for not persistent) + * For all other databases, this setting is deprecated. + * + * host => + * the host you connect to the database. To add a socket or port number, use 'port' => # + * + * prefix => + * Uses the given prefix for all the tables in this database. This setting can be overridden + * on a per-table basis with the Model::$tablePrefix property. + * + * schema => + * For Postgres and DB2, specifies which schema you would like to use the tables in. Postgres defaults to + * 'public', DB2 defaults to empty. + * + * encoding => + * For MySQL, MySQLi, Postgres and DB2, specifies the character encoding to use when connecting to the + * database. Uses database default. + * + */ +class DATABASE_CONFIG { + + var $default = array( + 'driver' => 'mysql', + 'persistent' => false, + 'host' => 'localhost', + 'login' => 'cyberdefence', + 'password' => '', + 'database' => 'cyberdefence_sig', + 'prefix' => '', + ); + +} diff --git a/app/config/routes.php b/app/config/routes.php new file mode 100755 index 000000000..0269e4e9e --- /dev/null +++ b/app/config/routes.php @@ -0,0 +1,35 @@ + 'pages', 'action' => 'display', 'home')); +/** + * ...and connect the rest of 'Pages' controller's urls. + */ +// Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + +Router::connect('/', array('controller' => 'events', 'action' => 'index')); \ No newline at end of file diff --git a/app/config/schema/db_acl.php b/app/config/schema/db_acl.php new file mode 100644 index 000000000..2e3546f4c --- /dev/null +++ b/app/config/schema/db_acl.php @@ -0,0 +1,73 @@ + array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'model' => array('type'=>'string', 'null' => true), + 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'alias' => array('type'=>'string', 'null' => true), + 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + + var $aros = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'parent_id' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'model' => array('type'=>'string', 'null' => true), + 'foreign_key' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'alias' => array('type'=>'string', 'null' => true), + 'lft' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'rght' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 10), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + + var $aros_acos = array( + 'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'aro_id' => array('type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index'), + 'aco_id' => array('type'=>'integer', 'null' => false, 'length' => 10), + '_create' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_read' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_update' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + '_delete' => array('type'=>'string', 'null' => false, 'default' => '0', 'length' => 2), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'ARO_ACO_KEY' => array('column' => array('aro_id', 'aco_id'), 'unique' => 1)) + ); + +} diff --git a/app/config/schema/i18n.php b/app/config/schema/i18n.php new file mode 100644 index 000000000..2c578fadc --- /dev/null +++ b/app/config/schema/i18n.php @@ -0,0 +1,50 @@ + array('type'=>'integer', 'null' => false, 'default' => NULL, 'length' => 10, 'key' => 'primary'), + 'locale' => array('type'=>'string', 'null' => false, 'length' => 6, 'key' => 'index'), + 'model' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'foreign_key' => array('type'=>'integer', 'null' => false, 'length' => 10, 'key' => 'index'), + 'field' => array('type'=>'string', 'null' => false, 'key' => 'index'), + 'content' => array('type'=>'text', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'locale' => array('column' => 'locale', 'unique' => 0), 'model' => array('column' => 'model', 'unique' => 0), 'row_id' => array('column' => 'foreign_key', 'unique' => 0), 'field' => array('column' => 'field', 'unique' => 0)) + ); + +} diff --git a/app/config/schema/sessions.php b/app/config/schema/sessions.php new file mode 100644 index 000000000..287e43dd7 --- /dev/null +++ b/app/config/schema/sessions.php @@ -0,0 +1,47 @@ + array('type'=>'string', 'null' => false, 'key' => 'primary'), + 'data' => array('type'=>'text', 'null' => true, 'default' => NULL), + 'expires' => array('type'=>'integer', 'null' => true, 'default' => NULL), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) + ); + +} diff --git a/app/controllers/components/empty b/app/controllers/components/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/controllers/events_controller.php b/app/controllers/events_controller.php new file mode 100755 index 000000000..a044b3019 --- /dev/null +++ b/app/controllers/events_controller.php @@ -0,0 +1,559 @@ + 50, + 'order' => array( + 'Event.date' => 'DESC' + ) + ); + var $components = array('Security', 'Email'); + + function beforeFilter() { + $this->Auth->allow('xml'); + $this->Auth->allow('snort'); + + //$this->Security->requirePost('delete'); // FIXME do this for every controller and fix the urls in the pages + + // These variables are required for every view + $me_user = $this->Auth->user(); + $this->set('me', $me_user['User']); + $this->set('isAdmin', $this->isAdmin()); + } + + + function index() { + // list the events + $this->Event->recursive = 0; + $this->set('events', $this->paginate()); + + if (empty($me_user['User']['gpgkey'])) { + $this->Session->setFlash(__('No GPG key set in your profile. To get encrypted emails, please submit your public key in your profile.', true)); + } + } + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid event', true)); + $this->redirect(array('action' => 'index')); + } + $this->set('event', $this->Event->read(null, $id)); + } + + function add() { + $user = $this->Auth->user(); + if (!empty($this->data)) { + // force check userid and orgname if its from yourself + $this->data['Event']['user_id'] = $user['User']['id']; + $this->data['Event']['org'] = $user['User']['org']; + $this->Event->create(); + if ($this->Event->save($this->data)) { + $this->Session->setFlash(__('The event has been saved', true)); + $this->redirect(array('action' => 'view', $this->Event->getId())); + } else { + $this->Session->setFlash(__('The event could not be saved. Please, try again.', true)); + } + } + + // combobox for risks + $risks = $this->Event->validate['risk']['allowedChoice']['rule'][1]; + $risks = $this->arrayToValuesIndexArray($risks); + $this->set('risks',compact('risks')); + } + + function edit($id = null) { + if (!$id && empty($this->data)) { + $this->Session->setFlash(__('Invalid event', true)); + $this->redirect(array('action' => 'index')); + } + // only edit own events + $user = $this->Auth->user(); + $old_event = $this->Event->read(null, $id); + if (!$this->isAdmin() && $user['User']['org'] != $old_event['Event']['org']) { + $this->Session->setFlash(__('You can only edit events from your organisation.', true)); + $this->redirect(array('action' => 'view', $id)); + } + // form submit + if (!empty($this->data)) { + // always force the user and org, but do not force it for admins + if (!$this->isAdmin()) { + $this->data['Event']['user_id'] = $user['User']['id']; + $this->data['Event']['org'] = $user['User']['org']; + } + // we probably also want to remove the alerted flag + $this->data['Event']['alerted'] = 0; + + if ($this->Event->save($this->data)) { + // redirect + $this->Session->setFlash(__('The event has been saved', true)); + $this->redirect(array('action' => 'view', $id)); + } else { + $this->Session->setFlash(__('The event could not be saved. Please, try again.', true)); + } + } + // no form submit + if (empty($this->data)) { + $this->data = $this->Event->read(null, $id); + } + + // combobox for types + $risks = $this->Event->validate['risk']['allowedChoice']['rule'][1]; + $risks = $this->arrayToValuesIndexArray($risks); + $this->set('risks',compact('risks')); + } + + function delete($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid id for event', true)); + $this->redirect(array('action'=>'index')); + } + // only delete own events + $user = $this->Auth->user(); + $old_event = $this->Event->read(null, $id); + if (!$this->isAdmin() && $user['User']['org'] != $old_event['Event']['org']) { + $this->Session->setFlash(__('You can only delete events from your organisation.', true)); + $this->redirect(array('action' => 'view', $id)); + } + // delete event or throw error + if ($this->Event->delete($id)) { + $this->Session->setFlash(__('Event deleted', true)); + $this->redirect(array('action'=>'index')); + } + $this->Session->setFlash(__('Event was not deleted', true)); + $this->redirect(array('action' => 'index')); + } + + + /** + * Send out an alert email to all the users that wanted to be notified. + * Users with a GPG key will get the mail encrypted, other users will get the mail unencrypted + */ + function alert($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid id for event', true)); + $this->redirect(array('action'=>'index')); + } + // only alert own events + $user = $this->Auth->user(); + $old_event = $this->Event->read(null, $id); + if (!$this->isAdmin() && $user['User']['org'] != $old_event['Event']['org']) { + $this->Session->setFlash(__('You can only send alerts for events from your organisation.', true)); + $this->redirect(array('action' => 'view', $id)); + } + + // fetch the event + $event = $this->Event->read(null, $id); + if (1 == $event['Event']['alerted']) { + $this->Session->setFlash(__('Everyone has already been alerted for this event. To try again, first edit it.', true)); + $this->redirect(array('action' => 'view', $id)); + } + + $body = ""; + $appendlen = 20; + $body = 'Event : '.$event['Event']['id']."\n"; + $body .= 'Date : '.$event['Event']['date']."\n"; + $body .= 'Reported by : '.Sanitize::html($event['Event']['org'])."\n"; + $body .= 'Risk : '.$event['Event']['risk']."\n"; + $body .= 'Signatures :'."\n"; + if (!empty($event['Signature'])) { + $i = 0; + foreach ($event['Signature'] as $signature){ + $body .= ' - '.$signature['type'].str_repeat(' ', $appendlen - 2 - strlen( $signature['type'])).': '.Sanitize::html($signature['value'])."\n"; + } + } + $body .= 'Extra info : '."\n"; + $body .= Sanitize::html($event['Event']['info']); + + // sign the body + require_once 'Crypt/GPG.php'; + $gpg = new Crypt_GPG(); + $gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); + $body_signed = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR); + + + $this->loadModel('Users'); + + // + // build a list of the recipients that get a non-encrypted mail + // + $alert_users = $this->Users->find('all', array( + 'conditions' => array('Users.autoalert' => 1, + 'Users.gpgkey =' => ""), + 'recursive' => 0, + ) ); + $alert_emails = Array(); + foreach ($alert_users as $user) { + $alert_emails[] = $user['Users']['email']; + } + // prepare the the unencrypted email + $this->Email->from = "CyDefSIG "; + $this->Email->to = "CyDefSIG "; + $this->Email->return = "sig@cyber-defence.be"; + $this->Email->bcc = $alert_emails; + $this->Email->subject = "[CyDefSIG] Event ".$id." - ".$event['Event']['risk']." - TLP Amber"; + //$this->Email->delivery = 'debug'; // do not really send out mails, only display it on the screen + $this->Email->template = 'body'; + $this->Email->sendAs = 'text'; // both text or html + $this->set('body', $body_signed); + // send it + $this->Email->send(); + // If you wish to send multiple emails using a loop, you'll need + // to reset the email fields using the reset method of the Email component. + $this->Email->reset(); + + + // + // Build a list of the recipients that wish to receive encrypted mails. + // + $alert_users = $this->Users->find('all', array( + 'conditions' => array('Users.autoalert' => 1, + 'Users.gpgkey !=' => ""), + 'recursive' => 0, + ) ); + // encrypt the mail for each user and send it separately + foreach ($alert_users as $user) { + // send the email + $this->Email->from = "CyDefSIG "; + $this->Email->to = "<".$user['Users']['email'].">"; + $this->Email->return = "sig@cyber-defence.be"; + $this->Email->subject = "[CyDefSIG] Event ".$id." - ".$event['Event']['risk']; + //$this->Email->delivery = 'debug'; // do not really send out mails, only display it on the screen + $this->Email->template = 'body'; + $this->Email->sendAs = 'text'; // both text or html + + // import the key of the user into the keyring // LATER do that when the user uploads a new key, but don't forget to remove the old keys before + $key_import_output = $gpg->importKey($user['Users']['gpgkey']); + // say what key should be used to encrypt + $gpg = new Crypt_GPG(); + //$gpg->addEncryptKey($user['Users']['email']); + $gpg->addEncryptKey($key_import_output['fingerprint']); // use the key that was given in the import + + $body_enc_sig = $gpg->encrypt($body_signed, true); + + $this->set('body', $body_enc_sig); + //debug($body_enc_sig); + $this->Email->send(); + // If you wish to send multiple emails using a loop, you'll need + // to reset the email fields using the reset method of the Email component. + $this->Email->reset(); + } + + + + // update the DB to set the alerted flag + $this->Event->set('alerted', 1); + $this->Event->save(); + + // redirect to the view event page + $this->Session->setFlash(__('Email sent to all participants.', true)); + $this->redirect(array('action' => 'view', $id)); + } + + + /** + * Send out an contact email to the person who posted the event. + * Users with a GPG key will get the mail encrypted, other users will get the mail unencrypted + * @todo allow the user to enter a comment in the contact email. + */ + function contact($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid id for event', true)); + $this->redirect(array('action'=>'index')); + } + + // fetch the event + $event = $this->Event->read(null, $id); + $reporter = $event['User']; // email, gpgkey + + $me_user = $this->Auth->user(); + $me_user = $me_user['User']; // email, gpgkey + + $body = ""; + $body .="Hello, \n"; + $body .="\n"; + $body .="Someone wants to get in touch with you concerning a CyDefSIG event. \n"; + $body .="\n"; + $body .="You can reach him at ".$me_user['email']."\n"; + if (!empty($me_user['gpgkey'])) + $body .="His GPG/PGP key is added as attachment to this email. \n"; + $body .="\n"; + $body .="The event is the following: \n"; + + // print the event in mail-format + // LATER place event-to-email-layout in a function + $appendlen = 20; + $body .= 'Event : '.$event['Event']['id']."\n"; + $body .= 'Date : '.$event['Event']['date']."\n"; + $body .= 'Reported by : '.Sanitize::html($event['Event']['org'])."\n"; + $body .= 'Risk : '.$event['Event']['risk']."\n"; + $body .= 'Signatures :'."\n"; + if (!empty($event['Signature'])) { + $i = 0; + foreach ($event['Signature'] as $signature){ + $body .= ' - '.$signature['type'].str_repeat(' ', $appendlen - 2 - strlen( $signature['type'])).': '.Sanitize::html($signature['value'])."\n"; + } + } + $body .= 'Extra info : '."\n"; + $body .= Sanitize::html($event['Event']['info']); + + // sign the body + require_once 'Crypt/GPG.php'; + $gpg = new Crypt_GPG(); + $gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); + $body_signed = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR); + + if (!empty($reporter['gpgkey'])) { + // import the key of the user into the keyring + // this isn't really necessary, but it gives it the fingerprint necessary for the next step + $key_import_output = $gpg->importKey($reporter['gpgkey']); + // say what key should be used to encrypt + $gpg = new Crypt_GPG(); + $gpg->addEncryptKey($key_import_output['fingerprint']); // use the key that was given in the import + + $body_enc_sig = $gpg->encrypt($body_signed, true); + } else { + $body_enc_sig = $body_signed; + } + + // prepare the email + $this->Email->from = "CyDefSIG "; + $this->Email->to = "<".$reporter['email'].">"; + $this->Email->subject = "[CyDefSIG] Need info about event ".$id." - TLP Amber"; + //$this->Email->delivery = 'debug'; // do not really send out mails, only display it on the screen + $this->Email->template = 'body'; + $this->Email->sendAs = 'text'; // both text or html + $this->set('body', $body_enc_sig); + + // Add the GPG key of the user as attachment + // LATER sign the attached GPG key + if (!empty($me_user['gpgkey'])) { + // save the gpg key to a temporary file + $tmpfname = tempnam(TMP, "GPGkey"); + $handle = fopen($tmpfname, "w"); + fwrite($handle, $me_user['gpgkey']); + fclose($handle); + // attach it + $this->Email->attachments = array( + 'gpgkey.asc' => $tmpfname + ); + } + + // send it + $this->Email->send(); + + // remove the temporary gpg file + if (!empty($me_user['gpgkey'])) + unlink($tmpfname); + + // redirect to the view event page + $this->Session->setFlash(__('Email sent to the reporter.', true)); + $this->redirect(array('action' => 'view', $id)); + + } + + + + function xml($key) { + // check if the key is valid -> search for users based on key + $this->loadModel('Users'); + // no input sanitization necessary, it's done by model + $user = $this->Users->findByAuthkey($key); + if (empty($user)) + $this->cakeError('error403', array('message' => 'Incorrect authentication key')); + // display the full xml + $this->header('Content-Type: text/xml'); // set the content type + $this->layout = 'xml/xml'; + $this->set('events', $this->Event->findAllByAlerted(1)); // find events that are finished + } + + function snort($key) { + // check if the key is valid -> search for users based on key + $this->loadModel('Users'); + $this->loadModel('Signatures'); + // no input sanitization necessary, it's done by model + $user = $this->Users->findByAuthkey($key); + if (empty($user)) + $this->cakeError('error403', array('message' => 'Incorrect authentication key')); + // display the full snort rulebase + $this->layout = 'xml/xml'; // LATER better layout than xml + + $rules= array(); + + // find events that are finished + $events = $this->Event->findAllByAlerted(1); + + + foreach ($events as $event) { + # proto src_ip src_port direction dst_ip dst_port msg rule_content tag sid rev + $rule_format = 'alert %s %s %s %s %s %s (msg: "CyDefSIG %s, Event '.$event['Event']['id'].', '.$event['Event']['risk'].'"; %s %s classtype:targeted-attack; sid:%d; rev:%d; reference:url,sig.cyber-defence.be/events/'.$event['Event']['id'].';) '; + + $sid = 3000000+($event['Event']['id']*100); // LATER this will cause issues with events containing more than 99 signatures + //debug($event); + foreach ($event['Signature'] as $signature) { + $sid++; + switch ($signature['type']) { + // LATER test all the snort signatures + // LATER add the tag keyword in the rules to capture network traffic + // LATER sanitize every $signature['value'] to not conflict with snort + case 'ip-dst': + $rules[] = sprintf($rule_format, + 'ip', // proto + '$HOME_NET', // src_ip + 'any', // src_port + '->', // direction + $signature['value'], // dst_ip + 'any', // dst_port + 'Outgoing To Bad IP '.$signature['value'], // msg + '', // rule_content + '', // tag + $sid, // sid + 1 // rev + ); + break; + case 'ip-src': + $rules[] = sprintf($rule_format, + 'ip', // proto + $signature['value'], // src_ip + 'any', // src_port + '->', // direction + '$HOME_NET', // dst_ip + 'any', // dst_port + 'Incoming From Bad IP '.$signature['value'], // msg + '', // rule_content + '', // tag + $sid, // sid + 1 // rev + ); + break; + case 'email-src': + $rules[] = sprintf($rule_format, + 'tcp', // proto + '$EXTERNAL_NET', // src_ip + 'any', // src_port + '<>', // direction + '$SMTP_SERVERS', // dst_ip + '25', // dst_port + 'Bad Source Email Address '.$signature['value'], // msg + 'flow:established,to_server; content:"MAIL FROM|3a|"; nocase; content:"'.$signature['value'].'"; nocase;', // rule_content + 'tag:session,600,seconds;', // tag + $sid, // sid + 1 // rev + ); + break; + case 'email-dst': + $rules[] = sprintf($rule_format, + 'tcp', // proto + '$EXTERNAL_NET', // src_ip + 'any', // src_port + '<>', // direction + '$SMTP_SERVERS', // dst_ip + '25', // dst_port + 'Bad Destination Email Address '.$signature['value'], // msg + 'flow:established,to_server; content:"RCPT TO|3a|"; nocase; content:"'.$signature['value'].'"; nocase;', // rule_content + 'tag:session,600,seconds;', // tag + $sid, // sid + 1 // rev + ); + break; + case 'email-subject': + // LATER email-subject rule might not match because of line-wrapping + $rules[] = sprintf($rule_format, + 'tcp', // proto + '$EXTERNAL_NET', // src_ip + 'any', // src_port + '<>', // direction + '$SMTP_SERVERS', // dst_ip + '25', // dst_port + 'Bad Email Subject '.$signature['value'], // msg + 'flow:established,to_server; content:"Subject|3a|"; nocase; content:"'.$signature['value'].'"; nocase;', // rule_content + 'tag:session,600,seconds;', // tag + $sid, // sid + 1 // rev + ); + break; + case 'email-attachment': + // LATER email-attachment rule might not match because of line-wrapping + $rules[] = sprintf($rule_format, + 'tcp', // proto + '$EXTERNAL_NET', // src_ip + 'any', // src_port + '<>', // direction + '$SMTP_SERVERS', // dst_ip + '25', // dst_port + 'Bad Email Attachment '.$signature['value'], // msg + 'flow:established,to_server; content:"Content-Disposition: attachment|3b| filename=|22|"; content:"'.$signature['value'].'|22|";', // rule_content // LATER test and finetune this snort rule https://secure.wikimedia.org/wikipedia/en/wiki/MIME#Content-Disposition + 'tag:session,600,seconds;', // tag + $sid, // sid + 1 // rev + ); + break; + case 'domain': + $rules[] = sprintf($rule_format, + 'udp', // proto + 'any', // src_ip + 'any', // src_port + '->', // direction + 'any', // dst_ip + '53', // dst_port + 'Lookup Of Bad Domain '.$signature['value'], // msg + 'content:"'.$signature['value'].'"; nocase;', // rule_content + '', // tag + $sid, // sid + 1 // rev + ); + $sid++; + $rules[] = sprintf($rule_format, + 'tcp', // proto + 'any', // src_ip + 'any', // src_port + '->', // direction + 'any', // dst_ip + '53', // dst_port + 'Lookup Of Bad Domain '.$signature['value'], // msg + 'content:"'.$signature['value'].'"; nocase;', // rule_content + '', // tag + $sid, // sid + 1 // rev + ); + $sid++; + //break; // domain should also detect the domain name in a url + case 'url': + $rules[] = sprintf($rule_format, + 'tcp', // proto + '$HOME_NET', // src_ip + 'any', // src_port + '->', // direction + '$EXTERNAL_NET', // dst_ip + '$HTTP_PORTS', // dst_port + 'Outgoing Bad HTTP URL '.$signature['value'], // msg + 'flow:to_server,established; uricontent:"'.$signature['value'].'"; nocase;', // rule_content + 'tag:session,600,seconds;', // tag + $sid, // sid + 1 // rev + ); + break; + case 'user-agent': + $rules[] = ""; + // TODO write snort user-agent rule + break; + default: + break; + } + + } + + } + print ("#

This part is not finished and might be buggy. Please report any issues.

\n"); + + print "#
 \n";
+        foreach ($rules as $rule)
+        print $rule."\n";
+        print "#
\n"; + + $this->set('rules', $rules); + + } + +} diff --git a/app/controllers/groups_controller.php b/app/controllers/groups_controller.php new file mode 100755 index 000000000..338b0a5a0 --- /dev/null +++ b/app/controllers/groups_controller.php @@ -0,0 +1,63 @@ +Group->recursive = 0; + $this->set('groups', $this->paginate()); + } + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid group', true)); + $this->redirect(array('action' => 'index')); + } + $this->set('group', $this->Group->read(null, $id)); + } + + function add() { + if (!empty($this->data)) { + $this->Group->create(); + if ($this->Group->save($this->data)) { + $this->Session->setFlash(__('The group has been saved', true)); + $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The group could not be saved. Please, try again.', true)); + } + } + } + + function edit($id = null) { + if (!$id && empty($this->data)) { + $this->Session->setFlash(__('Invalid group', true)); + $this->redirect(array('action' => 'index')); + } + if (!empty($this->data)) { + if ($this->Group->save($this->data)) { + $this->Session->setFlash(__('The group has been saved', true)); + $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The group could not be saved. Please, try again.', true)); + } + } + if (empty($this->data)) { + $this->data = $this->Group->read(null, $id); + } + } + + function delete($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid id for group', true)); + $this->redirect(array('action'=>'index')); + } + if ($this->Group->delete($id)) { + $this->Session->setFlash(__('Group deleted', true)); + $this->redirect(array('action'=>'index')); + } + $this->Session->setFlash(__('Group was not deleted', true)); + $this->redirect(array('action' => 'index')); + } + + +} diff --git a/app/controllers/relations_controller.php b/app/controllers/relations_controller.php new file mode 100644 index 000000000..e5ab0c555 --- /dev/null +++ b/app/controllers/relations_controller.php @@ -0,0 +1,37 @@ +Relations->find('all'); + debug($this); + + } + +// /** +// * Updates the relations table for a specific Signature +// * @param unknown_type $id +// */ +// function _updateForSignature($id) { +// // remove all entries in the relations table +// // remove all entries where signature_id +// // remove all entries where event_id + +// // search for similar signatures + + +// // create new entries +// } + + function _getRelationsForEvent($id) { + // get relations_id from Relations for event_id + + // get event_id[] from Relations for relations_id + + // perhaps write a subquery ? + + } + +} diff --git a/app/controllers/signatures_controller.php b/app/controllers/signatures_controller.php new file mode 100755 index 000000000..3f9185c17 --- /dev/null +++ b/app/controllers/signatures_controller.php @@ -0,0 +1,163 @@ +Auth->user(); + $this->set('me', $me_user['User']); + $this->set('isAdmin', $this->isAdmin()); + } + + function index() { + $this->Signature->recursive = 0; + $this->set('signatures', $this->paginate()); + } + + function view($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid signature', true)); + $this->redirect(array('action' => 'index')); + } + $this->set('signature', $this->Signature->read(null, $id)); + } + + function add($event_id = null) { + if (!$event_id && empty($this->data)) { + $this->Session->setFlash(__('Invalid id for event', true)); + $this->redirect(array('controller' => 'events', 'action'=>'index')); + } + if ($event_id || !empty($this->data)) { + // only add signatures from events of yourself + $user = $this->Auth->user(); + if (!empty($this->data)) + $old_signature = $this->Signature->Event->read(null, $this->data['Signature']['event_id']); + else + $old_signature = $this->Signature->Event->read(null, $event_id); + if (!$this->isAdmin() && $user['User']['org'] != $old_signature['Event']['org']) { + $this->Session->setFlash(__('You can only add signatures for your own organisation.', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $old_signature['Event']['id'])); + } + + } + + if (!empty($this->data)) { + // create the signature + $this->Signature->create(); + + if ($this->Signature->save($this->data)) { + // remove the alerted flag from the event + $this->loadModel('Event'); + $event = $this->Event->read(null, $this->data['Signature']['event_id']); + $event['Event']['alerted'] = 0; + $this->Event->save($event); + // inform the user and redirect + $this->Session->setFlash(__('The signature has been saved', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $this->data['Signature']['event_id'])); + } else { + $this->Session->setFlash(__('The signature could not be saved. Please, try again.', true)); + } + } + if (empty($this->data)) { + $this->data['Signature']['event_id'] = $event_id; + } + + // combobox for types + $types = $this->Signature->validate['type']['allowedChoice']['rule'][1]; + $types = $this->arrayToValuesIndexArray($types); + $this->set('types',compact('types')); + } + + function edit($id = null) { + if (!$id && empty($this->data)) { + $this->Session->setFlash(__('Invalid signature', true)); + $this->redirect(array('controller' => 'events', 'action' => 'index')); + } + // only edit own signatures (from events of yourself) + $user = $this->Auth->user(); + $old_signature = $this->Signature->read(null, $id); + if (!$this->isAdmin() && $user['User']['org'] != $old_signature['Event']['org']) { + $this->Session->setFlash(__('You can only edit signatures from your own organisation.', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $old_signature['Event']['id'])); + } + + // form submit + if (!empty($this->data)) { + // block naughty stuff where the id or event_id are changed in the form + if ($this->data['Signature']['id'] != $id || + $this->data['Signature']['event_id'] != $old_signature['Signature']['event_id']) { + $this->Session->setFlash(__('You can only edit signatures from your own organisation.', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $old_signature['Event']['id'])); + } + + // data is valid, let's save the update + if ($this->Signature->save($this->data)) { + // remove the alerted flag from the event + $this->loadModel('Event'); + $event = $this->Event->read(null, $this->data['Signature']['event_id']); + $event['Event']['alerted'] = 0; + $this->Event->save($event); + // inform the user and redirect + $this->Session->setFlash(__('The signature has been saved', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $this->data['Signature']['event_id'])); + } else { + $this->Session->setFlash(__('The signature could not be saved. Please, try again.', true)); + } + } + if (empty($this->data)) { + $this->data = $this->Signature->read(null, $id); + } + + $events = $this->Signature->Event->find('list'); + $this->set(compact('events')); + + // combobox for types + $types = $this->Signature->validate['type']['allowedChoice']['rule'][1]; + $types = $this->arrayToValuesIndexArray($types); + $this->set('types',compact('types')); + } + + function delete($id = null) { + if (!$id) { + $this->Session->setFlash(__('Invalid id for signature', true)); + $this->redirect(array('action'=>'index')); + } + // only delete own signatures (from events of yourself) + $user = $this->Auth->user(); + $old_signature = $this->Signature->read(null, $id); + if (!$this->isAdmin() && $user['User']['org'] != $old_signature['Event']['org']) { + $this->Session->setFlash(__('You can only delete signatures from your own organisation.', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $old_signature['Event']['id'])); + } + // delete the signature + if ($this->Signature->delete($id)) { + $this->Session->setFlash(__('Signature deleted', true)); + $this->redirect(array('controller' => 'events', 'action' => 'view', $old_signature['Event']['id'])); + } + $this->Session->setFlash(__('Signature was not deleted', true)); + $this->redirect(array('action' => 'index')); + } + + + function search($keyword = null) { + if (!$keyword && !$this->data['Signature']['keyword']) { + // no search keyword is given, show the search form + } else { + if (!$keyword) $keyword = $this->data['Signature']['keyword']; + + // search the db + $this->Signature->recursive = 0; + $this->paginate = array( + 'conditions' => array('Signature.value LIKE' => '%'.$keyword.'%'), + ); + $this->set('signatures', $this->paginate()); + + // set the same view as the index page + $this->action = 'index'; + + } + } +} diff --git a/app/controllers/users_controller.php b/app/controllers/users_controller.php new file mode 100755 index 000000000..dfea9dffe --- /dev/null +++ b/app/controllers/users_controller.php @@ -0,0 +1,209 @@ +Auth->allow('login', 'logout'); + + // These variables are required for every view + $me_user = $this->Auth->user(); + $this->set('me', $me_user['User']); + $this->set('isAdmin', $this->isAdmin()); + } + + + function index() { + if (!$this->isAdmin()) { + $this->Session->setFlash(__('Not authorized to list users', true)); + $this->redirect(array('controller' => 'events' , 'action' => 'index')); + } + + $this->User->recursive = 0; + $this->set('users', $this->paginate()); + } + + function view($id = null) { + $user = $this->Auth->user(); + if (!$id) { + $this->Session->setFlash(__('Invalid user', true)); + $this->redirect(array('action' => 'index')); + } + + if ('me' == $id ) $id = $user['User']['id']; + + // only allow access to own profile, except for admins + if (!$this->isAdmin() && $id != $user['User']['id']) { + $this->Session->setFlash(__('Not authorized to view this user_error', true)); + $this->redirect(array('controller' => 'events' , 'action' => 'index')); + } + + $this->set('user', $this->User->read(null, $id)); + } + + function add() { + if (!$this->isAdmin()) { + $this->Session->setFlash(__('Not authorized to create new users', true)); + $this->redirect(array('controller' => 'events' , 'action' => 'index')); + } + + if (!empty($this->data)) { + if ($this->data['User']['password'] == '1deba050eee85e4ea7447edc6c289e4f55b81d45' ) { + // FIXME bug of auth ??? when passwd is empty it adds this hash + $this->data['User']['password'] = ''; + } + if (empty($this->data['User']['authkey'])) $this->data['User']['authkey'] = sha1('foo'+time()); // TODO place authkey generation into a function + $this->User->create(); + + if ($this->User->save($this->data)) { + // TODO send out email to user to inform of new user + // TODO send out email to admins to inform of new user + $this->Session->setFlash(__('The user has been saved', true)); + $this->redirect(array('action' => 'index')); + } else { + $this->Session->setFlash(__('The user could not be saved. Please, try again.', true)); + } + } + $groups = $this->User->Group->find('list'); + $this->set(compact('groups')); + } + + function edit($id = null) { + $user = $this->Auth->user(); + + if (!$id && empty($this->data)) { + $this->Session->setFlash(__('Invalid user', true)); + $this->redirect(array('action' => 'index')); + } + if ('me' == $id ) $id = $user['User']['id']; + + // only allow access to own profile, except for admins + if (!$this->isAdmin() && $id != $user['User']['id']) { + $this->Session->setFlash(__('Not authorized to edit this user', true)); + $this->redirect(array('action' => 'index')); + } + + if (!empty($this->data)) { + $this->User->read(null, $id); + + if ("" != $this->data['User']['password'] && $this->data['User']['password'] != '1deba050eee85e4ea7447edc6c289e4f55b81d45' ) // FIXME bug of auth ??? when passwd is empty it adds this hash + $this->User->set('password', $this->data['User']['password']); + $this->User->set('email', $this->data['User']['email']); + $this->User->set('autoalert', $this->data['User']['autoalert']); + $this->User->set('gpgkey', $this->data['User']['gpgkey']); + // LATER import the gpg key in the keychain, and remove the old key + // TODO check the key for validity + // LATER let the user reset his XML key + + + // administrative actions + if ($this->isAdmin()) { + $this->User->set('group_id', $this->data['User']['group_id']); + $this->User->set('org', $this->data['User']['org']); + } + + if ($this->User->save()) { + $this->Session->setFlash(__('The user has been saved', true)); + $this->redirect(array('action' => 'view', $id)); + } else { + $this->Session->setFlash(__('The user could not be saved. Please, try again.', true)); + } + +// if (empty($this->data['User']['authkey'])) $this->data['User']['authkey'] = sha1('foo'+time()); // TODO place authkey generation into a function +// if ($this->User->save($this->data)) { +// $this->Session->setFlash(__('The user has been saved', true)); +// $this->redirect(array('action' => 'index')); +// } else { +// $this->Session->setFlash(__('The user could not be saved. Please, try again.', true)); +// } + } + if (empty($this->data)) { + $this->data = $this->User->read(null, $id); + } + $this->data['User']['password'] = ""; // empty out the password + $groups = $this->User->Group->find('list'); + $this->set(compact('groups')); + } + + function delete($id = null) { + $me_user = $this->Auth->user(); + if (!$id) { + $this->Session->setFlash(__('Invalid id for user', true)); + $this->redirect(array('action'=>'index')); + } + if ('me' == $id ) $id = $user['User']['id']; + + // only allow delete own account, except for admins + if (!$this->isAdmin() && $id != $me_user['User']['id']) { + $this->Session->setFlash(__('Not authorized to delete this user', true)); + $this->redirect(array('action' => 'index')); + } + + if ($this->User->delete($id)) { + $this->Session->setFlash(__('User deleted', true)); + if (!$this->isAdmin()) { + // user deletes himself, force logout + $this->redirect(array('action'=>'logout')); + } + } + $this->Session->setFlash(__('User was not deleted', true)); + $this->redirect(array('action' => 'index')); + } + + + + function login() { +// if (!empty($this->data)) { +// // FIXME FIXME get the captcha to work +// if ($this->Recaptcha->verify()) { +// // do something, save you data, login, whatever +// +// } else { +// // display the raw API error +// $this->Session->setFlash($this->Recaptcha->error); +// $this->redirect($this->Auth->logout()); +// } +// } + + // if user is already logged in + if ($this->Session->read('Auth.User')) { + $this->Session->setFlash('You are already logged in!'); + $this->redirect('/', null, false); + } + + } + + + function logout() { + $this->Session->setFlash('Good-Bye'); + $this->redirect($this->Auth->logout()); + } + + + + + function initDB() { + $group =& $this->User->Group; + //Allow admins to everything + $group->id = 1; + $this->Acl->allow($group, 'controllers'); + + //allow managers to posts and widgets + $group->id = 2; + $this->Acl->deny($group, 'controllers'); + $this->Acl->allow($group, 'controllers/Events'); + $this->Acl->allow($group, 'controllers/Signatures'); + $this->Acl->allow($group, 'controllers/Users'); + + //we add an exit to avoid an ugly "missing views" error message + echo "all done"; + exit; + } + + + +} diff --git a/app/index.php b/app/index.php new file mode 100644 index 000000000..1d5f4bb39 --- /dev/null +++ b/app/index.php @@ -0,0 +1,18 @@ + array( + 'notempty' => array( + 'rule' => array('notempty'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'date' => array( + 'date' => array( + 'rule' => array('date'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'user_id' => array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'risk' => array( + 'allowedChoice' => array( + 'rule' => array('inList', array('Undefined', 'Low','Medium','High')), + 'message' => 'Options : Undefined, Low, Medium, High' + ), + ), + 'alerted' => array( + 'boolean' => array( + 'rule' =>array('boolean'), + ), + ), + ); + //The Associations below have been created with all possible keys, those that are not needed can be removed + + var $belongsTo = array( + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'user_id', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) + ); + + var $hasMany = array( + 'Signature' => array( + 'className' => 'Signature', + 'foreignKey' => 'event_id', + 'dependent' => true, // cascade deletes + 'conditions' => '', + 'fields' => '', + 'order' => 'Signature.type ASC', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) + ); + + +} diff --git a/app/models/group.php b/app/models/group.php new file mode 100755 index 000000000..9d0655bec --- /dev/null +++ b/app/models/group.php @@ -0,0 +1,38 @@ + array( + 'notempty' => array( + 'rule' => array('notempty'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + ); + //The Associations below have been created with all possible keys, those that are not needed can be removed + + var $hasMany = array( + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'group_id', + 'dependent' => false, + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) + ); + var $actsAs = array('Acl' => array('type' => 'requester')); + function parentNode() { + return null; + } + +} diff --git a/app/models/relation.php b/app/models/relation.php new file mode 100644 index 000000000..164a2d07b --- /dev/null +++ b/app/models/relation.php @@ -0,0 +1,33 @@ + array( + 'notempty' => array( + 'rule' => array('notempty'), + ), + ), + 'event_id' => array( + 'notempty' => array( + 'rule' => array('notempty'), + ), + ), + 'relation_id' => array( + 'notempty' => array( + 'rule' => array('notempty'), + ), + ), + ); + + // We explicitly have no relations + var $belongsTo = array( + + ); + + var $hasMany = array( + + ); + + +} diff --git a/app/models/signature.php b/app/models/signature.php new file mode 100755 index 000000000..fb47edc2c --- /dev/null +++ b/app/models/signature.php @@ -0,0 +1,174 @@ + "DESC", "Signature.type" => "ASC"); + var $validate = array( + 'event_id' => array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + 'required' => true, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'type' => array( + 'allowedChoice' => array( + 'rule' => array('inList', array('md5','sha1', + 'filename', + 'ip-src', + 'ip-dst', + 'domain', + 'email-src', + 'email-dst', + 'email-subject', + 'email-attachment', + 'url', + 'user-agent', + 'regkey', + 'other')), + 'message' => 'Options : md5, sha1, filename, ip, domain, email, url, regkey, other, ...' + ), + ), + 'value' => array( + 'notempty' => array( + 'rule' => array('notempty'), + 'message' => 'Please fill in this field', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + 'rightformat' => array( + 'rule' => array('validateSignatureValue'), + 'message' => 'Value not in the right type/format. Please double check the value or select "other" for a type.' + ), +// 'unique' => array( +// 'rule' => array('signatureExists'), +// 'message' => 'Signature is already used in another event.' // Message set by signatureExists() function +// ) + ), + ); + //The Associations below have been created with all possible keys, those that are not needed can be removed + + var $belongsTo = array( + 'Event' => array( + 'className' => 'Event', + 'foreignKey' => 'event_id', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) + ); + + function signatureExists($fields) { + $result = $this->find('first', array('conditions' => $fields, 'recursive' => -1)); + // signature doesn't exist + if (empty($result)) return true; + // signature exists + return 'Signature is already used in this event.'; // LATER find a way to do fill path automatically. // LATER permit user to a sig that exists. + } + + function validateSignatureValue ($fields) { + $value = $fields['value']; + switch($this->data['Signature']['type']) { + case 'md5': + if (preg_match("#^[0-9a-f]{32}$#i", $value)) + return true; + return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.'; + break; + case 'sha1': + if (preg_match("#^[0-9a-f]{40}$#i", $value)) + return true; + return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.'; + break; + case 'filename': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'ip-src': + $parts = explode("/", $value); + // [0] = the ip + // [1] = the network address + if (count($parts) <= 2 ) { + // ipv4 and ipv6 matching + if (filter_var($parts[0],FILTER_VALIDATE_IP)) { + // ip is validated, now check if we have a valid network mask + if (empty($parts[1])) + return true; + else if(is_numeric($parts[1]) && $parts[1] < 129) + return true; + } + } + return 'IP address has invalid format. Please double check the value or select "other" for a type.'; + break; + case 'ip-dst': + $parts = explode("/", $value); + // [0] = the ip + // [1] = the network address + if (count($parts) <= 2 ) { + // ipv4 and ipv6 matching + if (filter_var($parts[0],FILTER_VALIDATE_IP)) { + // ip is validated, now check if we have a valid network mask + if (empty($parts[1])) + return true; + else if(is_numeric($parts[1]) && $parts[1] < 129) + return true; + } + } + return 'IP address has invalid format. Please double check the value or select "other" for a type.'; + break; + case 'domain': + if(preg_match("#^[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value)) + return true; + return 'Domain name has invalid format. Please double check the value or select "other" for a type.'; + break; + case 'email-src': + // we don't use the native function to prevent issues with partial email addresses + if(preg_match("#^[A-Z0-9._%+-]*@[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value)) + return true; + return 'Email address has invalid format. Please double check the value or select "other" for a type.'; + break; + case 'email-dst': + // we don't use the native function to prevent issues with partial email addresses + if(preg_match("#^[A-Z0-9._%+-]*@[A-Z0-9.-]+\.[A-Z]{2,4}$#i", $value)) + return true; + return 'Email address has invalid format. Please double check the value or select "other" for a type.'; + break; + case 'email-subject': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'email-attachment': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'url': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'user-agent': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'regkey': + // no newline + if (!preg_match("#\n#", $value)) + return true; + break; + case 'other': + return true; + break; + } + + // default action is to return false + return false; + + } +} diff --git a/app/models/user.php b/app/models/user.php new file mode 100755 index 000000000..0daaa21dd --- /dev/null +++ b/app/models/user.php @@ -0,0 +1,186 @@ + array( + 'numeric' => array( + 'rule' => array('numeric'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'password' => array( + 'notempty' => array( + 'rule' => array('notempty'), + 'message' => 'A password is required', // LATER password strength requirements + //'allowEmpty' => false, + 'required' => true, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), +// 'complex' => array( +// 'rule' => array('complexPassword'), +// 'message' => 'Password must be 8 characters minimum and contain at least one number and one uppercase character' +// ), + + ), + 'org' => array( + 'notempty' => array( + 'rule' => array('notempty'), + 'message' => 'Please specify the organisation where you are working.', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'email' => array( + 'email' => array( + 'rule' => array('email'), + 'message' => 'Please enter a valid email address.', + //'allowEmpty' => false, + 'required' => true, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + 'unique' => array( + 'rule' => 'isUnique', + 'message' => 'An account with this email address already exists.' + ), + ), + 'autoalert' => array( + 'boolean' => array( + 'rule' => array('boolean'), + //'message' => 'Your custom message here', + //'allowEmpty' => false, + 'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'authkey' => array( + 'notempty' => array( + 'rule' => array('notempty'), // LATER define the format of the authkey + //'message' => 'Your custom message here', + //'allowEmpty' => false, + //'required' => false, + //'last' => false, // Stop validation after this rule + //'on' => 'create', // Limit validation to 'create' or 'update' operations + ), + ), + 'gpgkey' => array( + 'rule' => array('validateGpgkey'), + 'message' => 'GPG key not valid, please enter a valid key' + ), + ); + //The Associations below have been created with all possible keys, those that are not needed can be removed + + var $belongsTo = array( + 'Group' => array( + 'className' => 'Group', + 'foreignKey' => 'group_id', + 'conditions' => '', + 'fields' => '', + 'order' => '' + ) + ); + + var $hasMany = array( + 'Event' => array( + 'className' => 'Event', + 'foreignKey' => 'user_id', + 'dependent' => false, // do not delete Events when user is deleted + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ), + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'invited_by', + 'dependent' => false, // do not delete Users when user is deleted + 'conditions' => '', + 'fields' => '', + 'order' => '', + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ) + ); + + + var $actsAs = array('Acl' => array('type' => 'requester')); + + function parentNode() { + if (!$this->id && empty($this->data)) { + return null; + } + if (isset($this->data['User']['group_id'])) { + $groupId = $this->data['User']['group_id']; + } else { + $groupId = $this->field('group_id'); + } + if (!$groupId) { + return null; + } else { + return array('Group' => array('id' => $groupId)); + } + } + + function bindNode($user) { + return array('model' => 'Group', 'foreign_key' => $user['User']['group_id']); + } + + + /** + * Checks if the GPG key is a valid key + * But also import it in the keychain. + */ + function ValidateGpgkey($check) { + // LATER first remove the old gpgkey from the keychain + + // empty value + if (empty($check['gpgkey'])) + return true; + + // key is entered + require_once 'Crypt/GPG.php'; + $gpg = new Crypt_GPG(); + try { + $key_import_output = $gpg->importKey($check['gpgkey']); + if (!empty($key_import_output['fingerprint'])) { + return true; + } + } catch (Exception $e) { + return false; + } + } + + + function complexPassword($check) { + debug($check); + /* + 8 characters minimum + 1 or more upper-case letters + 1 or more lower-case letters + 1 or more digits or special characters + example: "EasyPeasy34" + */ + + $value = array_values($check); + $value = $value[0]; + return preg_match('/(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/', $value); + + } + + +} diff --git a/app/plugins/empty b/app/plugins/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/plugins/recaptcha/controllers/components/recaptcha.php b/app/plugins/recaptcha/controllers/components/recaptcha.php new file mode 100755 index 000000000..8cbbd3dd8 --- /dev/null +++ b/app/plugins/recaptcha/controllers/components/recaptcha.php @@ -0,0 +1,143 @@ +privateKey = Configure::read('Recaptcha.privateKey'); + $this->Controller = $controller; + + if (empty($this->privateKey)) { + throw new Exception(__d('recaptcha', "You must set your private recaptcha key using Configure::write('Recaptcha.privateKey', 'your-key');!", true)); + } + + $defaults = array( + 'modelClass' => $this->Controller->modelClass, + 'errorField' => 'recaptcha', + 'actions' => array()); + + $this->settings = array_merge($defaults, $settings); + $this->actions = array_merge($this->actions, $this->settings['actions']); + extract($this->settings); + + if ($this->enabled == true) { + $this->Controller->helpers[] = 'Recaptcha.Recaptcha'; + $this->Controller->{$modelClass}->Behaviors->attach('Recaptcha.Recaptcha', array( + 'field' => $errorField)); + + $this->Controller->{$modelClass}->recaptcha = true; + if (in_array($this->Controller->action, $this->actions)) { + if (!$this->verify()) { + $this->Controller->{$modelClass}->recaptcha = false; + $this->Controller->{$modelClass}->recaptchaError = $this->error; + } + } + } + } + +/** + * Verifies the recaptcha input + * + * Please note that you still have to pass the result to the model and do + * the validation there to make sure the data is not saved! + * + * @return boolean True if the response was correct + */ + public function verify() { + if (isset($this->Controller->params['form']['recaptcha_challenge_field']) && + isset($this->Controller->params['form']['recaptcha_response_field'])) { + + $response = $this->_getApiResponse(); + $response = explode("\n", $response); + + if ($response[0] == 'true') { + return true; + } + + if ($response[1] == 'incorrect-captcha-sol') { + $this->error = __d('recaptcha', 'Incorrect captcha', true); + } else { + $this->error = $response[1]; + } + + return false; + } + } + +/** + * Queries the Recaptcha API and and returns the raw response + * + * @return string + */ + protected function _getApiResponse() { + App::import('Core', 'HttpSocket'); + $Socket = new HttpSocket(); + return $Socket->post($this->apiUrl, array( + 'privatekey'=> $this->privateKey, + 'remoteip' => env('REMOTE_ADDR'), + 'challenge' => $this->Controller->params['form']['recaptcha_challenge_field'], + 'response' => $this->Controller->params['form']['recaptcha_response_field'])); + } + +} diff --git a/app/plugins/recaptcha/license.txt b/app/plugins/recaptcha/license.txt new file mode 100755 index 000000000..25e5ee24c --- /dev/null +++ b/app/plugins/recaptcha/license.txt @@ -0,0 +1,25 @@ +The MIT License + +Copyright 2009-2010 +Cake Development Corporation +1785 E. Sahara Avenue, Suite 490-423 +Las Vegas, Nevada 89104 +http://cakedc.com + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/app/plugins/recaptcha/locale/fre/LC_MESSAGES/recaptcha.po b/app/plugins/recaptcha/locale/fre/LC_MESSAGES/recaptcha.po new file mode 100755 index 000000000..477a4cf56 --- /dev/null +++ b/app/plugins/recaptcha/locale/fre/LC_MESSAGES/recaptcha.po @@ -0,0 +1,28 @@ +msgid "" +msgstr "" +"Project-Id-Version: CakePHP Recaptcha Plugin\n" +"POT-Creation-Date: 2010-09-15 15:30+0200\n" +"PO-Revision-Date: \n" +"Last-Translator: Pierre MARTIN \n" +"Language-Team: CakeDC \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: French\n" + +#: /controllers/components/recaptcha.php:79 +msgid "You must set your private recaptcha key using Cofigure::write('Recaptcha.privateKey', 'your-key');!" +msgstr "Vous devez définir votre clé privée Recaptcha en utilisant Configure::write('Recaptcha.privateKey', 'votre-clé'); !" + +#: /controllers/components/recaptcha.php:110 +msgid "Incorect captcha" +msgstr "Captcha incorrect" + +#: /views/helpers/recaptcha.php:115 +msgid "To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed." +msgstr "Afin d'utiliser reCAPTCHA Mailhide, vous devez avoir le module php mcrypt installé." + +#: /views/helpers/recaptcha.php:147 +msgid "You need to set a private and public mail hide key. Please visit http://mailhide.recaptcha.net/apikey" +msgstr "Vous devez définir les clés publique et privée mailhide. Veuillez visiter http://mailhide.recaptcha.net/apikey" + diff --git a/app/plugins/recaptcha/locale/recaptcha.pot b/app/plugins/recaptcha/locale/recaptcha.pot new file mode 100755 index 000000000..f78279fb0 --- /dev/null +++ b/app/plugins/recaptcha/locale/recaptcha.pot @@ -0,0 +1,39 @@ +# LANGUAGE translation of the CakePHP Categories plugin +# +# Copyright 2010, Cake Development Corporation (http://cakedc.com) +# +# Licensed under The MIT License +# Redistributions of files must retain the above copyright notice. +# +# @copyright Copyright 2010, Cake Development Corporation (http://cakedc.com) +# @license MIT License (http://www.opensource.org/licenses/mit-license.php) +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2010-09-15 15:30+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: /controllers/components/recaptcha.php:79 +msgid "You must set your private recaptcha key using Cofigure::write('Recaptcha.privateKey', 'your-key');!" +msgstr "" + +#: /controllers/components/recaptcha.php:110 +msgid "Incorect captcha" +msgstr "" + +#: /views/helpers/recaptcha.php:115 +msgid "To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed." +msgstr "" + +#: /views/helpers/recaptcha.php:147 +msgid "You need to set a private and public mail hide key. Please visit http://mailhide.recaptcha.net/apikey" +msgstr "" + diff --git a/app/plugins/recaptcha/models/behaviors/recaptcha.php b/app/plugins/recaptcha/models/behaviors/recaptcha.php new file mode 100755 index 000000000..b5badf855 --- /dev/null +++ b/app/plugins/recaptcha/models/behaviors/recaptcha.php @@ -0,0 +1,71 @@ + 'recaptcha'); + +/** + * Setup + * + * @param AppModel $model + * @param array $settings + */ + public function setup(Model $Model, $settings = array()) { + if (!isset($this->settings[$Model->alias])) { + $this->settings[$Model->alias] = $this->defaults; + } + $this->settings[$Model->alias] = array_merge($this->settings[$Model->alias], ife(is_array($settings), $settings, array())); + } + +/** + * Validates the captcha responses status set by the component to the model + * + * @object Model instance + * @return boolean + * @see RecaptchaComponent::initialize() + */ + public function validateCaptcha(Model $Model) { + if (isset($Model->recaptcha) && $Model->recaptcha === false) { + $Model->invalidate($this->settings[$Model->alias]['errorField'], $Model->recaptchaError); + } + return true; + } + +/** + * Validates the captcha + * + * @object Model instance + * @return void; + */ + public function beforeValidate(Model $Model) { + $this->validateCaptcha($Model); + return true; + } +} \ No newline at end of file diff --git a/app/plugins/recaptcha/readme.md b/app/plugins/recaptcha/readme.md new file mode 100755 index 000000000..1aabc4912 --- /dev/null +++ b/app/plugins/recaptcha/readme.md @@ -0,0 +1,57 @@ +# Recaptcha Plugin for CakePHP # + +The Recaptcha plugin for CakePHP provides spam protection in an easy use helper. + +## Usage ## + +To use the recaptcha plugin its required to include the following two lines in your `/app/config/bootstrap.php` file. + + Configure::write('Recaptcha.publicKey', 'your-public-api-key'); + Configure::write('Recaptcha.privateKey', 'your-private-api-key'); + +Don't forget to replace the placeholder text with your actual keys! + +Keys can be obtained for free from the [Recaptcha website](http://www.google.com/recaptcha). + +Controllers that will be using recaptcha require the Recaptcha Component to be included. Through inclusion of the component, the helper is automatically made available to your views. + +In the view simply call the helpers `display()` method to render the recaptcha input: + + echo $this->Recaptcha->display(); + +To check the result simply do something like this in your controller: + + if (!empty($this->data)) { + if ($this->Recaptcha->verify()) { + // do something, save you data, login, whatever + } else { + // display the raw API error + $this->Session->setFlash($this->Recaptcha->error); + } + } + +## Requirements ## + +* PHP version: PHP 5.2+ +* CakePHP version: Cakephp 1.3 Stable + +## Support ## + +For support and feature request, please visit the [Recaptcha Plugin Support Site](http://cakedc.lighthouseapp.com/projects/60546-recaptcha-plugin/). + +For more information about our Professional CakePHP Services please visit the [Cake Development Corporation website](http://cakedc.com). + +## License ## + +Copyright 2009-2010, [Cake Development Corporation](http://cakedc.com) + +Licensed under [The MIT License](http://www.opensource.org/licenses/mit-license.php)
+Redistributions of files must retain the above copyright notice. + +## Copyright ### + +Copyright 2009-2010
+[Cake Development Corporation](http://cakedc.com)
+1785 E. Sahara Avenue, Suite 490-423
+Las Vegas, Nevada 89104
+http://cakedc.com
diff --git a/app/plugins/recaptcha/tests/cases/behaviors/recaptcha.test.php b/app/plugins/recaptcha/tests/cases/behaviors/recaptcha.test.php new file mode 100755 index 000000000..2a2accd97 --- /dev/null +++ b/app/plugins/recaptcha/tests/cases/behaviors/recaptcha.test.php @@ -0,0 +1,73 @@ +Model = new RecaptchaArticle(); + $this->Behavior = new RecaptchaBehavior(); + } + +/** + * Destroy the model instance + * + * @return void + */ + public function endTest() { + unset($this->Model); + unset($this->Behavior); + ClassRegistry::flush(); + } + +/** + * testValidateCaptcha + * + * @return void + */ + public function testValidateCaptcha() { + $this->Model->validateCaptcha(); + $result = $this->Model->invalidFields(); + $this->assertTrue(empty($result)); + + $this->Model->recaptcha = false; + $this->Model->recaptchaError = 'Invalid Recaptcha'; + $this->Model->validateCaptcha(); + $result = $this->Model->invalidFields(); + $this->assertEqual($result, array('recaptcha' => 'Invalid Recaptcha')); + } + +} \ No newline at end of file diff --git a/app/plugins/recaptcha/tests/cases/components/recaptcha.test.php b/app/plugins/recaptcha/tests/cases/components/recaptcha.test.php new file mode 100755 index 000000000..d2d7036ca --- /dev/null +++ b/app/plugins/recaptcha/tests/cases/components/recaptcha.test.php @@ -0,0 +1,84 @@ +Controller = new ArticleTestController(); + $this->Controller->constructClasses(); + //$this->Controller->modelClass = 'RecaptchaTestArticle'; + $this->Controller->Component->init($this->Controller); + $this->Controller->Component->initialize($this->Controller); + } + +/** + * endTest + * + * @return void + */ + function endTest() { + unset($this->Controller); + ClassRegistry::flush(); + } + +/** + * testRecaptcha + * + * @return void + */ + public function testRecaptcha() { + $this->Controller->params['form']['recaptcha_challenge_field'] = 'something'; + $this->Controller->params['form']['recaptcha_response_field'] = 'something'; + $this->assertFalse($this->Controller->Recaptcha->verify()); + } + +} diff --git a/app/plugins/recaptcha/tests/cases/helpers/recaptcha.test.php b/app/plugins/recaptcha/tests/cases/helpers/recaptcha.test.php new file mode 100755 index 000000000..f40493d87 --- /dev/null +++ b/app/plugins/recaptcha/tests/cases/helpers/recaptcha.test.php @@ -0,0 +1,67 @@ +Recaptcha); + } +} diff --git a/app/plugins/recaptcha/tests/fixtures/article_fixture.php b/app/plugins/recaptcha/tests/fixtures/article_fixture.php new file mode 100755 index 000000000..22d386627 --- /dev/null +++ b/app/plugins/recaptcha/tests/fixtures/article_fixture.php @@ -0,0 +1,45 @@ + array('type' => 'integer', 'key' => 'primary'), + 'title' => array('type' => 'string', 'null' => false), + 'comments' => array('type' => 'integer', 'null' => false, 'default' => '0')); + +/** + * records property + * + * @var array + */ + public $records = array( + array('title' => 'First Article', 'comments' => 2), + array('title' => 'Second Article', 'comments' => 0)); +} diff --git a/app/plugins/recaptcha/views/helpers/recaptcha.php b/app/plugins/recaptcha/views/helpers/recaptcha.php new file mode 100755 index 000000000..fffee0425 --- /dev/null +++ b/app/plugins/recaptcha/views/helpers/recaptcha.php @@ -0,0 +1,215 @@ + null, + 'publicKey' => Configure::read('Recaptcha.publicKey'), + 'error' => null, + 'ssl' => true, + 'error' => false, + 'div' => array( + 'class' => 'recaptcha')); + $options = array_merge($defaults, $options); + extract($options); + + if ($ssl) { + $server = $this->secureApiUrl; + } else { + $server = $this->apiUrl; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + + if (!empty($element)) { + $elementOptions = array(); + if (is_array($element)) { + $keys = array_keys($element); + $elementOptions = $element[$keys[0]]; + } + $View = $this->__view(); + return $View->element($element, $elementOptions); + } + + $script = ' + '; + + if (!empty($error)) { + $script .= $this->Form->error($error); + } + + if ($options['div'] != false) { + $script = $this->Html->tag('div', $script, $options['div']); + } + + return $script; + } + +/** + * Recaptcha signup URL + * + * @return string + */ + function signupUrl($appname = null) { + return "http://recaptcha.net/api/getkey?domain=" . WWW_ROOT . '&app=' . urlencode($appName); + } + +/** + * AES Pad + * + * @param string value + * @return string + */ + private function __AesPad($val) { + $blockSize = 16; + $numpad = $blockSize - (strlen($val) % $blockSize); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); + } + +/** + * AES Encryption + * + * @return string + */ + function __AesEncrypt($value, $key) { + if (!function_exists('mcrypt_encrypt')) { + throw new Exception(__d('recaptcha', 'To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.', true)); + } + + $mode = MCRYPT_MODE_CBC; + $encryption = MCRYPT_RIJNDAEL_128; + $value = $this->__AesPad($value); + + return mcrypt_encrypt($encryption, $key, $value, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + +/** + * + * + * @return string base 64 encrypted string + */ + private function __mailhideUrlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); + } + +/** + * Gets the reCAPTCHA Mailhide url for a given email + * + * @param $email + * @return string + */ + public function mailHideUrl($email = null) { + $publicKey = Configure::read('Recaptcha.mailHide.publicKey'); + $privateKey = Configure::read('Recaptcha.mailHide.privateKey'); + + if ($publicKey == '' || $publicKey == null || $privateKey == "" || $privateKey == null) { + throw new Exception(__d('recaptcha', "You need to set a private and public mail hide key. Please visit http://mailhide.recaptcha.net/apikey", true)); + } + + $key = pack('H*', $privateKey); + $cryptmail = $this->__AesEncrypt ($email, $key); + + return "http://mailhide.recaptcha.net/d?k=" . $publicKey . "&c=" . $this->__mailhideUrlbase64($cryptmail); + } + +/** + * Get a part of the email to show + * + * Given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + * + * @param string email + * @return array + */ + private function __hideEmailParts($email) { + $array = preg_split("/@/", $email ); + + if (strlen ($array[0]) <= 4) { + $array[0] = substr ($array[0], 0, 1); + } else if (strlen ($array[0]) <= 6) { + $array[0] = substr ($array[0], 0, 3); + } else { + $array[0] = substr ($array[0], 0, 4); + } + return $array; + } + +/** + * Gets html to display an email address given a public an private key to get a key go to: + * http://mailhide.recaptcha.net/apikey + * + * @param string Email + * @return string + */ + public function mailHide($email) { + $emailparts = __hideEmailParts ($email); + $url = $this->mailHideUrl($email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + } + +/** + * Get current view class + * + * @return object, View class + */ + private function __view() { + if (!empty($this->globalParams['viewInstance'])) { + return $this->globalParams['viewInstance']; + } + return ClassRegistry::getObject('view'); + } + +} diff --git a/app/tests/cases/behaviors/empty b/app/tests/cases/behaviors/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/cases/components/empty b/app/tests/cases/components/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/cases/controllers/empty b/app/tests/cases/controllers/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/cases/helpers/empty b/app/tests/cases/helpers/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/cases/models/empty b/app/tests/cases/models/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/fixtures/empty b/app/tests/fixtures/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/tests/groups/empty b/app/tests/groups/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/vendors/shells/tasks/empty b/app/vendors/shells/tasks/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/vendors/shells/templates/empty b/app/vendors/shells/templates/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/elements/actions_menu.ctp b/app/views/elements/actions_menu.ctp new file mode 100644 index 000000000..72e452ef7 --- /dev/null +++ b/app/views/elements/actions_menu.ctp @@ -0,0 +1,15 @@ +
  • Html->link(__('New Event', true), array('controller' => 'events', 'action' => 'add')); ?>
  • +
  • Html->link(__('List Events', true), array('controller' => 'events', 'action' => 'index')); ?>
  • +
  • Html->link(__('List Signatures', true), array('controller' => 'signatures', 'action' => 'index')); ?>
  • +
  • Html->link(__('Search Signatures', true), array('controller' => 'signatures', 'action' => 'search')); ?>
  • +
  • Html->link(__('Export', true), array('controller' => 'events', 'action' => 'export')); ?>
  • + +
  •  
  • +
  • Html->link(__('My Profile', true), array('controller' => 'users', 'action' => 'view', 'me')); ?>
  • + +
  •  
  • +
  • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add')); ?>
  • +
  • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index')); ?>
  • +
  • Html->link(__('New Group', true), array('controller' => 'groups', 'action' => 'add')); ?>
  • +
  • Html->link(__('List Groups', true), array('controller' => 'groups', 'action' => 'index')); ?>
  • + \ No newline at end of file diff --git a/app/views/elements/email/html/empty b/app/views/elements/email/html/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/elements/email/text/body.ctp b/app/views/elements/email/text/body.ctp new file mode 100755 index 000000000..9a6d26343 --- /dev/null +++ b/app/views/elements/email/text/body.ctp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/views/elements/email/text/empty b/app/views/elements/email/text/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/elements/email/text/new_event.ctp b/app/views/elements/email/text/new_event.ctp new file mode 100755 index 000000000..702dbebeb --- /dev/null +++ b/app/views/elements/email/text/new_event.ctp @@ -0,0 +1,17 @@ + + +Event : +Date : +Reported by : +Risk : +Signatures : + +- +: + +Extra info : + + + diff --git a/app/views/elements/empty b/app/views/elements/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/errors/empty b/app/views/errors/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/errors/error403.ctp b/app/views/errors/error403.ctp new file mode 100755 index 000000000..72f7ac4ea --- /dev/null +++ b/app/views/errors/error403.ctp @@ -0,0 +1,5 @@ +

    +

    + : + +

    \ No newline at end of file diff --git a/app/views/events/add.ctp b/app/views/events/add.ctp new file mode 100755 index 000000000..c87fe634d --- /dev/null +++ b/app/views/events/add.ctp @@ -0,0 +1,22 @@ +
    +Form->create('Event');?> +
    + + Form->hidden('user_id'); + echo $this->Form->hidden('org'); + echo $this->Form->input('date'); + echo $this->Form->input('risk'); + echo $this->Form->input('info'); + + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + element('actions_menu'); ?> + +
    +
    \ No newline at end of file diff --git a/app/views/events/edit.ctp b/app/views/events/edit.ctp new file mode 100755 index 000000000..7f76d814b --- /dev/null +++ b/app/views/events/edit.ctp @@ -0,0 +1,25 @@ +
    +Form->create('Event');?> +
    + + Form->input('id'); + echo $this->Form->hidden('user_id'); + echo $this->Form->hidden('org'); + echo $this->Form->input('date'); + echo $this->Form->input('risk'); + echo $this->Form->input('info'); + + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + +
    • Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value('Event.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $this->Form->value('Event.id'))); ?>
    • +
    •  
    • + element('actions_menu'); ?> +
    +
    \ No newline at end of file diff --git a/app/views/events/index.ctp b/app/views/events/index.ctp new file mode 100755 index 000000000..5dae203c7 --- /dev/null +++ b/app/views/events/index.ctp @@ -0,0 +1,60 @@ +
    +

    + + + + + + + + + + onclick="document.location ='Html->url(array('action' => 'view', $event['Event']['id']), true) ;?>';"> + + + + + + + +
    Paginator->sort('org');?>Paginator->sort('date');?>Paginator->sort('risk');?>Paginator->sort('info');?>
         + Html->link(__('Finish Edit', true), array('action' => 'alert', $event['Event']['id']), array(), 'Are you sure this event is complete and everyone should be alerted?'); + elseif (0 == $event['Event']['alerted']) echo 'Not finished editing'; + ?> + Html->link(__('Edit', true), array('action' => 'edit', $event['Event']['id'])); + echo $this->Html->link(__('Delete', true), array('action' => 'delete', $event['Event']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $event['Event']['id'])); + } + ?> + Html->link(__('View', true), array('action' => 'view', $event['Event']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) + )); + ?>

    + +
    + Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?> + | Paginator->numbers();?> + | + Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?> +
    +
    +
    +

    +
      + element('actions_menu'); ?> + +
    +
    diff --git a/app/views/events/snort.ctp b/app/views/events/snort.ctp new file mode 100755 index 000000000..e69de29bb diff --git a/app/views/events/view.ctp b/app/views/events/view.ctp new file mode 100755 index 000000000..6c4074e0b --- /dev/null +++ b/app/views/events/view.ctp @@ -0,0 +1,95 @@ +
    +
    + +
    • Html->link(__('Finish Edit', true), array('action' => 'alert', $event['Event']['id']), array(), 'Are you sure this event is complete and everyone should be alerted?'); ?>
    + +
    • Not finished editing
    + + + +
    • Html->link(__('Contact reporter', true), array('action' => 'contact', $event['Event']['id']), array(), 'An email with your contact info will be sent to the reporter. Are you sure?'); ?>
    +
    + + + +

    +
    + > + > + +   + + > + > + +   + + > + > + +   + + > + > + +   + +
    + + + +
    + +
    +

    +
      + +
    • Html->link(__('New Signature', true), array('controller' => 'signatures', 'action' => 'add', $event['Event']['id']));?>
    • +
    • Html->link(__('Edit Event', true), array('action' => 'edit', $event['Event']['id'])); ?>
    • +
    • Html->link(__('Delete Event', true), array('action' => 'delete', $event['Event']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $event['Event']['id'])); ?>
    • +
    •  
    • + + element('actions_menu'); ?> +
    +
    + diff --git a/app/views/events/xml.ctp b/app/views/events/xml.ctp new file mode 100755 index 000000000..2b2b81b85 --- /dev/null +++ b/app/views/events/xml.ctp @@ -0,0 +1,17 @@ +\n"; ?> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/views/groups/add.ctp b/app/views/groups/add.ctp new file mode 100755 index 000000000..9af24f997 --- /dev/null +++ b/app/views/groups/add.ctp @@ -0,0 +1,19 @@ +
    +Form->create('Group');?> +
    + + Form->input('name'); + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + +
    • Html->link(__('List Groups', true), array('action' => 'index'));?>
    • +
    • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    \ No newline at end of file diff --git a/app/views/groups/edit.ctp b/app/views/groups/edit.ctp new file mode 100755 index 000000000..e8e7616a3 --- /dev/null +++ b/app/views/groups/edit.ctp @@ -0,0 +1,21 @@ +
    +Form->create('Group');?> +
    + + Form->input('id'); + echo $this->Form->input('name'); + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + +
    • Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value('Group.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $this->Form->value('Group.id'))); ?>
    • +
    • Html->link(__('List Groups', true), array('action' => 'index'));?>
    • +
    • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    \ No newline at end of file diff --git a/app/views/groups/index.ctp b/app/views/groups/index.ctp new file mode 100755 index 000000000..781df81aa --- /dev/null +++ b/app/views/groups/index.ctp @@ -0,0 +1,49 @@ +
    +

    + + + + + + + + > + + + + + +
    Paginator->sort('id');?>Paginator->sort('name');?>
       + Html->link(__('View', true), array('action' => 'view', $group['Group']['id'])); ?> + Html->link(__('Edit', true), array('action' => 'edit', $group['Group']['id'])); ?> + Html->link(__('Delete', true), array('action' => 'delete', $group['Group']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $group['Group']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) + )); + ?>

    + +
    + Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?> + | Paginator->numbers();?> + | + Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?> +
    +
    +
    +

    +
      +
    • Html->link(__('New Group', true), array('action' => 'add')); ?>
    • +
    • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    \ No newline at end of file diff --git a/app/views/groups/view.ctp b/app/views/groups/view.ctp new file mode 100755 index 000000000..0f6c54648 --- /dev/null +++ b/app/views/groups/view.ctp @@ -0,0 +1,72 @@ +
    +

    +
    + > + > + +   + + > + > + +   + +
    +
    +
    +

    +
      +
    • Html->link(__('Edit Group', true), array('action' => 'edit', $group['Group']['id'])); ?>
    • +
    • Html->link(__('Delete Group', true), array('action' => 'delete', $group['Group']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $group['Group']['id'])); ?>
    • +
    • Html->link(__('List Groups', true), array('action' => 'index')); ?>
    • +
    • Html->link(__('New Group', true), array('action' => 'add')); ?>
    • +
    • Html->link(__('List Users', true), array('controller' => 'users', 'action' => 'index')); ?>
    • +
    • Html->link(__('New User', true), array('controller' => 'users', 'action' => 'add')); ?>
    • +
    +
    + diff --git a/app/views/helpers/empty b/app/views/helpers/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/default.ctp b/app/views/layouts/default.ctp new file mode 100755 index 000000000..c4f4863b4 --- /dev/null +++ b/app/views/layouts/default.ctp @@ -0,0 +1,74 @@ + + + + + Html->charset(); ?> + + <?php __('CyDefSIG: Cyber-Defence Signatures: sharing detection patterns'); ?> + <?php echo $title_for_layout; ?> + + Html->meta('icon'); + + echo $this->Html->css('cake.generic'); + + echo $scripts_for_layout; + + ?> + + +
    + +
    + Session->flash('auth'); ?> + Session->flash(); ?> + Session->flash('email'); ?> + + + + +
    + +
    + element('sql_dump'); ?> + + + + diff --git a/app/views/layouts/email/html/default.ctp b/app/views/layouts/email/html/default.ctp new file mode 100755 index 000000000..4f1938c7c --- /dev/null +++ b/app/views/layouts/email/html/default.ctp @@ -0,0 +1,29 @@ + + + + + <?php echo $title_for_layout;?> + + + + + + diff --git a/app/views/layouts/email/html/empty b/app/views/layouts/email/html/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/email/text/default.ctp b/app/views/layouts/email/text/default.ctp new file mode 100755 index 000000000..072ee3c6c --- /dev/null +++ b/app/views/layouts/email/text/default.ctp @@ -0,0 +1,20 @@ + + diff --git a/app/views/layouts/email/text/empty b/app/views/layouts/email/text/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/js/empty b/app/views/layouts/js/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/rss/empty b/app/views/layouts/rss/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/xml/empty b/app/views/layouts/xml/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/layouts/xml/xml.ctp b/app/views/layouts/xml/xml.ctp new file mode 100755 index 000000000..3f290130e --- /dev/null +++ b/app/views/layouts/xml/xml.ctp @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/views/pages/empty b/app/views/pages/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/scaffolds/empty b/app/views/scaffolds/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/signatures/add.ctp b/app/views/signatures/add.ctp new file mode 100755 index 000000000..b13407445 --- /dev/null +++ b/app/views/signatures/add.ctp @@ -0,0 +1,18 @@ +
    +Form->create('Signature');?> +
    + + Form->hidden('event_id'); + echo $this->Form->input('type'); + echo $this->Form->input('value', array('error' => array('escape' => false))); + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + element('actions_menu'); ?> +
    +
    \ No newline at end of file diff --git a/app/views/signatures/edit.ctp b/app/views/signatures/edit.ctp new file mode 100755 index 000000000..0e3258658 --- /dev/null +++ b/app/views/signatures/edit.ctp @@ -0,0 +1,22 @@ +
    +Form->create('Signature');?> +
    + + Form->input('id'); + echo $this->Form->hidden('event_id'); + echo $this->Form->input('type'); + echo $this->Form->input('value'); + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + +
    • Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value('Signature.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $this->Form->value('Signature.id'))); ?>
    • +
    •  
    • + element('actions_menu'); ?> +
    +
    diff --git a/app/views/signatures/index.ctp b/app/views/signatures/index.ctp new file mode 100755 index 000000000..04886c532 --- /dev/null +++ b/app/views/signatures/index.ctp @@ -0,0 +1,54 @@ +
    +

    + + + + + + + + + onclick="document.location ='Html->url(array('controller' => 'events', 'action' => 'view', $signature['Signature']['event_id']), true) ;?>';"> + + + + + + +
    Paginator->sort('event_id');?>Paginator->sort('type');?>Paginator->sort('value');?>
    + Html->link($signature['Event']['id'], array('controller' => 'events', 'action' => 'view', $signature['Event']['id'])); ?> +    + Html->link(__('Edit', true), array('action' => 'edit', $signature['Signature']['id'])); + echo $this->Html->link(__('Delete', true), array('action' => 'delete', $signature['Signature']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $signature['Signature']['id'])); + } ?> + Html->link(__('View', true), array('action' => 'view', $signature['Signature']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) + )); + ?>

    + +
    + Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?> + | Paginator->numbers();?> + | + Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?> +
    +
    +
    +

    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/views/signatures/search.ctp b/app/views/signatures/search.ctp new file mode 100755 index 000000000..a26e45028 --- /dev/null +++ b/app/views/signatures/search.ctp @@ -0,0 +1,16 @@ +
    +Form->create('Signature');?> +
    + + Form->input('keyword'); + ?> +
    +Form->end(__('Search', true));?> +
    +
    +

    +
      + element('actions_menu'); ?> +
    +
    \ No newline at end of file diff --git a/app/views/signatures/view.ctp b/app/views/signatures/view.ctp new file mode 100755 index 000000000..3f5f87e72 --- /dev/null +++ b/app/views/signatures/view.ctp @@ -0,0 +1,36 @@ +
    +

    +
    + > + > + +   + + > + > + Html->link($signature['Event']['id'], array('controller' => 'events', 'action' => 'view', $signature['Event']['id'])); ?> +   + + > + > + +   + + > + > + +   + +
    +
    +
    +

    +
      + +
    • Html->link(__('Edit Signature', true), array('action' => 'edit', $signature['Signature']['id'])); ?>
    • +
    • Html->link(__('Delete Signature', true), array('action' => 'delete', $signature['Signature']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $signature['Signature']['id'])); ?>
    • +
    •  
    • + + element('actions_menu'); ?> +
    +
    diff --git a/app/views/users/add.ctp b/app/views/users/add.ctp new file mode 100755 index 000000000..f603e7ade --- /dev/null +++ b/app/views/users/add.ctp @@ -0,0 +1,21 @@ +
    +Form->create('User');?> +
    + + Form->input('group_id'); + echo $this->Form->input('email'); + echo $this->Form->input('password'); + echo $this->Form->input('org'); + echo $this->Form->input('autoalert'); + echo $this->Form->input('gpgkey'); + ?> +
    +Form->end(__('Submit', true));?> +
    +
    +

    +
      + element('actions_menu'); ?> +
    +
    diff --git a/app/views/users/edit.ctp b/app/views/users/edit.ctp new file mode 100755 index 000000000..8ada7a37a --- /dev/null +++ b/app/views/users/edit.ctp @@ -0,0 +1,26 @@ +
    +Form->create('User');?> +
    + + Form->input('id'); + if ($isAdmin) echo $this->Form->input('group_id'); + echo $this->Form->input('email'); + echo $this->Form->input('password'); + if ($isAdmin) echo $this->Form->input('org'); + else echo $this->Form->input('org', array('disabled' => 'disabled')); + echo $this->Form->input('autoalert'); + echo $this->Form->input('gpgkey'); + ?> +
    +Form->end(__('Submit', true));?> +
    + +
    +

    +
      +
    • Html->link(__('Delete', true), array('action' => 'delete', $this->Form->value('User.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $this->Form->value('User.id'))); ?>
    • +
    •  
    • + element('actions_menu'); ?> +
    +
    diff --git a/app/views/users/index.ctp b/app/views/users/index.ctp new file mode 100755 index 000000000..7bf9e367c --- /dev/null +++ b/app/views/users/index.ctp @@ -0,0 +1,59 @@ +
    +

    + + + + + + + + + + + + > + + + + + + + + + +
    Paginator->sort('id');?>Paginator->sort('group_id');?>Paginator->sort('email');?>Paginator->sort('org');?>Paginator->sort('autoalert');?>Paginator->sort('authkey');?>
      + Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?> +      + Html->link(__('View', true), array('action' => 'view', $user['User']['id'])); ?> + Html->link(__('Edit', true), array('action' => 'edit', $user['User']['id'])); ?> + Html->link(__('Delete', true), array('action' => 'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id'])); ?> +
    +

    + Paginator->counter(array( + 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) + )); + ?>

    + +
    + Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?> + | Paginator->numbers();?> + | + Paginator->next(__('next', true) . ' >>', array(), null, array('class' => 'disabled'));?> +
    +
    + +
    +

    +
      + element('actions_menu'); ?> +
    +
    + diff --git a/app/views/users/login.ctp b/app/views/users/login.ctp new file mode 100755 index 000000000..88f53bb3d --- /dev/null +++ b/app/views/users/login.ctp @@ -0,0 +1,12 @@ +Session->flash('auth'); +echo $this->Form->create('User', array('action' => 'login')); +echo $this->Form->inputs(array( + 'legend' => __('Login', true), + 'email', + 'password' +)); +//echo $this->Recaptcha->display(); + +echo $this->Form->end('Login'); +?> diff --git a/app/views/users/view.ctp b/app/views/users/view.ctp new file mode 100755 index 000000000..34e09a61e --- /dev/null +++ b/app/views/users/view.ctp @@ -0,0 +1,99 @@ +
    +

    +
    + > + > + +   + + > + > + Html->link($user['Group']['name'], array('controller' => 'groups', 'action' => 'view', $user['Group']['id'])); ?> +   + + > + > + +   + + > + > + + XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +   + + > + > + +   + + > + > + +   + + > + > + +   + + > + style="font-size: 10px; padding:0px; margin:0px;line-height:100%;"> +
    +   + +
    +
    +
    +

    +
      +
    • Html->link(__('Edit User', true), array('action' => 'edit', $user['User']['id'])); ?>
    • +
    • Html->link(__('Delete User', true), array('action' => 'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id'])); ?>
    • +
    •  
    • + element('actions_menu'); ?> +
    +
    + diff --git a/app/webroot/.htaccess b/app/webroot/.htaccess new file mode 100644 index 000000000..f9d8b938b --- /dev/null +++ b/app/webroot/.htaccess @@ -0,0 +1,6 @@ + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] + \ No newline at end of file diff --git a/app/webroot/css.php b/app/webroot/css.php new file mode 100644 index 000000000..8bf90839f --- /dev/null +++ b/app/webroot/css.php @@ -0,0 +1,96 @@ +compress($data); + $ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100); + $output = " /* file: $name, ratio: $ratio% */ " . $output; + return $output; + } +/** + * Write CSS cache + * + * @param unknown_type $path + * @param unknown_type $content + * @return unknown + */ + function write_css_cache($path, $content) { + if (!is_dir(dirname($path))) { + mkdir(dirname($path)); + } + $cache = new File($path); + return $cache->write($content); + } + + if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs)) { + die('Wrong file name.'); + } + + $filename = 'css/' . $regs[1]; + $filepath = CSS . $regs[1]; + $cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]); + + if (!file_exists($filepath)) { + die('Wrong file name.'); + } + + if (file_exists($cachepath)) { + $templateModified = filemtime($filepath); + $cacheModified = filemtime($cachepath); + + if ($templateModified > $cacheModified) { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + } else { + $output = file_get_contents($cachepath); + } + } else { + $output = make_clean_css($filepath, $filename); + write_css_cache($cachepath, $output); + $templateModified = time(); + } + + header("Date: " . date("D, j M Y G:i:s ", $templateModified) . 'GMT'); + header("Content-Type: text/css"); + header("Expires: " . gmdate("D, d M Y H:i:s", time() + DAY) . " GMT"); + header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1 + header("Pragma: cache"); // HTTP/1.0 + print $output; diff --git a/app/webroot/css/cake.generic.css b/app/webroot/css/cake.generic.css new file mode 100644 index 000000000..281d99d25 --- /dev/null +++ b/app/webroot/css/cake.generic.css @@ -0,0 +1,555 @@ +/** + * + * Generic CSS for CakePHP + * + * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) + * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @package cake + * @subpackage cake.app.webroot.css + * @since CakePHP(tm) + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ + +* { + margin:0; + padding:0; +} + +/** General Style Info **/ +body { + background: #003d4c; + color: #fff; + font-family:'lucida grande',verdana,helvetica,arial,sans-serif; + font-size:90%; + margin: 0; +} +a { + color: #003d4c; + text-decoration: underline; + font-weight: bold; +} +a:hover { + color: #367889; + text-decoration:none; +} +a img { + border:none; +} +h1, h2, h3, h4 { + font-weight: normal; + margin-bottom:0.5em; +} +h1 { + background:#fff; + color: #003d4c; + font-size: 100%; +} +h2 { + background:#fff; + color: #e32; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 190%; +} +h3 { + color: #993; + font-family:'Gill Sans','lucida grande', helvetica, arial, sans-serif; + font-size: 165%; +} +h4 { + color: #993; + font-weight: normal; +} +ul, li { + margin: 0 12px; +} + +/** Layout **/ +#container { + text-align: left; +} + +#header{ + padding: 10px 20px; +} +#header h1 { + line-height:20px; + background: #003d4c url('../img/cake.icon.png') no-repeat left; + color: #fff; + padding: 0px 30px; +} +#header h1 a { + color: #fff; + background: #003d4c; + font-weight: normal; + text-decoration: none; +} +#header h1 a:hover { + color: #fff; + background: #003d4c; + text-decoration: underline; +} +#content{ + background: #fff; + clear: both; + color: #333; + padding: 10px 20px 40px 20px; + overflow: auto; +} +#footer { + clear: both; + padding: 6px 10px; + text-align: right; +} + +/** containers **/ +div.form, +div.index, +div.view { + float:right; + width:76%; + border-left:1px solid #666; + padding:10px 2%; +} +div.actions { + float:left; + width:16%; + padding:10px 1.5%; +} +div.actions h3 { + padding-top:0; + color:#777; +} + + +/** Tables **/ +table { + background: #fff; + border-right:0; + clear: both; + color: #333; + margin-bottom: 10px; + width: 100%; +} +th { + border:0; + border-bottom:2px solid #555; + text-align: left; + padding:4px; +} +th a { + display: block; + padding: 2px 4px; + text-decoration: none; +} +th a.asc:after { + content: ' ⇣'; +} +th a.desc:after { + content: ' ⇡'; +} +table tr td { + /*background: #fff;*/ + padding: 6px; + text-align: left; + vertical-align: top; + border-bottom:1px solid #ddd; +} +/*table tr:nth-child(2n) td { + background: #f5f5f5; +} +table .altrow td { + background: #f5f5f5; +}*/ +td.actions { + text-align: center; + white-space: nowrap; +} +table td.actions a { + margin: 0px 6px; + padding:2px 5px; +} + +/* added */ +div .events table tr:hover, div .events table tr.altrow:hover, div .signatures table tr:hover, div .signatures table tr.altrow:hover{ + background-color: #F5F5D7; +} + +table tr.altrow { + background: #f5f5f5; +} +table tr:nth-child(2n) { + background: #f5f5f5; +} +/* /added */ + +.cake-sql-log table { + background: #f4f4f4; +} +.cake-sql-log td { + padding: 4px 8px; + text-align: left; + font-family: Monaco, Consolas, "Courier New", monospaced; +} +.cake-sql-log caption { + color:#fff; +} + +/** Paging **/ +div.paging { + background:#fff; + color: #ccc; + margin-top: 1em; + clear:both; +} +div.paging span.disabled { + color: #ddd; + display: inline; +} +div.paging span.current { + color: #c73e14; +} +div.paging span a { +} + +/** Scaffold View **/ +dl { + line-height: 2em; + margin: 0em 0em; + width: 60%; +} +dl .altrow { + background: #f4f4f4; +} +dt { + font-weight: bold; + padding-left: 4px; + vertical-align: top; + width: 10em; +} +dd { + margin-left: 10em; + margin-top: -2em; + vertical-align: top; +} + +/** Forms **/ +form { + clear: both; + margin-right: 20px; + padding: 0; + width: 95%; +} +fieldset { + border: 1px solid #ccc; + margin-bottom: 1em; + padding: 16px 20px; +} +fieldset legend { + background:#fff; + color: #e32; + font-size: 160%; + font-weight: bold; +} +fieldset fieldset { + margin-top: 0px; + margin-bottom: 20px; + padding: 16px 10px; +} +fieldset fieldset legend { + font-size: 120%; + font-weight: normal; +} +fieldset fieldset div { + clear: left; + margin: 0 20px; +} +form div { + clear: both; + margin-bottom: 1em; + padding: .5em; + vertical-align: text-top; +} +form .input { + color: #444; +} +form .required { + font-weight: bold; +} +form .required label:after { + color: #e32; + content: '*'; + display:inline; +} +form div.submit { + border: 0; + clear: both; + margin-top: 10px; +} +label { + display: block; + font-size: 110%; + margin-bottom:3px; +} +input, textarea { + clear: both; + font-size: 140%; + font-family: "frutiger linotype", "lucida grande", "verdana", sans-serif; + padding: 1%; + width:98%; +} +select { + clear: both; + font-size: 120%; + vertical-align: text-bottom; +} +select[multiple=multiple] { + width: 100%; +} +option { + font-size: 120%; + padding: 0 3px; +} +input[type=checkbox] { + clear: left; + float: left; + margin: 0px 6px 7px 2px; + width: auto; +} +div.checkbox label { + display: inline; +} +input[type=radio] { + float:left; + width:auto; + margin: 0 3px 7px 0; +} +div.radio label { + margin: 0 0 6px 20px; +} +input[type=submit] { + display: inline; + font-size: 110%; + width: auto; +} +form .submit input[type=submit] { + background:#62af56; + background: -webkit-gradient(linear, left top, left bottom, from(#a8ea9c), to(#62af56)); + background-image: -moz-linear-gradient(top, #a8ea9c, #62af56); + border-color: #2d6324; + color: #000; + text-shadow: #8cee7c 0px 1px 0px; +} +form .submit input[type=submit]:hover { + background:#4ca83d; + background: -webkit-gradient(linear, left top, left bottom, from(#85e573), to(#4ca83d)); + background-image: -moz-linear-gradient(top, #85e573, #4ca83d); +} + +/** Notices and Errors **/ +div.message { + clear: both; + color: #fff; + font-size: 140%; + font-weight: bold; + margin: 0 0 1em 0; + background: #c73e14; + padding: 5px; +} +div.error-message { + clear: both; + color: #fff; + font-weight: bold; + background: #c73e14; +} +p.error { + background-color: #e32; + color: #fff; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +p.error em { + color: #000; + font-weight: normal; + line-height: 140%; +} +.notice { + background: #ffcc00; + color: #000; + display: block; + font-family: Courier, monospace; + font-size: 120%; + line-height: 140%; + padding: 0.8em; + margin: 1em 0; +} +.success { + background: green; + color: #fff; +} + +/** Actions **/ +div.actions ul { + margin: 0; + padding: 0; +} +div.actions li { + margin:0 0 0.5em 0; + list-style-type: none; + white-space: nowrap; + padding: 0; +} +div.actions ul li a { + font-weight: normal; + display: block; + clear: both; +} +div.actions ul li a:hover { + text-decoration: underline; +} + +input[type=submit], +div.actions ul li a, +td.actions a { + font-weight:normal; + padding: 4px 8px; + background:#e6e49f; + background: -webkit-gradient(linear, left top, left bottom, from(#f1f1d4), to(#e6e49f)); + background-image: -moz-linear-gradient(top, #f1f1d4, #e6e49f); + color:#333; + border:1px solid #aaac62; + -webkit-border-radius:8px; + -moz-border-radius:8px; + border-radius:8px; + text-decoration:none; + text-shadow: #fff 0px 1px 0px; + min-width: 0; +} +input[type=submit]:hover, +div.actions ul li a:hover, +td.actions a:hover { + background: #f0f09a; + background: -webkit-gradient(linear, left top, left bottom, from(#f7f7e1), to(#eeeca9)); +} + +/** Related **/ +div.related { + clear: both; + display: block; +} + +/** Debugging **/ +pre { + color: #000; + background: #f0f0f0; + padding: 1em; +} +pre.cake-debug { + background: #ffcc00; + font-size: 120%; + line-height: 140%; + margin-top: 1em; + overflow: auto; + position: relative; +} +div.cake-stack-trace { + background: #fff; + color: #333; + margin: 0px; + padding: 6px; + font-size: 120%; + line-height: 140%; + overflow: auto; + position: relative; +} +div.cake-code-dump pre { + position: relative; + overflow: auto; +} +div.cake-stack-trace pre, div.cake-code-dump pre { + color: #000; + background-color: #F0F0F0; + margin: 0px; + padding: 1em; + overflow: auto; +} +div.cake-code-dump pre, div.cake-code-dump pre code { + clear: both; + font-size: 12px; + line-height: 15px; + margin: 4px 2px; + padding: 4px; + overflow: auto; +} +div.cake-code-dump span.code-highlight { + background-color: #ff0; + padding: 4px; +} +div.code-coverage-results div.code-line { + padding-left:5px; + display:block; + margin-left:10px; +} +div.code-coverage-results div.uncovered span.content { + background:#ecc; +} +div.code-coverage-results div.covered span.content { + background:#cec; +} +div.code-coverage-results div.ignored span.content { + color:#aaa; +} +div.code-coverage-results span.line-num { + color:#666; + display:block; + float:left; + width:20px; + text-align:right; + margin-right:5px; +} +div.code-coverage-results span.line-num strong { + color:#666; +} +div.code-coverage-results div.start { + border:1px solid #aaa; + border-width:1px 1px 0px 1px; + margin-top:30px; + padding-top:5px; +} +div.code-coverage-results div.end { + border:1px solid #aaa; + border-width:0px 1px 1px 1px; + margin-bottom:30px; + padding-bottom:5px; +} +div.code-coverage-results div.realstart { + margin-top:0px; +} +div.code-coverage-results p.note { + color:#bbb; + padding:5px; + margin:5px 0 10px; + font-size:10px; +} +div.code-coverage-results span.result-bad { + color: #a00; +} +div.code-coverage-results span.result-ok { + color: #fa0; +} +div.code-coverage-results span.result-good { + color: #0a0; +} + +/** Elements **/ +#url-rewriting-warning { + display: none; +} diff --git a/app/webroot/favicon.ico b/app/webroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b36e81f2f35133dede48dc18f78d3e1a3353f7bc GIT binary patch literal 372 zcmV-)0gL{LP)Ce4&o}{bgKX)=r-B_#-Qs`WM)Da5Prgl zsZ%T@FChJjuHh1`0m$SsVlk8KBKw^@522Z3S}fqzqUy4nLhVp$W)@MMy#WF!pk3U7 zB65kQi)kZ#rBYK5tRifZI?>XjFU4<@lsph9Z1>blC@{qW13mof`13?K&cOa z9q)cr7> zIGsQFGn3| zCzs2iP$-yfVPOGVTU&6sT(-5fwHb2tVsLP9#{Vr9Ct?R7q(rf?v2A5#W$OI=e1YUJ zQ1YRnA&iWSQ1XYAm__>aYb6XIhMiYVD+-z8_pYi6+CsH{*^m;vOjqvbr=H&DFkeqxHQBh$Scsoy0Glw(T zsaSG*ok62V;~yXYNgP*DUw;o98^+0@vGFb{HC+As}XJ=;xg=B7N_;-mKbHH{|lXs_o+aPcs5~J?s%^P2Odb)Uz z$GvY6^!N9(C2-h?28B$qx7%_yHnt2eU%nQ0qThbl6a_+b)EirjBgQ`g1_07Fr&6R? RzIgxu002ovPDHLkV1mdlwUYn< literal 0 HcmV?d00001 diff --git a/app/webroot/img/cake.power.gif b/app/webroot/img/cake.power.gif new file mode 100644 index 0000000000000000000000000000000000000000..8f8d570a2e24d86f0ad7730ee8f2435fd49f152c GIT binary patch literal 201 zcmV;)05<&ZTq0L2I(c1A@d@rg`ENj#vn zcl`yi#iKX*jb2F7vd0WQgUq5Tw}Jp}g+ZnCeBY3dYNI+m71%bHRfx4UCkD2th(Q*@ zmd5r+MJNYn7MP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0006>Nklb9w)H??3Zq{to|uwe}MC z?e+bSKTh?^`{wD^wWPb{@}CHTKzw?6`$N5PyyB2n?^xO1cT3;J75nFbCC72*cXxMX zxm>nuPu5Jmal8UN_lgUheaAk$l{jyl3&KrcBcIP#g%B8HC>Dz>KUn6j_Z4&V8GCzs z-A1EvJqQBvqUM>*;2yA1EEd0ylNIZ(i;!35`Jv=;2ffT?2Hm9ejw-*-|nVOoS zP$-bg<ZSNe~2d zIvrfsrBEnvbaVtD0RtdB&%Ev&{`8k5nAgpm!>J!T7(dIo(HhhYPE122Ukjo z)|xnuiK1xS*%(6!=07%mmpr3`|W=94{ zl7!J{gb;!>P0?DTlp>B}thHEcQAz=P&I}}wOVjjGmP3*xD5d^b*-V_DpQDs|1-u(S z2jBtm%*+h=eE!N=P)c!fasm(lgNzq|0@nKdKEvVguj1iwnEipLzl$$o0oV~jSl{>U o#KeU4ecuWpEU@#dehEJZ0P;*FSb&o|IsgCw07*qoM6N<$f;Xi=v;Y7A literal 0 HcmV?d00001 diff --git a/app/webroot/img/test-fail-icon.png b/app/webroot/img/test-fail-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d2f147ec4ef406186c967c17aa9d592a09336e GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8r2#%6u9q1Y_A@XXWnehT!0_XL%T5M{>kJI%85mYDFg#fm z@qmG069dC728KUx-uyQ(To^b!NZ6$XaAf}9Vfq#lclKVe|Fz`$^pf#C)( z?|%b>GYkxe7#Qw~i+_GseHzG+ka#R5b(MkPp}6=pPR{@T|KFMBG8O1x^O7LHU?7(a zz#tGgw;O20JWm(LkcwMA=Z`Zr83?dk2y7BE6=YUe|*dabSZF*mIx>v0bsK zhImj64Tcwn`A_+lH49lnMl*u*gGmJ>51|J|OF=yr*B8yJW~ec3o5lk`2g|wzshC-w ziSCMq2n+=S*%-Br(g+${U@~D`FhT&sxB*oN&;*cc$WcTT9wVtcsdKUucOmbP;||0x z#q3tLa6o9^vHKPIF?*Owqq5D^MpaVDQnH801Nwb#BL&(K7w$YlkeuS0|3?^KwXL|W zZgJvS!cr?^E>%Hd`MA%ASbAg$QJRhgw-~dTt8se0&H5u6>~Hz*7HSB4*!uq4vq;NjRN`wbRrY=k ztZO7Hf9`nx3i*;j@S2CmwzWRi?KpG$@ix}{S-*Q_F&XAfUVh5qiW8;W{?l*PWU~|& zXJF+?I7gWJwdv?FLs}6xsv$ajvf*#NUOnZcOK`I#;=J!d&L`LN;INb9hb~B}{sAr8 BX72z1 literal 0 HcmV?d00001 diff --git a/app/webroot/img/test-skip-icon.png b/app/webroot/img/test-skip-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..749771c9895a1abbb843f894540a43a5dc426b2c GIT binary patch literal 1207 zcmcgr`BM@I7-iGaX-3y=+tk$2=@1heYqQ2Fr&dQR+w#8F+EyJ=3j{$FBt!*70YwQk z&qPo`NyPiUZ$&My$ooDs54`YP_Mg~q=6i3xpWeLp=A+ozJW)A+`8)ssP_ce$=^$sd z|5jN+js`b>c9fGQ%E}Gp2=_suJ&|C5xi{PktY_`-=?iuMdwPc;I>BZDfC9koxs%l! zEKAYA?)3C@cTc8#*X_a+xXL52WNcR59{1Y|%sC@xAdC&dt3FBE?v&eJ(VET#C1aPfAbX9c(2(>>t!J=b9>Y}4{Vv&J?_v2@ z{Zeit+2+s~*}>tpx2alAc$vPpzT>hM@4;GBTU{^zhonf02G`{6!jDK79JiR5fMQk5Qsz~iA0Kwj3krEQBhIR(a{tNg-WH;XtbD^nAq4@I-MRD z7Z)EN&tNd#zkknUGFdEELPA1fV&aDnACi)il9Q8DQc_Y=Q`6GY($mvFe*Bn`k-=uO zGcz+e91fSu&C1Hk&d$!s$>H&Md_F%nH#aXYPaqI{`t&J3KVK*m78DeSM54mN!lI(0 z;^N|xl9JNW(z3F$^78VEii*n0%BrfW>gwv6nwr|$+Pb>B`uh5YhK9z*#-^sG=H}*> zmX_Am*0#2`_V)IUj*iaG&aSSm?(S}}SlrXo)7#tI*Vp&?^XD&LzV!F^4-52q~$`a_L84UdB=6VyZ=u9nnig?j|*#Wx)G#h*aePfzjUYo@M;4}Fb&*iuQMNlT)P zH1r_DmZyA5)S=yZ1P7iME0&D<)aAv^j SzbA!qhX89U8%v>u2jO4XOSDb^ literal 0 HcmV?d00001 diff --git a/app/webroot/index.php b/app/webroot/index.php new file mode 100644 index 000000000..5b5c98709 --- /dev/null +++ b/app/webroot/index.php @@ -0,0 +1,84 @@ +dispatch(); + } diff --git a/app/webroot/js/empty b/app/webroot/js/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/webroot/js/jquery.js b/app/webroot/js/jquery.js new file mode 100644 index 000000000..48590ecb9 --- /dev/null +++ b/app/webroot/js/jquery.js @@ -0,0 +1,18 @@ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
    a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
    ",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
    t
    ",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
    ";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/app/webroot/robots.txt b/app/webroot/robots.txt new file mode 100644 index 000000000..1f53798bb --- /dev/null +++ b/app/webroot/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/app/webroot/test.php b/app/webroot/test.php new file mode 100644 index 000000000..e0c69a164 --- /dev/null +++ b/app/webroot/test.php @@ -0,0 +1,94 @@ + + * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/view/1196/Testing + * @package cake + * @subpackage cake.app.webroot + * @since CakePHP(tm) v 1.2.0.4433 + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +set_time_limit(0); +ini_set('display_errors', 1); +/** + * Use the DS to separate the directories in other defines + */ + if (!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); + } +/** + * These defines should only be edited if you have cake installed in + * a directory layout other than the way it is distributed. + * When using custom settings be sure to use the DS and do not add a trailing DS. + */ + +/** + * The full path to the directory which holds "app", WITHOUT a trailing DS. + * + */ + if (!defined('ROOT')) { + define('ROOT', dirname(dirname(dirname(__FILE__)))); + } +/** + * The actual directory name for the "app". + * + */ + if (!defined('APP_DIR')) { + define('APP_DIR', basename(dirname(dirname(__FILE__)))); + } +/** + * The absolute path to the "cake" directory, WITHOUT a trailing DS. + * + */ + if (!defined('CAKE_CORE_INCLUDE_PATH')) { + define('CAKE_CORE_INCLUDE_PATH', ROOT); + } + +/** + * Editing below this line should not be necessary. + * Change at your own risk. + * + */ +if (!defined('WEBROOT_DIR')) { + define('WEBROOT_DIR', basename(dirname(__FILE__))); +} +if (!defined('WWW_ROOT')) { + define('WWW_ROOT', dirname(__FILE__) . DS); +} +if (!defined('CORE_PATH')) { + if (function_exists('ini_set') && ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get('include_path'))) { + define('APP_PATH', null); + define('CORE_PATH', null); + } else { + define('APP_PATH', ROOT . DS . APP_DIR . DS); + define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS); + } +} +if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) { + trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR); +} + +$corePath = App::core('cake'); +if (isset($corePath[0])) { + define('TEST_CAKE_CORE_INCLUDE_PATH', rtrim($corePath[0], DS) . DS); +} else { + define('TEST_CAKE_CORE_INCLUDE_PATH', CAKE_CORE_INCLUDE_PATH); +} + +if (Configure::read('debug') < 1) { + die(__('Debug setting does not allow access to this url.', true)); +} + +require_once CAKE_TESTS_LIB . 'cake_test_suite_dispatcher.php'; + +$Dispatcher = new CakeTestSuiteDispatcher(); +$Dispatcher->dispatch();