From dacd4fd329f5cb01452f258f11a204dd8bc345be Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Fri, 21 Sep 2012 21:47:51 +0930 Subject: [PATCH 001/136] Initial commit of refactored Feather. Signed-off-by: Jason Lewis --- applications/core/routes.php | 6 +++ applications/core/start.php | 1 + config/components.php | 5 +++ config/database.php | 13 ++++++ start.php | 76 ++++++++++++++++++++++++++++++++++++ start/autoloading.php | 5 +++ start/facades.php | 7 ++++ 7 files changed, 113 insertions(+) create mode 100644 applications/core/routes.php create mode 100644 applications/core/start.php create mode 100644 config/components.php create mode 100644 config/database.php create mode 100644 start.php create mode 100644 start/autoloading.php create mode 100644 start/facades.php diff --git a/applications/core/routes.php b/applications/core/routes.php new file mode 100644 index 0000000..e9c1eaf --- /dev/null +++ b/applications/core/routes.php @@ -0,0 +1,6 @@ + 'localhost', + 'database' => 'feather', + 'username' => 'root', + 'password' => '', + 'prefix' => '', + 'charset' => 'utf8', + 'driver' => 'mysql' + +); \ No newline at end of file diff --git a/start.php b/start.php new file mode 100644 index 0000000..02faa4a --- /dev/null +++ b/start.php @@ -0,0 +1,76 @@ +share(function() +{ + return new Components\Config\Repository; +}); + +$feather['config']->set('laravel: database.connections.feather', $feather['config']->get('feather: database')); + +//$feather['config']->db(); + +/* +|-------------------------------------------------------------------------- +| Register Feather Applications +|-------------------------------------------------------------------------- +| +| Feather is split into separate applications for better separation of +| logic. Register each of the applications as defined in the configuration +| with the Laravel bundle manager. +| +*/ + diff --git a/start/autoloading.php b/start/autoloading.php new file mode 100644 index 0000000..6be24be --- /dev/null +++ b/start/autoloading.php @@ -0,0 +1,5 @@ + path('feather') +)); \ No newline at end of file diff --git a/start/facades.php b/start/facades.php new file mode 100644 index 0000000..365f928 --- /dev/null +++ b/start/facades.php @@ -0,0 +1,7 @@ + Date: Fri, 21 Sep 2012 21:48:17 +0930 Subject: [PATCH 002/136] Initial commit of application component. Signed-off-by: Jason Lewis --- components/feather/application.php | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 components/feather/application.php diff --git a/components/feather/application.php b/components/feather/application.php new file mode 100644 index 0000000..320e653 --- /dev/null +++ b/components/feather/application.php @@ -0,0 +1,108 @@ +register($this); + } + + /** + * Share a closure such that it is a singleton. + * + * @param Closure $closure + * @return Closure + */ + public function share(Closure $closure) + { + return function($container) use ($closure) + { + // We'll declare a static object here, if the object is not defined + // then we'll get execute the closure and return it. + static $object; + + if(is_null($object)) + { + $object = $closure($container); + } + + return $object; + }; + } + + /** + * Determine if an offset exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->container[$key]); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + if(!$this->offsetExists($key)) + { + throw new InvalidArgumentException("Identifier {$key} is not in the container."); + } + + return $this->container[$key]($this); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + if(!$value instanceof Closure) + { + $value = function() use ($value) + { + return $value; + }; + } + + $this->container[$key] = $value; + } + + /** + * Unset a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->container[$key]); + } + +} \ No newline at end of file From 959f7d3c03da81f6067c7accedce41ffd2069a59 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Fri, 21 Sep 2012 21:48:41 +0930 Subject: [PATCH 003/136] Initial commit of config component and config tests. Signed-off-by: Jason Lewis --- components/config/repository.php | 134 +++++++++++++++++++++++++++++++ tests/config.test.php | 59 ++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 components/config/repository.php create mode 100644 tests/config.test.php diff --git a/components/config/repository.php b/components/config/repository.php new file mode 100644 index 0000000..2626ff7 --- /dev/null +++ b/components/config/repository.php @@ -0,0 +1,134 @@ +key, str_replace('(:feather)', '(:bundle)', $item->value)); + } + + return $config; + }); + } + + /** + * Determine if a key exists. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return !is_null($this->get($key)); + } + + /** + * Set a config key to a given value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + return Config::set($this->prefix($key), $value); + } + + /** + * Get a key from the configuration. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + return Config::get($this->prefix($key), $default); + } + + /** + * Determine the prefix for a given key. + * + * @param string $key + * @return string + */ + private function prefix($key) + { + if(!str_contains($key, ': ')) + { + return $key; + } + + list($prefix, $key) = explode(': ', $key); + + switch($prefix) + { + // The feather prefix is applied to both database and file-based + // configuration items located within the bundle. + case 'feather': + return "feather::{$key}"; + break; + + // The gear prefix is applied to Feather gears, the gear + // name should appear before the file and key to be used. + case 'gear': + list($gear, $key) = explode(' ', $key); + + return "feather::gear:{$gear} {$key}"; + break; + default: + return $key; + } + } + +} \ No newline at end of file diff --git a/tests/config.test.php b/tests/config.test.php new file mode 100644 index 0000000..85778d2 --- /dev/null +++ b/tests/config.test.php @@ -0,0 +1,59 @@ +getRepository(); + + $config->set('name', 'jason'); + $this->assertEquals('jason', $config->get('name')); + + $config->set('person.name', 'jason'); + $this->assertEquals('jason', $config->get('person.name')); + + $config->set('namespace::person.name', 'jason'); + $this->assertEquals('jason', $config->get('namespace::person.name')); + + $config->set('gear: mock person.name', 'jason'); + $this->assertEquals('jason', $config->get('gear: mock person.name')); + + $config->set('theme: mock person.name', 'jason'); + $this->assertEquals('jason', $config->get('theme: mock person.name')); + } + + public function testGetBasicItems() + { + $config = $this->getRepository(); + + $this->assertEquals('bar', $config->get('test.foo')); + $this->assertEquals('orange', $config->get('test.apple')); + } + + public function testEntireArrayCanBeReturned() + { + $config = $this->getRepository(); + + $this->assertEquals($this->getItems(), $config->get('test')); + } + + private function getRepository() + { + $config = new Feather\Components\Config\Repository; + + $config->set('test', $this->getItems()); + + return $config; + } + + private function getItems() + { + return array('foo' => 'bar', 'apple' => 'orange'); + } + +} \ No newline at end of file From 3e7a16f324d0cd4095cd3e212da39d04cabf349e Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 00:27:47 +0930 Subject: [PATCH 004/136] Allow saving and deleting of database configuration items. Signed-off-by: Jason Lewis --- components/config/repository.php | 153 ++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/components/config/repository.php b/components/config/repository.php index 2626ff7..c24fbab 100644 --- a/components/config/repository.php +++ b/components/config/repository.php @@ -1,5 +1,6 @@ table('config')->get() as $item) { array_set($config, $item->key, str_replace('(:feather)', '(:bundle)', $item->value)); } return $config; }); + + foreach($items as $key => $item) + { + array_set(Config::$items['feather']['db'], $key, $item); + } + } + + /** + * Reload the database configuration items. + * + * @return void + */ + public function reload() + { + Cache::forget('config'); + + unset(Config::$items['feather']['db']); + + $this->db(); } /** @@ -81,6 +108,13 @@ public function has($key) */ public function set($key, $value) { + // If the key does not exist and it belongs to the database configuration the item is + // dirty and needs to be inserted. + if(!$this->has($key) and starts_with($this->idenfitier($key), 'db')) + { + $this->dirty[] = substr($this->idenfitier($key), 3); + } + return Config::set($this->prefix($key), $value); } @@ -96,6 +130,105 @@ public function get($key, $default = null) return Config::get($this->prefix($key), $default); } + /** + * Save a key, group of keys, or all configuration items to the database. + * Only keys that belong to the database configuration can be saved. + * + * @param array $keys + * @return void + */ + public function save($keys = array()) + { + $items = array(); + + if($keys) + { + // Each key must belong to the database set of configuration items to be saved + // back into the database. + foreach((array) $keys as $key) + { + if(starts_with($this->idenfitier($key), 'db')) + { + $items[substr($this->idenfitier($key), 3)] = $this->get($key); + } + } + } + else + { + $items = $this->get('feather: db'); + } + + $update = $insert = array(); + + foreach($items as $key => $value) + { + // Using variable variables we can assign the item to the correct array + // depending on whether or not the item already exists within the + // database. + $variable = in_array($key, $this->dirty) ? 'insert' : 'update'; + + if(is_array($value)) + { + foreach($value as $key => $value) + { + ${$variable}[] = compact('key', 'value'); + } + + continue; + } + + ${$variable}[] = compact('key', 'value'); + } + + // Using PDO transactions we'll spin through our array of keys to update and execute + // the query for each of them. If something goes wrong the transaction will be rolled + // back automatically. + if($update) + { + DB::connection(FEATHER_DATABASE)->transaction(function() use ($update) + { + foreach($update as $item) + { + DB::connection(FEATHER_DATABASE)->table('config')->where_key($item['key'])->update(array('value' => $item['value'])); + } + }); + } + + // Because inserts behave differently to an update we can perform a batch insert with + // Fluent by providing an array of arrays. We'll use this method instead of transactions. + if($insert) + { + DB::connection(FEATHER_DATABASE)->table('config')->insert($insert); + } + } + + /** + * Delete a key or group of keys from the database. + * Only keys that belong to the database configuration can be deleted. + * + * @param array $keys + * @return void + */ + public function delete($keys) + { + $delete = array(); + + // Each key must belong to the database set of configuration items to be deleted + // from the database. + foreach((array) $keys as $key) + { + if(starts_with($this->idenfitier($key), 'db')) + { + $delete[] = substr($this->idenfitier($key), 3); + } + } + + if($delete) + { + DB::connection(FEATHER_DATABASE)->table('config')->where_in('key', $delete)->delete(); + } + } + /** * Determine the prefix for a given key. * @@ -131,4 +264,22 @@ private function prefix($key) } } + /** + * Determine the idenfitier excluding any namespacing. + * + * @param string $key + * @return string + */ + private function idenfitier($key) + { + if(!str_contains($key, '::') and !str_contains($key = $this->prefix($key), '::')) + { + return null; + } + + list($namespace, $idenfitier) = explode('::', $key); + + return $idenfitier; + } + } \ No newline at end of file From d48ea3525486cff6d23200990413a32123928d68 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 00:28:04 +0930 Subject: [PATCH 005/136] Updated config tests to include saving and deleting. Signed-off-by: Jason Lewis --- tests/config.test.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/config.test.php b/tests/config.test.php index 85778d2..9899485 100644 --- a/tests/config.test.php +++ b/tests/config.test.php @@ -42,6 +42,46 @@ public function testEntireArrayCanBeReturned() $this->assertEquals($this->getItems(), $config->get('test')); } + public function testCheckItemExistance() + { + $config = $this->getRepository(); + + $config->set('foo', 'bar'); + $this->assertTrue($config->has('foo')); + + $this->assertTrue(!$config->has('apple')); + } + + public function testItemsCanBeSaved() + { + $config = $this->getRepository(); + + $config->set('feather: db.test', 'foobar'); + $config->save('feather: db.test'); + + $config->reload(); + + $this->assertEquals('foobar', $config->get('feather: db.test')); + + $config->delete('feather: db.test'); + } + + public function testItemsCanBeDeleted() + { + $config = $this->getRepository(); + + $config->set('feather: db.test', 'foobar'); + $config->save('feather: db.test'); + + $config->reload(); + + $config->delete('feather: db.test'); + + $config->reload(); + + $this->assertEquals(null, $config->get('feather: db.test')); + } + private function getRepository() { $config = new Feather\Components\Config\Repository; From e0717f07d9351f8b1e91f066fec7b8624f8278bc Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 00:29:19 +0930 Subject: [PATCH 006/136] No longer break off the start script when in CLI. Signed-off-by: Jason Lewis --- start.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/start.php b/start.php index 02faa4a..92ed333 100644 --- a/start.php +++ b/start.php @@ -1,6 +1,7 @@ set('laravel: database.connections.feather', $feather['config']->get('feather: database')); +define('FEATHER_DATABASE', 'feather'); -//$feather['config']->db(); +$feather['config']->set('laravel: database.connections.' . FEATHER_DATABASE, $feather['config']->get('feather: database')); + +$feather['config']->db(); /* |-------------------------------------------------------------------------- @@ -72,5 +75,4 @@ | logic. Register each of the applications as defined in the configuration | with the Laravel bundle manager. | -*/ - +*/ \ No newline at end of file From 42d2bd849ebdea57e9c73d4f425c6bdd6acf3ae1 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 00:29:34 +0930 Subject: [PATCH 007/136] Initial commit of support component. Signed-off-by: Jason Lewis --- components/support/facade.php | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 components/support/facade.php diff --git a/components/support/facade.php b/components/support/facade.php new file mode 100644 index 0000000..7d9db6f --- /dev/null +++ b/components/support/facade.php @@ -0,0 +1,7 @@ + Date: Sat, 22 Sep 2012 00:33:25 +0930 Subject: [PATCH 008/136] Namespaced the autoloading start script. Signed-off-by: Jason Lewis --- start/autoloading.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/start/autoloading.php b/start/autoloading.php index 6be24be..6f453f0 100644 --- a/start/autoloading.php +++ b/start/autoloading.php @@ -1,4 +1,15 @@ - path('feather') From 9cac24645a99ff4c8d4810ef517ea6bb03496051 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 00:59:47 +0930 Subject: [PATCH 009/136] Initial commit of facade tests. Signed-off-by: Jason Lewis --- tests/facade.test.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/facade.test.php diff --git a/tests/facade.test.php b/tests/facade.test.php new file mode 100644 index 0000000..150db8f --- /dev/null +++ b/tests/facade.test.php @@ -0,0 +1,29 @@ + new ApplicationStub)); + + $this->assertEquals('apple', FacadeStub::bar()); + } + +} + +class FacadeStub extends Feather\Components\Support\Facade { + + protected static function accessor(){ return 'foo'; } + +} + +class ApplicationStub { + + public function bar() + { + return 'apple'; + } + +} \ No newline at end of file From 62a0fbf36cf3683b3d303f132e6e9b1ca068de54 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:00:11 +0930 Subject: [PATCH 010/136] Polishing off the facades. Signed-off-by: Jason Lewis --- components/support/facade.php | 46 ++++++++++++++++++++++++++++++++++- start.php | 14 +++++++++++ start/facades.php | 9 +++++-- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/components/support/facade.php b/components/support/facade.php index 7d9db6f..0d800c5 100644 --- a/components/support/facade.php +++ b/components/support/facade.php @@ -1,7 +1,51 @@ db(); +/* +|-------------------------------------------------------------------------- +| Load Feather Facades +|-------------------------------------------------------------------------- +| +| Load in the Feather facades to give components a static interface through +| which methods can be accessed throughout the application. +| +*/ + +Components\Support\Facade::application($feather); + +require path('feather') . 'start' . DS . 'facades' . EXT; + /* |-------------------------------------------------------------------------- | Register Feather Applications diff --git a/start/facades.php b/start/facades.php index 365f928..3054823 100644 --- a/start/facades.php +++ b/start/facades.php @@ -2,6 +2,11 @@ class Config extends Components\Support\Facade { - - + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'config'; } + } \ No newline at end of file From 11825a16f35ae0713989618c4e86bec36f2bc53f Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:05:17 +0930 Subject: [PATCH 011/136] Combined the configuration files into a single file. Signed-off-by: Jason Lewis --- config/components.php | 5 ----- config/database.php | 13 ------------ config/feather.php | 47 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 18 deletions(-) delete mode 100644 config/components.php delete mode 100644 config/database.php create mode 100644 config/feather.php diff --git a/config/components.php b/config/components.php deleted file mode 100644 index 207cc1e..0000000 --- a/config/components.php +++ /dev/null @@ -1,5 +0,0 @@ - 'localhost', - 'database' => 'feather', - 'username' => 'root', - 'password' => '', - 'prefix' => '', - 'charset' => 'utf8', - 'driver' => 'mysql' - -); \ No newline at end of file diff --git a/config/feather.php b/config/feather.php new file mode 100644 index 0000000..759709f --- /dev/null +++ b/config/feather.php @@ -0,0 +1,47 @@ + array( + 'host' => 'localhost', + 'database' => 'feather', + 'username' => 'root', + 'password' => '', + 'prefix' => '', + 'charset' => 'utf8', + 'driver' => 'mysql' + ), + + + /* + |-------------------------------------------------------------------------- + | Feather Components + |-------------------------------------------------------------------------- + | + | Components to be registered at runtime with Feather. It is advised you + | do not edit anything down there. + | + */ + + 'components' => array() + +); \ No newline at end of file From 90c82a3a7981ef0b34663869ac3638f46b9840dc Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:25:00 +0930 Subject: [PATCH 012/136] Added default applications to feather configuration. Signed-off-by: Jason Lewis --- config/feather.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/config/feather.php b/config/feather.php index 759709f..59c7dee 100644 --- a/config/feather.php +++ b/config/feather.php @@ -42,6 +42,21 @@ | */ - 'components' => array() + 'components' => array(), + + /* + |-------------------------------------------------------------------------- + | Feather Applications + |-------------------------------------------------------------------------- + | + | Applications to be registered at runtime with Feather. It is advised you + | do not edit anything down there. + | + */ + + 'applications' => array( + 'admin' => '(:feather)/admin', + 'core' => '(:feather)' + ), ); \ No newline at end of file From beef3130621ef704bfb8cad7ac24f13f25f2c741 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:25:59 +0930 Subject: [PATCH 013/136] Register and start applications. Signed-off-by: Jason Lewis --- start.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/start.php b/start.php index 6265853..e33634f 100644 --- a/start.php +++ b/start.php @@ -62,7 +62,7 @@ define('FEATHER_DATABASE', 'feather'); -$feather['config']->set('laravel: database.connections.' . FEATHER_DATABASE, $feather['config']->get('feather: database')); +$feather['config']->set('laravel: database.connections.' . FEATHER_DATABASE, $feather['config']->get('feather: feather.database')); $feather['config']->db(); @@ -89,4 +89,16 @@ | logic. Register each of the applications as defined in the configuration | with the Laravel bundle manager. | -*/ \ No newline at end of file +*/ + +foreach($feather['config']->get('feather: feather.applications') as $application => $handles) +{ + $handles = str_replace('(:feather)', Bundle::option('feather', 'handles'), $handles); + + Bundle::register("feather/{$application}", array( + 'handles' => $handles, + 'location' => "feather/applications/{$application}" + )); + + starts_with(Request::uri(), $handles) and Bundle::start("feather/{$application}"); +} \ No newline at end of file From 4d0b4fa3ec3e73daff67fd8ee2067f1c27c32456 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:30:04 +0930 Subject: [PATCH 014/136] Initial commit of basic theme. Signed-off-by: Jason Lewis --- themes/basic/public/css/theme.css | 1493 +++++++++++++++++ themes/basic/public/img/background.png | Bin 0 -> 23173 bytes themes/basic/public/img/heading.png | Bin 0 -> 1729 bytes themes/basic/public/img/loading.gif | Bin 0 -> 847 bytes themes/basic/public/img/sprites.png | Bin 0 -> 15604 bytes themes/basic/public/js/jquery-ui.js | 17 + themes/basic/public/js/jquery.js | 2 + themes/basic/public/js/leaner.js | 68 + themes/basic/public/js/theme.js | 170 ++ themes/basic/public/js/tooltip.js | 125 ++ themes/basic/start.php | 27 + .../basic/views/misc/authorization.blade.php | 7 + themes/basic/views/template.blade.php | 90 + 13 files changed, 1999 insertions(+) create mode 100644 themes/basic/public/css/theme.css create mode 100644 themes/basic/public/img/background.png create mode 100644 themes/basic/public/img/heading.png create mode 100644 themes/basic/public/img/loading.gif create mode 100644 themes/basic/public/img/sprites.png create mode 100644 themes/basic/public/js/jquery-ui.js create mode 100644 themes/basic/public/js/jquery.js create mode 100644 themes/basic/public/js/leaner.js create mode 100644 themes/basic/public/js/theme.js create mode 100644 themes/basic/public/js/tooltip.js create mode 100644 themes/basic/start.php create mode 100644 themes/basic/views/misc/authorization.blade.php create mode 100644 themes/basic/views/template.blade.php diff --git a/themes/basic/public/css/theme.css b/themes/basic/public/css/theme.css new file mode 100644 index 0000000..19c0ac2 --- /dev/null +++ b/themes/basic/public/css/theme.css @@ -0,0 +1,1493 @@ +body { + background-image: url('../img/background.png'); + padding: 0; + margin: 0; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + font-size: 13px; + color: #454545; + line-height: 1.45em; +} + +input, button, textarea { + color: #454545; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + font-size: 1em; +} + +a { + color: #d7634c; +} + +a:hover { + text-decoration: none; +} + +.group:after, +fieldset dd:after, +.alert-floated:after { + content: ""; + display: table; + clear: both; +} + +/** + * The container element holds all other elements and is centered on our screen. + */ +.container { + position: relative; + width: 1140px; + margin: 0 auto; + padding: 0; +} + +/** + * The header contains the logo and some vital navigation for getting around the forum. + * Our user related data will also appear here when they are signed in. + */ +.header > .logo { + clear: right; + float: left; +} + +/** + * The user section of the header is above everything and is easily to see, it's fixed. + */ +.header > .user { + float: right; + margin: 10px 0 30px; + font-size: 14px; +} + +.header > .user a { + text-decoration: none; +} + +.header > .user > ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.header > .user > ul > li { + float: left; + padding: 0 18px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background-color: #fff; + margin: 0 0 0 6px; + position: relative; + line-height: 34px; +} + +.header > .user > ul > li.light { background-color: #d1c9c1; } +.header > .user > ul > li.light:hover { background-color: #d9d2cb; } + +.header > .user > ul > li.dark { background: rgba(0, 0, 0, 0.6); } +.header > .user > ul > li.dark:hover { background: rgba(0, 0, 0, 0.7); } + +.header > .user > ul > li.attn { background: rgba(215, 99, 76, 0.8); } +.header > .user > ul > li.attn:hover { background: rgba(215, 99, 76, 0.95); } + +.header > .user > ul > li.link > a { + display: block; + margin: 0 -18px; + padding: 0 18px; +} + +.header > .user > ul > li.dark a, +.header > .user > ul > li.attn a { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); +} + +/** + * When a menu with options is hovered we show another dropdown menu. + */ +.header > .user > ul > li.has-options:hover { + -moz-border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; +} + +.header > .user > ul > li.has-options > ul { + display: none; + list-style-type: none; + margin: 0; + padding: 0; + position: absolute; + top: 40px; + left: 0; + line-height: normal; + width: 350px; + -moz-border-radius: 0 2px 2px 2px; + -webkit-border-radius: 0 2px 2px 2px; + border-radius: 0 2px 2px 2px; + overflow: hidden; +} + +.header > .user > ul > li.dark.has-options > ul { background: rgba(0, 0, 0, 0.7); } +.header > .user > ul > li.attn.has-options > ul { background: rgba(215, 99, 76, 0.95); } + +.header > .user > ul > li.has-options:hover > ul { + display: block; +} + +/** + * Place a smaller bar after the hovered option so we can shift the actual dropdown below + * other menu items. + */ +.header > .user > ul > li.has-options:hover:before { + content: ""; + display: block; + width: 100%; + height: 6px; + position: absolute; + top: 34px; + left: 0; +} + +.header > .user > ul > li.dark.has-options:hover:before { background: rgba(0, 0, 0, 0.7); } +.header > .user > ul > li.attn.has-options:hover:before { background: rgba(215, 99, 76, 0.95); } + +/** + * Items for the dropdown menu. + */ +.header > .user > ul > li.has-options > ul > li { + float: left; + width: 50%; + padding: 4px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.header > .user > ul > li.has-options > ul > li > a { + display: block; + padding: 6px 14px; + color: #fff; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.header > .user > ul > li.attn.has-options > ul > li > a:hover { + background-color: #d15843; +} + +/** + * The header navigation is floated right and consists of nice squares, love the squares. + */ +.header > .navigation { + float: right; +} + +.header > .navigation > li { + float: left; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + margin: 0 0 0 3px; +} + +.header > .navigation > li > a { + display: block; + color: #999; + text-shadow: 0 1px 0 #fff; + text-decoration: none; + font-weight: bold; + font-size: 14px; + text-transform: lowercase; + padding: 8px 16px; +} + +.header > .navigation > li:hover { + background: rgba(0, 0, 0, 0.1); +} + +.header > .navigation > li.important { + background: rgba(215, 99, 76, 0.6); +} + +.header > .navigation > li.important:hover { + background: rgba(215, 99, 76, 0.8); +} + +.header > .navigation > li.selected { + background: rgba(0, 0, 0, 0.25); +} + +.header > .navigation > li.selected > a, +.header > .navigation > li.important > a { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); +} + +/** + * The main body is a large container that stands out from the background. + */ +.body { + margin: 30px 0 0; + background-color: #fff; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.1); + padding: 10px 10px 20px; + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; +} + +.body .content { + clear: both; +} + +/** + * The breadcrumbs get nice little arrow separators. + */ +.breadcrumbs { + margin: -10px -10px 10px; + background-color: #f2f2f2; + border-bottom: 1px solid #e6e6e6; + text-shadow: 0 1px 0 #fff; + -moz-border-radius: 2px 2px 0 0; + -webkit-border-radius: 2px 2px 0 0; + border-radius: 2px 2px 0 0; + line-height: 40px; +} + +.breadcrumbs > li, +.breadcrumbs > li > a { + color: #666; +} + +.breadcrumbs > li { + display: inline-block; + margin: 0 0 0 -3px; + cursor: default; +} + +.breadcrumbs > li:first-child { margin-left: 0; } + +.breadcrumbs > li, +.breadcrumbs > li > a { + position: relative; + padding: 0 4px 0 26px; + color: #989898; +} + +.breadcrumbs > li > a { + display: block; + text-decoration: none; + margin: 0 -4px 0 -26px; +} + +.breadcrumbs > li:first-child, +.breadcrumbs > li:first-child > a { + position: relative; + padding: 0 4px 0 10px; + color: #989898; +} + +.breadcrumbs > li:first-child > a { + margin: 0 -4px 0 -10px; +} + +.breadcrumbs > li > a:hover, +.breadcrumbs > li > a:focus { + color: #555; +} + +.breadcrumbs > li:hover { + background-color: #f9f9f9; +} + +.breadcrumbs > li:active { + border-color: #f1f1f1; +} + +.breadcrumbs > li:before { + position: absolute; + display: inline-block; + content: ""; + top: -1px; + right: -20px; + border-top: 21px solid rgba(255, 255, 255, 0); + border-bottom: 21px solid rgba(255, 255, 255, 0); + border-left: 19px solid #e2e2e2; + z-index: 1; +} + +.breadcrumbs > li:hover:before, +.breadcrumbs > li:focus:before { + border-left-color: #d7d7d7; +} + +.breadcrumbs > li:after { + position: absolute; + display: inline-block; + content: ""; + top: -1px; + right: -18px; + border-top: 21px solid rgba(255, 255, 255, 0); + border-bottom: 21px solid rgba(255, 255, 255, 0); + border-left: 19px solid #f2f2f2; + z-index: 1; +} + +.breadcrumbs > li:hover:after { + border-left-color: #f9f9f9; +} + +.breadcrumbs > li:hover { text-decoration: none; } + +/** + * Headings appear throughout the body and are a different font and weight + * then other items. + */ +.body h1, +.body h2, +.body h3 { + margin: 0 0 16px; + font-family: "Trebuchet MS", "Ubuntu", serif; + font-size: 24px; + color: #999; + text-transform: uppercase; +} + +.body h2 { font-size: 20px; } +.body h3 { font-size: 18px; } + +.body h1 a, +.body h2 a { + text-decoration: none; +} + +.body p:first-child { margin-top: 0; } +.body p:last-child { margin-bottom: 0; } + +/** + * Body halves allow the page to be split in two equal halves. + */ +.halves > .half-left { + float: left; + width: 49%; +} + +.halves > .half-right { + margin: 0 0 0 51%; +} + +/** + * The footer simply appears under the body and nothing more. + */ +.footer { + font-size: 11px; + padding: 10px 0; + color: #999; +} + +.footer > .powered { + float: left; + margin: 0; +} + +.footer > .stats { + float: right; +} + +.footer > .stats > li { + float: left; + margin: 0 0 0 24px; +} + +.footer > .stats .unit { + display: inline-block; + background-color: #999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); + padding: 1px 4px; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + font-weight: bold; + margin: 0 4px 0 0; +} + +/** + * A loading div that can be placed just about anywhere. + */ +.loading { + background: url('../img/loading.gif') no-repeat; + width: 16px; + height: 16px; + display: inline-block; +} + +/** + * The place description style. Shown when viewing a single place. + */ +.place-description { + margin: 0 0 16px; + padding: 4px 10px; + background-color: #f1f1f1; + border: 1px solid #eaeaea; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + display: inline-block; + color: #888; +} + +/** + * Styles for showing all discussions within places. This is used on the main page and when viewing + * a single place. + */ +.discussions > li { + display: block; + padding: 16px 0; + border-top: 1px solid #EAEAEA; + letter-spacing: -4px; + word-spacing: -4px; +} + +.discussions > li.place { + border-top: 0; +} + +.discussions > li.place + li { + margin-top: 0; + border-top: 1px solid #f4f4f4; +} + +.discussions > li.discussion > div { + display: inline-block; + line-height: 1.7em; + word-spacing: normal; + vertical-align: middle; + letter-spacing: normal; + *display: inline; + zoom: 1; +} + +.discussions > li.place, +.discussions > li.empty, +.discussions > li.extra { + position: relative; + letter-spacing: normal; + word-spacing: normal; +} + +.discussions > li.place, +.discussions > li.extra, +.discussions > li.empty { + border-top: 0; +} + +.discussions > li.place, +.discussions > li.extra, +.discussions > li.empty { + padding: 0; +} + +.discussions > li.place + li.empty { + border-top: 0; + margin: 0 0 30px; +} + +.discussions > li h1, +.discussions > li h2, +.discussions > li h3 { + margin: 0 0 16px; +} + +.place h1 a { color: #494949; } + +/** + * The see more discussions link for each place. + */ +.discussions > li.more { + text-align: right; + padding: 16px 0 30px; + border: 0; + letter-spacing: normal; + word-spacing: normal; + width: 21%; +} + +.discussions > li.more span { + position: relative; + font-size: 14px; + top: -1px; +} + +/** + * A total discussions nested within place counter is shown next to each place title. + */ +.discussion-counter { + position: relative; + top: -1px; + cursor: default; + font-size: 10px; + display: inline-block; + margin: 0 2px 0 18px; + background-color: #777; + color: #fff; + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + border-radius: 12px; + padding: 0 5px; + line-height: 16px; + vertical-align: middle; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); +} + +h1 > .discussion-counter { + top: -2px; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + margin-left: 9px; +} + +/** + * We show the places children to a specified depth only in a nice dropdown, similar to the user bar. + */ +.children { + margin: 0 0 16px; +} + +.children li { + position: relative; +} + +.children > li { + display: inline-block; + font-size: 13px; + color: #777; + margin: 0 6px 0 0; + padding: 0 8px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background-color: #F1F1F1; + line-height: 25px; +} + +.children > li a { + color: #777; + text-decoration: none; +} + +.children > li a:hover { color: #444; } + +.children > li.parent-child ul li .discussion-counter { + position: absolute; + right: 0; + top: 5px; +} + +.children li:hover .discussion-counter { + background-color: #fff; + color: #d7634c; +} + +.children > li:hover { + background: rgba(215, 99, 76, 0.95); +} + +.children > li.parent-child:hover { + -moz-border-radius: 2px 2px 0 0; + -webkit-border-radius: 2px 2px 0 0; + border-radius: 2px 2px 0 0; +} + +/** + * This is a spacer that we put between the place and the dropdown itself. So we + * can space the dropdown a bit. + */ +.children > li.parent-child:hover:before { + content: ""; + display: block; + width: 100%; + height: 4px; + background: rgba(215, 99, 76, 0.95); + position: absolute; + top: 25px; + left: 0; +} + +.children > li:hover > a:hover { text-decoration: none; } + +/** + * Those that have a dropdown arrow show it first as gray then as white. + */ +.children > li > .dropdown-arrow { + display: inline-block; + width: 7px; + height: 4px; + background: url('../img/sprites.png') no-repeat 0 -48px; + vertical-align: middle; + margin: -1px 0 0 1px; +} + +.children > li.parent-child:hover > .dropdown-arrow { background-position: -7px -48px; } + +/** + * When a child place is hovered show the dropdown. + */ +.children > li.parent-child:hover > ul { + display: block; +} + +.children > li:hover li, +.children > li:hover a { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); +} + +.children > li > ul a { + display: inline-block; + padding: 0 8px; + margin: 0 70px 0 0; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} + +.children > li > ul a:hover { + background-color: #d15843; + text-decoration: none; +} + +.children > li > ul { + display: none; + list-style-type: none; + position: absolute; + z-index: 2; + top: 29px; + left: 0; + margin: 0; + padding: 0; + background: rgba(215, 99, 76, 0.95); + min-width: 200px; + max-width: 500px; + white-space: nowrap; + -moz-border-radius: 0 2px 2px 2px; + -webkit-border-radius: 0 2px 2px 2px; + border-radius: 0 2px 2px 2px; + padding: 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); +} + +.children > li > ul li { + line-height: 24px; +} + +.children > li > ul li ul { + list-style-type: none; + margin: 0; + padding: 0 0 0 20px; +} + +/** + * Showing a discussions information within a place listing. + */ +.discussions > li.discussion > .place { + width: 21%; + text-align: right; +} + +.discussions > li.discussion > .meta { + margin: 0 0 0 2%; + position: relative; + width: 55%; +} + +.discussions > li.discussion > .meta > .title { + font-size: 16px; +} + +.discussions > li.discussion > .meta > .title:after { + content: ""; + display: block; +} + +.discussions > li.discussion > .meta > p { + color: #777; + margin: 0 12px 0 0; +} + +.discussions > li.discussion > .meta > p a { + color: #444; + text-decoration: none; +} + +.discussions > li.discussion > .meta > span + a { margin: 0 0 0 4px; } + +/** + * When a user is signed in the discussions options appear when they hover + * over a discussion. + */ +.discussions > li.discussion > .meta .options { + position: absolute; + top: 50%; + right: 25px; + margin: -8px 0 0 18px; +} + +.discussions > li.discussion > .meta .options a { + display: none; + background: url('../img/sprites.png') no-repeat -16px -48px; + width: 16px; + height: 16px; + margin: 0 8px 0 0; + vertical-align: text-top; +} + +.discussions > li.discussion > .meta .options a.watch:hover { background-position: -16px -64px; } + +.discussions > li.discussion > .meta .options a.watch.watching { + display: inline-block; + background-position: -16px -80px; +} + +.discussions > li.discussion > .meta .options a.tools { background-position: -32px -48px; } +.discussions > li.discussion > .meta .options a.tools:hover { background-position: -32px -64px; } + +.discussions > li.discussion:hover > .meta .options a { display: inline-block; } + +/** + * The statistics consists of 3 boxes of equal width with a light gray coloring. + */ +.discussions > li.discussion > .stats { + text-align: right; + width: 22%; + word-spacing: -4px; + letter-spacing: -4px; +} + +.discussions > li.discussion > .stats > div { + width: 33%; + background-color: #f6f6f6; + display: inline-block; + padding: 2px 0 5px; + word-spacing: normal; + letter-spacing: normal; +} + +.discussions > li.discussion > .stats > div > span { + display: block; + text-align: center; + font-size: 10px; + text-transform: uppercase; + color: #777; +} + +.discussions > li.discussion > .stats > div > span.number { + font-size: 22px; +} + +/** + * When selecting users who can participate in a discussion. + */ +.discussion-participants input { + min-width: 40%; +} + +.participants { + min-height: 35px; + margin: 8px 0 0; +} + +.participants > img { + width: 30px; + height: 30px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + margin: 0 3px 0 0; + cursor: pointer; +} + +.participants > span { + margin: 0 0 14px; +} + +/** + * Start discussion textbox. + */ +.discussion-textarea { + margin: 24px 0 0; +} + +.discussion-textarea textarea { + width: 100%; + height: 400px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +/** + * Forms and form related stylings. + */ +fieldset { + border: 0; + padding: 0; + margin: 30px 0 0; +} + +fieldset:first-child { + margin: 0; +} + +fieldset dl { + margin: 0; + line-height: 1.4em; +} + +fieldset dl dt { + float: left; + width: 30%; + text-align: right; + margin: 5px 0 12px 0; +} + +fieldset dl dt label { + font-weight: bold; + font-size: 14px; +} + +fieldset dl dd { + margin: 0 0 12px 33%; +} + +fieldset dl dd:last-child { + margin-bottom: 0; +} + +fieldset dl dd .description { + display: block; + margin: 6px 0 0; + color: #999; + font-size: 12px; + line-height: 1.5em; +} + +input, +textarea, +select { + background-color: #fff; + border: 1px solid #dcdcdc; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + padding: 6px 8px; + -webkit-transition: box-shadow 0.1s ease-in-out 0s; + -moz-transition: box-shadow 0.1s ease-in-out 0s; + -o-transition: box-shadow 0.1s ease-in-out 0s; + transition: box-shadow 0.1s ease-in-out 0s; + min-width: 60%; + resize: none; + font-family: "Helvetica", "Helvetica Neue", Arial, sans-serif; + font-size: 100%; +} + +select, +input[type=radio], +input[type=checkbox] { + min-width: 0; +} + +input[type=radio], +input[type=checkbox] { + vertical-align: middle; + margin: -3px 0 0; +} + +input:focus, +textarea:focus, +select:focus { + box-shadow: 0 0 4px #d7d7d7; +} + +input::-webkit-input-placeholder { + color: #bbb !important; +} + +input:-moz-placeholder { + color: #bbb !important; +} + +input[type="submit"] { + min-width: inherit; +} + +button::-moz-focus-inner, +input[type="button"]::-moz-focus-inner, +input[type="submit"]::-moz-focus-inner, +input[type="reset"]::-moz-focus-inner { + padding: 0 !important; + border: 0 none !important; +} + +.error input, +.error textarea { + border-color: #fab1b1; + box-shadow: 0 0 4px #fab1b1; +} + +.label.error-inline { + margin: 6px 0 0; +} + +.error-spacer { + display: block; +} + +select optgroup { + text-align: center; +} + +fieldset .decider { + overflow: hidden; +} + +fieldset .decider span { + float: left; + padding: 5px 12px 4px; + border-width: 1px; + border-style: solid; + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + text-shadow: 0 1px 0 #fff; +} + +fieldset .decider input { + margin-right: 4px; +} + +fieldset .decider .yes { + background-color: #DFF0D8; + border-color: #bed9a8; + color: #468847; + border-right: 0; + -moz-border-radius: 2px 0 0 2px; + -webkit-border-radius: 2px 0 0 2px; + border-radius: 2px 0 0 2px; +} + +fieldset .decider .no { + background-color: #FCE9E9; + border-color: #fab1b1; + color: #B94A48; + border-left: 0; + -moz-border-radius: 0 2px 2px 0; + -webkit-border-radius: 0 2px 2px 0; + border-radius: 0 2px 2px 0; +} + +/** + * User Authorization styles, for Twitter, Facebook, Google, and OpenID. + */ +.authorization-links > a { + background: url('../img/sprites.png') no-repeat 0 0; + width: 32px; + height: 32px; + display: inline-block; + margin: 0 6px 0 0; +} + +.authorization-links > a.google { background-position: -32px 0; } +.authorization-links > a.facebook { background-position: -64px 0; } +.authorization-links > a.twitter { background-position: -96px 0; } + +/** + * Buttons are styled by applying the btn and a btn type to an element. + */ +.btn { + display: inline-block; + line-height: normal; + padding: 12px 14px 13px; + background: #fafafa; + background: -moz-linear-gradient(top, #fafafa 0%, #ececec 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fafafa), color-stop(100%,#ececec)); + background: -webkit-linear-gradient(top, #fafafa 0%,#ececec 100%); + background: -o-linear-gradient(top, #fafafa 0%,#ececec 100%); + background: -ms-linear-gradient(top, #fafafa 0%,#ececec 100%); + background: linear-gradient(top, #fafafa 0%,#ececec 100%); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + border: 1px solid #dadada; + border-bottom-color: #c5c5c5; + box-shadow: inset 0 1px 0 #fff; + text-shadow: 0 1px 0 #fff; + color: #666; + text-decoration: none; + cursor: pointer; +} + +.btn:active { + box-shadow: inset 0 4px 10px rgba(0, 0, 0, 0.1) !important; +} + +.btn:hover { + background: #f1f1f1; +} + +.btn.btn-big { + font-size: 14px; + font-weight: bold; +} + +.btn.btn-small { + font-size: 11px; +} + +.btn.btn-fat { + padding: 12px 18px 13px; +} + +/** + * Primary Button + */ +.btn.btn-primary { + color: #fff !important; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #d8724b; + background: -moz-linear-gradient(top, #d8724b 0%, #d7614d 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d8724b), color-stop(100%,#d7614d)); + background: -webkit-linear-gradient(top, #d8724b 0%,#d7614d 100%); + background: -o-linear-gradient(top, #d8724b 0%,#d7614d 100%); + background: -ms-linear-gradient(top, #d8724b 0%,#d7614d 100%); + background: linear-gradient(top, #d8724b 0%,#d7614d 100%); + border-color: #cf5a47; + border-bottom-color: #c3432d; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-primary:hover { + background: #d56e47; +} + +/** + * Info Button + */ +.btn.btn-info { + color: #fff !important; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #65afe3; + background: -moz-linear-gradient(top, #65afe3 0%, #469ad5 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#65afe3), color-stop(100%,#469ad5)); + background: -webkit-linear-gradient(top, #65afe3 0%,#469ad5 100%); + background: -o-linear-gradient(top, #65afe3 0%,#469ad5 100%); + background: -ms-linear-gradient(top, #65afe3 0%,#469ad5 100%); + background: linear-gradient(top, #65afe3 0%,#469ad5 100%); + border-color: #3e92cd; + border-bottom-color: #1a72b1; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-info:hover { + background: #56a2d7; +} + +/** + * Success Button + */ +.btn.btn-success { + color: #fff !important; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #80b65d; + background: -moz-linear-gradient(top, #80b65d 0%, #6ca843 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#80b65d), color-stop(100%,#6ca843)); + background: -webkit-linear-gradient(top, #80b65d 0%,#6ca843 100%); + background: -o-linear-gradient(top, #80b65d 0%,#6ca843 100%); + background: -ms-linear-gradient(top, #80b65d 0%,#6ca843 100%); + background: linear-gradient(top, #80b65d 0%,#6ca843 100%); + border-color: #639f3b; + border-bottom-color: #569829; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-success:hover { + background: #7ab254; +} + +/** + * Warning Button + */ +.btn.btn-warning { + color: #fff !important; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #dda24f; + background: -moz-linear-gradient(top, #dda24f 0%, #ce8927 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dda24f), color-stop(100%,#ce8927)); + background: -webkit-linear-gradient(top, #dda24f 0%,#ce8927 100%); + background: -o-linear-gradient(top, #dda24f 0%,#ce8927 100%); + background: -ms-linear-gradient(top, #dda24f 0%,#ce8927 100%); + background: linear-gradient(top, #dda24f 0%,#ce8927 100%); + border-color: #c7811d; + border-bottom-color: #bc730a; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-warning:hover { + background: #d89b44; +} + +/** + * Danger Button + */ +.btn.btn-danger { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #cc493f; + background: -moz-linear-gradient(top, #cc493f 0%, #c43025 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#cc493f), color-stop(100%,#c43025)); + background: -webkit-linear-gradient(top, #cc493f 0%,#c43025 100%); + background: -o-linear-gradient(top, #cc493f 0%,#c43025 100%); + background: -ms-linear-gradient(top, #cc493f 0%,#c43025 100%); + background: linear-gradient(top, #cc493f 0%,#c43025 100%); + border-color: #c2291e; + border-bottom-color: #bc1509; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-danger:hover { + background: #c84338; +} + +/** + * Inverse Button + */ +.btn.btn-inverse { + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); + background: #595959; + background: -moz-linear-gradient(top, #595959 0%, #434343 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#595959), color-stop(100%,#434343)); + background: -webkit-linear-gradient(top, #595959 0%,#434343 100%); + background: -o-linear-gradient(top, #595959 0%,#434343 100%); + background: -ms-linear-gradient(top, #595959 0%,#434343 100%); + background: linear-gradient(top, #595959 0%,#434343 100%); + border-color: #3e3e3e; + border-bottom-color: #313131; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2); +} + +.btn.btn-inverse:hover { + background: #515151; +} + +/** + * Soft Button + * This button is slightly different, it's smaller and barely got a background gradient. + */ +.btn-soft { + padding: 6px 10px; + color: #999; + text-shadow: none; + background: #fff; + border-color: #ededed; + border-bottom-color: #d9d9d9; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.07); +} + +.btn.btn-soft:hover { + background: #fff; + border-color: #e0e0e0; + border-bottom-color: #cfcfcf; + color: #666; +} + +.btn.btn-soft:active { + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.07) !important; + border-color: #f3f3f3; + border-bottom-color: #e1e1e1; +} + +/** + * Inline labels, different colors for different labels. + */ +.label { + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + padding: 2px 8px 3px; + background-color: #bbb; + color: #FFFFFF; + font-size: 11px; + line-height: 14px; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); + vertical-align: text-top; + position: relative; + top: -1px; + white-space: nowrap; + text-transform: uppercase; + display: inline-block; +} + +.label.label-success { + background-color: #1f9a31; +} + +.label.label-important { + background-color: #dd5956; +} + +.label.label-info { + background-color: #2895ca; +} + +.label.label-bright { + background-color: #e58749; +} + +.label.label-private { + background-color: #dd5956; +} + +.label.label-draft { + background-color: #4e4744; +} + +.label.label-inverse { + background-color: #4e4744; +} + +/** + * Alert boxes, different colors for different types of alerts. + */ +.alert { + background-color: #FCF8E3; + border: 1px solid #FBEED5; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + color: #C09853; + margin-bottom: 16px; + padding: 12px 16px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); +} + +.alert.alert-floated { + float: left; +} + +.alert:last-child { margin: 0; } + +.alert.alert-light { + background-color: #e7e7e7; + border-color: #d8d8d8; + color: #5b5b5b; +} + +.alert.alert-error { + background-color: #FCE9E9; + border-color: #fab1b1; + color: #B94A48; +} + +.alert.alert-success { + background-color: #DFF0D8; + border-color: #D6E9C6; + color: #468847; +} + +.alert.alert-info { + background-color: #D9EDF7; + border-color: #BCE8F1; + color: #3A87AD; +} + +.alert ol { + list-style-type: decimal; + margin: 8px 0 0 0; + padding: 0 0 0 40px; +} + +/** + * By default lists have no style type or margins/paddings. + */ +ul, ol { + list-style-type: none; + margin: 0; + padding: 0; +} + +/** + * Some text alignments. + */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +/** + * Tooltip Styles + */ +.tooltip-container { + display: none; + position: absolute; + top: 0; + left: 0; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + line-height: normal !important; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + background-color: #222; + background: rgba(0, 0, 0, 0.85); + z-index: 99; +} + +.tooltip-container .tooltip-wrapper { + position: relative; +} + +.tooltip-container .tooltip-arrow { + position: absolute; + border-width: 5px; + border-style: solid; +} + +.tooltip-container.arrow-b .tooltip-arrow { + top: -10px; + left: 50%; + margin-left: -5px; + border-color: transparent transparent #222 transparent; + border-color: transparent transparent rgba(0, 0, 0, 0.85) transparent; +} + +.tooltip-container.arrow-t .tooltip-arrow { + bottom: -10px; + left: 50%; + margin-left: -5px; + border-color: #222 transparent transparent transparent; + border-color: rgba(0, 0, 0, 0.85) transparent transparent transparent; +} + +.tooltip-container.arrow-l .tooltip-arrow { + top: 50%; + right: -10px; + margin-top: -5px; + border-color: transparent transparent transparent #222; + border-color: transparent transparent transparent rgba(0, 0, 0, 0.85); +} + +.tooltip-container.arrow-r .tooltip-arrow { + top: 50%; + left: -10px; + margin-top: -5px; + border-color: transparent #222 transparent transparent; + border-color: transparent rgba(0, 0, 0, 0.85) transparent transparent; +} + +.tooltip-container .tooltip-content { + color: #fff; + text-shadow: 0 1px 0 #000; + padding: 8px 16px; + font-size: 12px; +} + +/** + * The box modal is a nice popup box that appears over the page. + */ +#lean-overlay { + position: fixed; + z-index:100; + top: 0px; + left: 0px; + height:100%; + width:100%; + background: #000; + display: none; +} + +#box-modal { + display: none; + max-width: 700px; + max-height: 500px; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + background-color: #fff; + box-shadow: 0 0 6px #222; +} + +#box-modal #box-content { + padding: 25px; +} + +#box-modal #box-content h2 { + margin: -25px -25px 25px; + padding: 25px 25px 28px; + -moz-border-radius: 6px 6px 0 0; + -webkit-border-radius: 6px 6px 0 0; + border-radius: 6px 6px 0 0; + background-color: #f9f9f9; + border-bottom: 1px solid #f1f1f1; + color: #666; + font-weight: normal; +} + +/** + * The autocomplete container is positioned after an input box. + */ +.autocomplete { + position: relative; +} + +.autocomplete .ui-autocomplete { + display: none; + position: absolute; + top: 100%; + left: 0; + width: 300px; + list-style-type: none; + border: 1px solid #dcdcdc; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + background-color: #fff; + color: #fff; + text-shadow: 0 1px 0 #fff; + padding: 0; + margin: 14px 0 0; +} + +.autocomplete .ui-autocomplete:after { + display: block; + position: absolute; + content: ""; + top: -8px; + left: 25px; + border-left: 8px solid rgba(255, 255, 255, 0); + border-right: 8px solid rgba(255, 255, 255, 0); + border-bottom: 8px solid #fff; +} + +.autocomplete .ui-autocomplete:before { + display: block; + position: absolute; + content: ""; + top: -11px; + left: 23px; + border-left: 10px solid rgba(255, 255, 255, 0); + border-right: 10px solid rgba(255, 255, 255, 0); + border-bottom: 10px solid #dcdcdc; +} + +.autocomplete .ui-autocomplete > li { margin: 0 0 6px; } + +.autocomplete .ui-autocomplete > li > a { + display: block; + padding: 6px; +} + +.autocomplete .ui-autocomplete > li:last-child { margin-bottom: 0; } + +.autocomplete .ui-autocomplete > li > a:hover, +.autocomplete .ui-autocomplete > li > a.ui-state-hover { background-color: #e3e3e3; } \ No newline at end of file diff --git a/themes/basic/public/img/background.png b/themes/basic/public/img/background.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3aca46b98cba878c55e5c8e451399093d70560 GIT binary patch literal 23173 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

1h2nDLqe>m=rswbkN*Aj# z&B7V|FEenp+4Q}g5l=HoF7nFV(nV-5igK-{ZSu$kHA}l&m8c}1Dbcnf9-qiElth8c zK&&TuBm~D|iN_=KXtnCAN~jU%eLUR1WMym#kHJ2v&kW``FIY6+l{(Rs2U3~)MM?F! z!RYY-#qt~%tmsoy^G##QG*>AMrS0Tr^kjwWN~;HHw#-P zn?65GSBpVZgh9!i6gOD{ey|(F3|`q2R!_65c=OBLC9cEri@M8Mhk1&k;`yySnksb4 zaTbgwc~IQ4gs9g(4ka9HAd~J$V^}sv)c-PHW~-{gfNe37e!cR$HNzTpOT<_@j6{hm z06}^?%QbmfEN+Xx>QGg?z_aDEhkK>EbDB9JlEamDc3zKQErezzGDznWB?)QeR_Ndg zp$Sgx-&w%w+L19reCwEcUW`yK4ZxuqC2K)!1gF9xbry=L#>MyZ)0($Uv&6J0f2Rb6 zlToNj_XU1}%lDO3P1kr)&D`roY+p66itRlTUU=A%W3i~butu+2;lqay9kZ@Q-%*;f zH?qfUie#a>&#jNG(3;T*I$24Y=-Y)7h$T) z98n=<9PU|7apKT#QL=U`h#0Av4pg~e5mW8w@->PpX(R#(Rvul7y=o`n7MHj(-ySDR zrs%d9iOjT}(_{rwDv@}BMt`x!&(3d=&m?Hen3yw|gj7YaBYi$10&QIQMBXJ`L<4hP zdUjP}h1CbAF1N-gE;Ux&YEsx*wHa1SH3I3{uPpPRaSuhS$!pCucL3t1u1!B-Rta8SC+G~$9;}ut9Hg+Qnzh|;!?kEEXfHA zGC8}dK`!lzK6;x7Sgzh!^-6bHW+@>=8un|fN>-1!JrV@u<}Qil)2B}w93(r+dnEGI z!yN#YgBsZYEVXlHqk`0__s1)d{$~%i(i#%jDT1ov3M*qInQ456!2RG`gk;6jd_hTV34&8uck(+Gxf z6|vm1_UKkbpOl=R3S}!@tQHcLYY2-l*x!kSa_*w#s9vH^6K$~nY%j)ERvwEio@+lb zyQ&i`%D!MxR+?iB$tz*XHaF21`;F|)CI4hcS{AybT$py1+9GIZxwjUYj|$!~_1vl5 zKt)P>!Cli#h@k9%bj2zvq@H?_!1_cf^h$al_jEr>`=D6NwrvrqR`4e#Cg+E#5vv8w z949d!pARt>iQUZpSU05Dgt z6%qO(;R)8%g4V=tt>k3qpXZoH>x<)MI=YCdd!bh%)Vy)!L(5-MbYCK#C^Qs);jCJ< z>N8K2I;|Q!dq*T06mW?Y+gSu+>~ESW`lm9C1;VOHiwK)yRmE|vvS~!Nq_CurA&J}7 zQ(WPAeuao{`h598HQHxc&yuBuMvCVRY}3G~z=e&h>eX&=trpf*j8zM0cUZBKJxFR> zy$%i%f-QagD;~zYO@!%wSD4oAA$#2W*;rlr&b+%a58s=ktc->MzI2P}G}$wxa9)F6 zd3?!@mfw_$pIR$dQ(rlkvc-Ht2%my6I~}Qbm6%d$$WiBoFhBKLvRjUXB~6iek1sDJ z0p*V>W3T+scDho=cgNc{6|E-a)7;b&q`KTymOeAPV!(S1hitr!?74KcI9pcfc{;X9GzrFa1afEQC0a2diozvs5bLsXR$pc%khX9A8mnqfsYcLpjMi2g6su}vlJd1)0Bb&3tNc?k$t-58_MmBrYQvY@ z>#_wI2bU4QC5Ih?Dq1WR^E9SVT55zpb_mk-#mSDJcI%sOzNylXraA7Xh{-9k#CsEd zTMtoK!x=sr;miO_rMVbvMslu@sR1r{U{Xd_4?AFW6OZvRy0T}~OseyZ9 zM{o(i$k6x?#zi#hY6Q>TdNt=~nG#p+oNe-S zIoXk(o^>=uxQtcB1XsqZJdsO$jaB7YB(E)bCqj!7%%qHm3d|BsNQjz7MNN`w`L<iI;FLh@PH&F1Z@)~>1duuE$i21E9b_(-t^rAD*-}(3|X#IZz3;_4qNOJ#D RU5o$#002ovPDHLkV1litTz&um literal 0 HcmV?d00001 diff --git a/themes/basic/public/img/heading.png b/themes/basic/public/img/heading.png new file mode 100644 index 0000000000000000000000000000000000000000..256be5a2f0782d2a9007e803957e149e7efe6e57 GIT binary patch literal 1729 zcmeAS@N?(olHy`uVBq!ia0vp^eL$?r!3HF!s9jVAQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?>3@Ln2Bde0{8v^Ko2Tt~skz|cV7z)0WFNY~KZ%Gk)tz(4^Clz_GsrKDK}xwt{?0`hE?GD=Dctn~HE z%ggo3jrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<-In3$Ab zT4JjNbScCOxdm`z^NOLt1Pn0!io^naLp=kKmtYEgeeo;J&4sHjE(uCSxEHIz#UYgi zsro^w#rdU0$-sz9QwCX8VC7ttnpl!w6q28x0}I7~jQo=P;*9(P1?ON>h49S0l>G8y zuxejlE6=>*lEl2^R8JRMC7^!2%*+%kb2BqHV{-!+HzxyELqk_XM_=yk=Z7nBfk3xGDeq!wkCrKY$Q<>xAZJ#3YU+bvEw&4cPq z!R;1joO<j&mQA-`Zs}ge{hTB1;<{TikGN+{`>1E-baS?=rIRJo z)R>FAI`>Vua9rWf{hqWO#!Ww}_WVCmzGHXQR<1h}Fx zx7{x8f1-P-$J8ap_qf&(8L|1>r@l%!(RtazPs`>1AnFRJps!?{W*TAImHZ73MCna&OD;O7`k-Nq)!itV{TeXQ9C*$wra( zpsNjClg~6-Ok6sNr$#uf4@0sa81FhOF>=-O2TcRqDp0+kfRBnbgflj-9ni#d1N3>yd+@%WP{Z`gHVj*&*w!bLo16W{zRb~Zg?r!Y_(ywo?EIh4zN@;rP0m%lP)vTlD`8@uQNHE0*_E^>qbnE-;OgOf$lQ|gnw zWt!5vF7LZ*>iEuM&bBhvt81SqM;2mAN$4-E~SHEULX zfB);(uQzYreD&(p%F4>-=4J*`fZ~5{Ki808XU70nBRvCVMxb8Be^Smxsfi`2DGKG8 zB^e5dS&0=n`H3ldnR#jX42nNlIJtnTbU?O%9Lc~MprFu~k~wdwfk=*`R!hRFwKGJH zO_VZi_`r6vqanJN#fB-fHDIDZD`RKtqP{hnIt`vtKNhJZec0tOX@W(|3R5-NicZsP z1sh?JPnEJq-}gIVvm`@7q09YJPllG%(FZG5IH>Uf%AY4_+!FE7;yMx_!l}EY-SE9}BbSx)X+!TV z9xY7|**x@SsNCFG$-p77@8TKO8(G|Xjo~g1claV178!VOinZz(^f_JO>dO}JdiItl z)ZVn5nWaW0n>R+2pQjtEr9qHjZBUrPWGHgfVMVjE9NQZ4W7*wJa~Kt!o+Q{aG;U|w zH$mC8$)d_BFrp!OckZGxZXuXN$NjhA8stpCkV#!C8H>m&MeWS!~VrCworWK*zpwZK-g4NP^sHGl2k8vz> zFkv{f>6oLE=R}STvlb?B*yXjddK##7Nc75in{VRv&|yE=saw*l)zs`NB_goouN2N#u}s9-J_<+U=2#1U;qFUX$cPi literal 0 HcmV?d00001 diff --git a/themes/basic/public/img/sprites.png b/themes/basic/public/img/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f56d75f3530b5e71ad9597b4b06029ed314b1a GIT binary patch literal 15604 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 Y%)jk5o%|yG_djG=DJ97&ag(6`1&NADj{pDw literal 0 HcmV?d00001 diff --git a/themes/basic/public/js/jquery-ui.js b/themes/basic/public/js/jquery-ui.js new file mode 100644 index 0000000..614e198 --- /dev/null +++ b/themes/basic/public/js/jquery-ui.js @@ -0,0 +1,17 @@ +/*! jQuery UI - v1.8.23 - 2012-08-15 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.core.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui,{version:"1.8.23",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;return a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0),/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a("").outerWidth(1).jquery||a.each(["Width","Height"],function(c,d){function h(b,c,d,f){return a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)}),c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){return c===b?g["inner"+d].call(this):this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){return typeof b!="number"?g["outer"+d].call(this,b):this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:a.expr.createPseudo?a.expr.createPseudo(function(b){return function(c){return!!a.data(c,b)}}):function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));c.offsetHeight,a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.curCSS||(a.curCSS=a.css),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!d||!a.element[0].parentNode)return;for(var e=0;e0?!0:(b[d]=1,e=b[d]>0,b[d]=0,e)},isOverAxis:function(a,b,c){return a>b&&a0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]===e)return;var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0},top:function(b,c){if(c.at[1]===e)return;var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];return!c||!c.ownerDocument?null:b?a.isFunction(b)?this.each(function(c){a(this).offset(b.call(this,c,a(this).offset()))}):this.each(function(){a.offset.setOffset(this,b)}):h.call(this)}),a.curCSS||(a.curCSS=a.css),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&a.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()})(jQuery);;/*! jQuery UI - v1.8.23 - 2012-08-15 +* https://github.com/jquery/jquery-ui +* Includes: jquery.ui.autocomplete.js +* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */ +(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.isMultiLine=this.element.is("textarea"),this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(b.options.disabled||b.element.propAttr("readOnly"))return;d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._keyEvent("previous",c);break;case e.DOWN:b._keyEvent("next",c);break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){if(b.options.disabled)return;b.selectedItem=null,b.previous=b.element.val()}).bind("blur.autocomplete",function(a){if(b.options.disabled)return;clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150)}),this._initSource(),this.menu=a("

    ").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,c,d;a.isArray(this.options.source)?(c=this.options.source,this.source=function(b,d){d(a.ui.autocomplete.filter(c,b.term))}):typeof this.options.source=="string"?(d=this.options.source,this.source=function(c,e){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:d,data:c,dataType:"json",success:function(a,b){e(a)},error:function(){e([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length").data("item.autocomplete",c).append(a("").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible")){this.search(null,b);return}if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)},widget:function(){return this.menu.element},_keyEvent:function(a,b){if(!this.isMultiLine||this.menu.element.is(":visible"))this._move(a,b),b.preventDefault()}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})})(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){if(!a(c.target).closest(".ui-menu-item a").length)return;c.preventDefault(),b.select(c)}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){if(!this.active)return;this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active){this.activate(c,this.element.children(b));return}var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:first")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bR[a]=c,c}function ch(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||cd.test(a)?d(a,e):ch(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ch(a+"["+e+"]",b[e],c,d);else d(a,b)}function cy(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.0",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,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(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return typeof a=="object"?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
    a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length||!d)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
    t
    ",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
    ",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/^(?:\{.*\}|\[.*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||++p.uuid:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.shift(),e=p._queueHooks(a,b),f=function(){p.dequeue(a,b)};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),delete e.stop,d.call(a,f,e)),!c.length&&e&&e.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c-1)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c-1)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,""+d),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;jq&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bd(a,b,c,d){var e=0,f=b.length;for(;e0?h(g,c,f):[]}function bf(a,c,d,e,f){var g,h,i,j,k,l,m,n,p=0,q=f.length,s=L.POS,t=new RegExp("^"+s.source+"(?!"+r+")","i"),u=function(){var a=1,c=arguments.length-2;for(;ai){m=a.slice(i,g.index),i=n,l=[c],B.test(m)&&(k&&(l=k),k=e);if(h=H.test(m))m=m.slice(0,-5).replace(B,"$&*");g.length>1&&g[0].replace(t,u),k=be(m,g[1],g[2],l,k,h)}}k?(j=j.concat(k),(m=a.slice(i))&&m!==")"?B.test(m)?bd(m,j,d,e):Z(m,c,d,e?e.concat(k):k):o.apply(d,j)):Z(a,c,d,e)}return q===1?d:Z.uniqueSort(d)}function bg(a,b,c){var d,e,f,g=[],i=0,j=D.exec(a),k=!j.pop()&&!j.pop(),l=k&&a.match(C)||[""],m=$.preFilter,n=$.filter,o=!c&&b!==h;for(;(e=l[i])!=null&&k;i++){g.push(d=[]),o&&(e=" "+e);while(e){k=!1;if(j=B.exec(e))e=e.slice(j[0].length),k=d.push({part:j.pop().replace(A," "),captures:j});for(f in n)(j=L[f].exec(e))&&(!m[f]||(j=m[f](j,b,c)))&&(e=e.slice(j.shift().length),k=d.push({part:f,captures:j}));if(!k)break}}return k||Z.error(a),g}function bh(a,b,e){var f=b.dir,g=m++;return a||(a=function(a){return a===e}),b.first?function(b,c){while(b=b[f])if(b.nodeType===1)return a(b,c)&&b}:function(b,e){var h,i=g+"."+d,j=i+"."+c;while(b=b[f])if(b.nodeType===1){if((h=b[q])===j)return b.sizset;if(typeof h=="string"&&h.indexOf(i)===0){if(b.sizset)return b}else{b[q]=j;if(a(b,e))return b.sizset=!0,b;b.sizset=!1}}}}function bi(a,b){return a?function(c,d){var e=b(c,d);return e&&a(e===!0?c:e,d)}:b}function bj(a,b,c){var d,e,f=0;for(;d=a[f];f++)$.relative[d.part]?e=bh(e,$.relative[d.part],b):(d.captures.push(b,c),e=bi(e,$.filter[d.part].apply(null,d.captures)));return e}function bk(a){return function(b,c){var d,e=0;for(;d=a[e];e++)if(d(b,c))return!0;return!1}}var c,d,e,f,g,h=a.document,i=h.documentElement,j="undefined",k=!1,l=!0,m=0,n=[].slice,o=[].push,q=("sizcache"+Math.random()).replace(".",""),r="[\\x20\\t\\r\\n\\f]",s="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",t=s.replace("w","w#"),u="([*^$|!~]?=)",v="\\["+r+"*("+s+")"+r+"*(?:"+u+r+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+t+")|)|)"+r+"*\\]",w=":("+s+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",x=":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",y=r+"*([\\x20\\t\\r\\n\\f>+~])"+r+"*",z="(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|"+v+"|"+w.replace(2,7)+"|[^\\\\(),])+",A=new RegExp("^"+r+"+|((?:^|[^\\\\])(?:\\\\.)*)"+r+"+$","g"),B=new RegExp("^"+y),C=new RegExp(z+"?(?="+r+"*,|$)","g"),D=new RegExp("^(?:(?!,)(?:(?:^|,)"+r+"*"+z+")*?|"+r+"*(.*?))(\\)|$)"),E=new RegExp(z.slice(19,-6)+"\\x20\\t\\r\\n\\f>+~])+|"+y,"g"),F=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,G=/[\x20\t\r\n\f]*[+~]/,H=/:not\($/,I=/h\d/i,J=/input|select|textarea|button/i,K=/\\(?!\\)/g,L={ID:new RegExp("^#("+s+")"),CLASS:new RegExp("^\\.("+s+")"),NAME:new RegExp("^\\[name=['\"]?("+s+")['\"]?\\]"),TAG:new RegExp("^("+s.replace("[-","[-\\*")+")"),ATTR:new RegExp("^"+v),PSEUDO:new RegExp("^"+w),CHILD:new RegExp("^:(only|nth|last|first)-child(?:\\("+r+"*(even|odd|(([+-]|)(\\d*)n|)"+r+"*(?:([+-]|)"+r+"*(\\d+)|))"+r+"*\\)|)","i"),POS:new RegExp(x,"ig"),needsContext:new RegExp("^"+r+"*[>+~]|"+x,"i")},M={},N=[],O={},P=[],Q=function(a){return a.sizzleFilter=!0,a},R=function(a){return function(b){return b.nodeName.toLowerCase()==="input"&&b.type===a}},S=function(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}},T=function(a){var b=!1,c=h.createElement("div");try{b=a(c)}catch(d){}return c=null,b},U=T(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),V=T(function(a){a.id=q+0,a.innerHTML="
    ",i.insertBefore(a,i.firstChild);var b=h.getElementsByName&&h.getElementsByName(q).length===2+h.getElementsByName(q+0).length;return g=!h.getElementById(q),i.removeChild(a),b}),W=T(function(a){return a.appendChild(h.createComment("")),a.getElementsByTagName("*").length===0}),X=T(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==j&&a.firstChild.getAttribute("href")==="#"}),Y=T(function(a){return a.innerHTML="",!a.getElementsByClassName||a.getElementsByClassName("e").length===0?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length!==1)}),Z=function(a,b,c,d){c=c||[],b=b||h;var e,f,g,i,j=b.nodeType;if(j!==1&&j!==9)return[];if(!a||typeof a!="string")return c;g=ba(b);if(!g&&!d)if(e=F.exec(a))if(i=e[1]){if(j===9){f=b.getElementById(i);if(!f||!f.parentNode)return c;if(f.id===i)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(i))&&bb(b,f)&&f.id===i)return c.push(f),c}else{if(e[2])return o.apply(c,n.call(b.getElementsByTagName(a),0)),c;if((i=e[3])&&Y&&b.getElementsByClassName)return o.apply(c,n.call(b.getElementsByClassName(i),0)),c}return bm(a,b,c,d,g)},$=Z.selectors={cacheLength:50,match:L,order:["ID","TAG"],attrHandle:{},createPseudo:Q,find:{ID:g?function(a,b,c){if(typeof b.getElementById!==j&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==j&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==j&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:W?function(a,b){if(typeof b.getElementsByTagName!==j)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(K,""),a[3]=(a[4]||a[5]||"").replace(K,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||Z.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&Z.error(a[0]),a},PSEUDO:function(a){var b,c=a[4];return L.CHILD.test(a[0])?null:(c&&(b=D.exec(c))&&b.pop()&&(a[0]=a[0].slice(0,b[0].length-c.length-1),c=b[0].slice(0,-1)),a.splice(2,3,c||a[3]),a)}},filter:{ID:g?function(a){return a=a.replace(K,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(K,""),function(b){var c=typeof b.getAttributeNode!==j&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(K,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=M[a];return b||(b=M[a]=new RegExp("(^|"+r+")"+a+"("+r+"|$)"),N.push(a),N.length>$.cacheLength&&delete M[N.shift()]),function(a){return b.test(a.className||typeof a.getAttribute!==j&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return b?function(d){var e=Z.attr(d,a),f=e+"";if(e==null)return b==="!=";switch(b){case"=":return f===c;case"!=":return f!==c;case"^=":return c&&f.indexOf(c)===0;case"*=":return c&&f.indexOf(c)>-1;case"$=":return c&&f.substr(f.length-c.length)===c;case"~=":return(" "+f+" ").indexOf(c)>-1;case"|=":return f===c||f.substr(0,c.length+1)===c+"-"}}:function(b){return Z.attr(b,a)!=null}},CHILD:function(a,b,c,d){if(a==="nth"){var e=m++;return function(a){var b,f,g=0,h=a;if(c===1&&d===0)return!0;b=a.parentNode;if(b&&(b[q]!==e||!a.sizset)){for(h=b.firstChild;h;h=h.nextSibling)if(h.nodeType===1){h.sizset=++g;if(h===a)break}b[q]=e}return f=a.sizset-d,c===0?f===0:f%c===0&&f/c>=0}}return function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b,c,d){var e=$.pseudos[a]||$.pseudos[a.toLowerCase()];return e||Z.error("unsupported pseudo: "+a),e.sizzleFilter?e(b,c,d):e}},pseudos:{not:Q(function(a,b,c){var d=bl(a.replace(A,"$1"),b,c);return function(a){return!d(a)}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!$.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},contains:Q(function(a){return function(b){return(b.textContent||b.innerText||bc(b)).indexOf(a)>-1}}),has:Q(function(a){return function(b){return Z(a,b).length>0}}),header:function(a){return I.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:R("radio"),checkbox:R("checkbox"),file:R("file"),password:R("password"),image:R("image"),submit:S("submit"),reset:S("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return J.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b,c){return c?a.slice(1):[a[0]]},last:function(a,b,c){var d=a.pop();return c?a:[d]},even:function(a,b,c){var d=[],e=c?1:0,f=a.length;for(;e$.cacheLength&&delete O[P.shift()],g};Z.matches=function(a,b){return Z(a,null,null,b)},Z.matchesSelector=function(a,b){return Z(b,null,null,[a]).length>0};var bm=function(a,b,e,f,g){a=a.replace(A,"$1");var h,i,j,k,l,m,p,q,r,s=a.match(C),t=a.match(E),u=b.nodeType;if(L.POS.test(a))return bf(a,b,e,f,s);if(f)h=n.call(f,0);else if(s&&s.length===1){if(t.length>1&&u===9&&!g&&(s=L.ID.exec(t[0]))){b=$.find.ID(s[1],b,g)[0];if(!b)return e;a=a.slice(t.shift().length)}q=(s=G.exec(t[0]))&&!s.index&&b.parentNode||b,r=t.pop(),m=r.split(":not")[0];for(j=0,k=$.order.length;j",a.querySelectorAll("[selected]").length||e.push("\\["+r+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),T(function(a){a.innerHTML="

    ",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+r+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=e.length&&new RegExp(e.join("|")),bm=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a)))if(d.nodeType===9)try{return o.apply(f,n.call(d.querySelectorAll(a),0)),f}catch(i){}else if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){var j=d.getAttribute("id"),k=j||q,l=G.test(a)&&d.parentNode||d;j?k=k.replace(c,"\\$&"):d.setAttribute("id",k);try{return o.apply(f,n.call(l.querySelectorAll(a.replace(C,"[id='"+k+"'] $&")),0)),f}catch(i){}finally{j||d.removeAttribute("id")}}return b(a,d,f,g,h)},g&&(T(function(b){a=g.call(b,"div");try{g.call(b,"[test!='']:sizzle"),f.push($.match.PSEUDO)}catch(c){}}),f=new RegExp(f.join("|")),Z.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!ba(b)&&!f.test(c)&&(!e||!e.test(c)))try{var h=g.call(b,c);if(h||a||b.document&&b.document.nodeType!==11)return h}catch(i){}return Z(c,null,null,[b]).length>0})}(),Z.attr=p.attr,p.find=Z,p.expr=Z.selectors,p.expr[":"]=p.expr.pseudos,p.unique=Z.uniqueSort,p.text=Z.getText,p.isXMLDoc=Z.isXML,p.contains=Z.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
    ","
    "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(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){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=0,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(g=b===e&&bA;(h=a[s])!=null;s++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{g=g||bk(b),l=l||g.appendChild(b.createElement("div")),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(f=n.length-1;f>=0;--f)p.nodeName(n[f],"tbody")&&!n[f].childNodes.length&&n[f].parentNode.removeChild(n[f])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l=g.lastChild}h.nodeType?t.push(h):t=p.merge(t,h)}l&&(g.removeChild(l),h=l=g=null);if(!p.support.appendChecked)for(s=0;(h=t[s])!=null;s++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(s=0;(h=t[s])!=null;s++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[s+1,0].concat(r)),s+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^margin/,bO=new RegExp("^("+q+")(.*)$","i"),bP=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bQ=new RegExp("^([-+])=("+q+")","i"),bR={},bS={position:"absolute",visibility:"hidden",display:"block"},bT={letterSpacing:0,fontWeight:400,lineHeight:1},bU=["Top","Right","Bottom","Left"],bV=["Webkit","O","Moz","ms"],bW=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return bZ(this,!0)},hide:function(){return bZ(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bW.apply(this,arguments):this.each(function(){(c?a:bY(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bX(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bQ.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bX(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bT&&(f=bT[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(a,b){var c,d,e,f,g=getComputedStyle(a,null),h=a.style;return g&&(c=g[b],c===""&&!p.contains(a.ownerDocument.documentElement,a)&&(c=p.style(a,b)),bP.test(c)&&bN.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=c,c=g.width,h.width=d,h.minWidth=e,h.maxWidth=f)),c}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bP.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0||bH(a,"display")!=="none"?ca(a,b,d):p.swap(a,bS,function(){return ca(a,b,d)})},set:function(a,c,d){return b$(a,c,d?b_(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bP.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bU[d]+b]=e[d]||e[d-2]||e[0];return f}},bN.test(a)||(p.cssHooks[a+b].set=b$)});var cc=/%20/g,cd=/\[\]$/,ce=/\r?\n/g,cf=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,cg=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||cg.test(this.nodeName)||cf.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(ce,"\r\n")}}):{name:b.name,value:c.replace(ce,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ch(d,a[d],c,f);return e.join("&").replace(cc,"+")};var ci,cj,ck=/#.*$/,cl=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cm=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,cn=/^(?:GET|HEAD)$/,co=/^\/\//,cp=/\?/,cq=/)<[^<]*)*<\/script>/gi,cr=/([?&])_=[^&]*/,cs=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,ct=p.fn.load,cu={},cv={},cw=["*/"]+["*"];try{ci=f.href}catch(cx){ci=e.createElement("a"),ci.href="",ci=ci.href}cj=cs.exec(ci.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&ct)return ct.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
    ").append(a.replace(cq,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cA(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cA(a,b),a},ajaxSettings:{url:ci,isLocal:cm.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cw},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cy(cu),ajaxTransport:cy(cv),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cB(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cC(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=""+(c||y),k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cl.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(ck,"").replace(co,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=cs.exec(l.url.toLowerCase()),l.crossDomain=!(!i||i[1]==cj[1]&&i[2]==cj[2]&&(i[3]||(i[1]==="http:"?80:443))==(cj[3]||(cj[1]==="http:"?80:443)))),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cz(cu,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!cn.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cp.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cr,"$1_="+z);l.url=A+(A===l.url?(cp.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cw+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cz(cv,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cD=[],cE=/\?/,cF=/(=)\?(?=&|$)|\?\?/,cG=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cD.pop()||p.expando+"_"+cG++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cF.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cF.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cF,"$1"+f):m?c.data=i.replace(cF,"$1"+f):k&&(c.url+=(cE.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cD.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cH,cI=a.ActiveXObject?function(){for(var a in cH)cH[a](0,1)}:!1,cJ=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cK()||cL()}:cK,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cI&&delete cH[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cJ,cI&&(cH||(cH={},p(a).unload(cI)),cH[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cM,cN,cO=/^(?:toggle|show|hide)$/,cP=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cQ=/queueHooks$/,cR=[cX],cS={"*":[function(a,b){var c,d,e,f=this.createTween(a,b),g=cP.exec(b),h=f.cur(),i=+h||0,j=1;if(g){c=+g[2],d=g[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&i){i=p.css(f.elem,a,!0)||c||1;do e=j=j||".5",i=i/j,p.style(f.elem,a,i+d),j=f.cur()/h;while(j!==1&&j!==e)}f.unit=d,f.start=i,f.end=g[1]?i+(g[1]+1)*c:c}return f}]};p.Animation=p.extend(cV,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c$.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c$.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=c_(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/themes/basic/public/js/leaner.js b/themes/basic/public/js/leaner.js new file mode 100644 index 0000000..76f99cf --- /dev/null +++ b/themes/basic/public/js/leaner.js @@ -0,0 +1,68 @@ +(function($){ + $.fn.extend({ + leaner: function(options) { + + var close = function (id){ + $("#lean-overlay").fadeOut(200); + $('#box-modal').hide(); + }; + + var center = function(width, height) + { + $('#box-modal').css({ + 'display' : 'block', + 'position' : 'fixed', + 'opacity' : 0, + 'z-index': 11000, + 'left' : 50 + '%', + 'margin-left' : -(width / 2) + "px", + 'top' : 50 + '%', + 'margin-top' : -(height / 2) + "px" + }); + } + + var defaults = { + top: 100, + overlay: 0.7 + }; + + $("body").append($("
    ")).append($("
    ")); + + options = $.extend({}, defaults, options); + + return this.each(function() { + + $(this).click(function(e) { + + $('#box-modal').show().find('#box-content').html('
    '); + + center($('#box-modal').outerWidth(), $('#box-modal').outerHeight()); + + $('#box-modal').find('#box-content').load($(this).attr("href") + ' .content', function() + { + $("#lean-overlay, #box-close").click(function() { + close(); + }); + + $(document).one('keydown', function(e) { + if(e.keyCode == 27) { + close(); + } + }); + + var overlay = $('#lean-overlay'); + + overlay.css({ 'display' : 'block', opacity : 0 }); + overlay.fadeTo(200, options.overlay); + + center($('#box-modal').outerWidth(), $('#box-modal').outerHeight()); + + $('#box-modal').fadeTo(200, 1); + }); + + e.preventDefault(); + }); + }); + } + }); +})(jQuery); diff --git a/themes/basic/public/js/theme.js b/themes/basic/public/js/theme.js new file mode 100644 index 0000000..07d1b54 --- /dev/null +++ b/themes/basic/public/js/theme.js @@ -0,0 +1,170 @@ +var FEATHER = {}; + +(function($) +{ + /** + * Register the leaner modal that works for this theme. + */ + $('a.popup-ui').leaner(); + + /** + * Register the tooltips that appear throughout the theme. + */ + $('.tooltip-ui').tooltip({ 'position': 'top', 'offset': 2, 'live': true }); + $('.tooltip-ui-right').tooltip({ 'position': 'right', 'offset': 4, 'live': true }); + $('.tooltip-ui-places').tooltip({ 'position': 'bottomLeft', 'offset': 3, 'width': 350 }); + $('.tooltip-ui-footer').tooltip({ 'position': 'right', 'offset': 5, 'width': 350 }); + + /** + * Assign the auto complete plugin to our discussion participants input box. + */ + (function($) + { + var properties = { + cache: {}, + participants: [], + elements: { + input: $('.discussion-participants').find('input#participants'), + auto: $('.discussion-participants').find('.autocomplete'), + anyone: $('.participants').find('.anyone'), + participants: $('.participants'), + loading: $('
    ').attr('class', 'loading') + } + } + + function add(participant) + { + // Hide the default 'anyone can participate' message. + properties.elements.anyone.hide(); + + // Loop over each participant and if they are the same as the one trying to be + // added then forget it! They can only be added once. + for(var i = 0; i < properties.participants.length; ++i) + { + if(properties.participants[i] == participant.value) + { + return; + } + } + + // Add the participants name to the participants array. + properties.participants.push(participant.value); + + // Add the participants avatar to the participants div. + properties.elements.participants.append($('').attr({ + 'src': participant.avatar + '?d=mm', + 'class': 'tooltip-ui', + 'title': participant.value + }).data('name', participant.value).fadeIn('fast')); + } + + /** + * If there is some comma separated names already in the participants input box we'll poll a request + * to the API to get all of the users details, we need their avatar. + */ + if(properties.elements.input.val().length) + { + var array = $.map(properties.elements.input.val().split(','), $.trim); + + properties.elements.input.val(''); + + properties.elements.anyone.hide().after(properties.elements.loading); + + $.getJSON(app.base + '/api/v1/user/bunch.json', { data: JSON.stringify(array) }, function(data, status, xhr) + { + properties.elements.loading.fadeOut('fast', function() + { + for(var i = 0; i < data.length; ++i) + { + setTimeout(add, (i + 1) * 150, { value: data[i].username, avatar: data[i].avatar }); + } + }); + }); + } + + properties.elements.input.autocomplete({ + source: function(request, response) + { + if(request.term in properties.cache) + { + response(properties.cache[request.term]); + + return; + } + + $.getJSON(app.base + '/api/v1/user/find.json', request, function(data, status, xhr) + { + var store = []; + + for(var i = 0; i < data.length; ++i) + { + store.push({ + 'value': data[i].username, + 'avatar': data[i].avatar + }); + } + + properties.cache[request.term] = store; + + response(store); + }); + }, + appendTo: properties.elements.auto, + position: { my: "left top", at: "left bottom", collision: "none", offset: "0 14px" }, + autoFocus: true, + + // When we show the results highlight the typed text in the matched results. + open: function (e, ui) + { + var data = properties.elements.input.data('autocomplete'); + + data.menu.element.find('a').each(function() + { + $(this).html($(this).text().replace(new RegExp('(' + data.term.split(' ').join('|') + ')', 'gi'), '$1')); + }); + }, + + // When an item is selected we'll add to the array of participants, then we'll add the + // users avatar to the participants div. + select: function(e, ui) + { + e.preventDefault(); + + add(ui.item); + + properties.elements.input.val('').autocomplete('close'); + } + }); + + // Attach an event handler to any clicking events of any images inside the participants div, + // we'll then remove the user from the selected participants. + properties.elements.participants.find('img').live('click', function() + { + var name = $(this).data('name'); + + for(var i = 0; i < properties.participants.length; ++i) + { + if(properties.participants[i] == name) break; + } + + properties.participants.splice(i, 1); + + $(this).fadeOut('fast', function() + { + // If there are no participants, show the default text. + if(properties.participants.length == 0) $('.participants').find('.anyone').show(); + }); + }); + + // Attach an event handler to the submission of the form so we can inject another form field + // with a comma separated list of participants. + $('.manage-discussion').find('form').on('submit', function(e) + { + $(this).find('input[name=participants]').after($('').attr({ + name: 'participants', + type: 'hidden', + value: properties.participants.join(',') + })); + }); + })($); +})(jQuery); \ No newline at end of file diff --git a/themes/basic/public/js/tooltip.js b/themes/basic/public/js/tooltip.js new file mode 100644 index 0000000..54ed2cc --- /dev/null +++ b/themes/basic/public/js/tooltip.js @@ -0,0 +1,125 @@ +/** + * Tooltip for jQuery by Jason Lewis + * ----------------------------------------- + * This is a simple tooltip plugin for jQuery which allows tooltips on elements with an arrow + * at multiple positions of the tooltip. + * + * @author Jason Lewis + * @copyright 2011 - 2012 Jason Lewis + * @version 1.0 + * @license 3-clause BSD + */ +(function($){ + $.fn.tooltip = function(opts){ + + var options = $.extend({}, $.fn.tooltip.defaults, opts), + elements = { + container: $('
    '), + wrapper: $('
    '), + arrow: $('
    '), + content: $('
    ') + }, + binder = options.live ? 'live' : 'bind', + title = ''; + + // Append the tooltip contents to the body + $('body').append(elements.container.html(elements.wrapper.html(elements.arrow).append(elements.content))); + + // Set some options + elements.container.css({ maxWidth: options.width + 'px' }); + + return this[binder]({ + mouseover: function(){ + var elm = $(this); + title = elm.attr(options.attr); + + if(title.length > 0){ + elements.content.text(title); + elm.attr(options.attr, ''); + + var dimensions = { + top: elm.offset().top, + left: elm.offset().left, + width: elm.outerWidth(), + height: elm.outerHeight(), + actualWidth: elm.outerWidth(true), + actualHeight: elm.outerHeight(true), + arrow: { + width: elements.arrow.outerWidth() / 2, + height: elements.arrow.outerHeight() / 2 + } + }, + offsets = {}; + + // Determine the position. + switch(options.position.charAt(0)){ + case 'b': + elements.container.addClass('arrow-b'); + offsets = { + top: (dimensions.top + dimensions.height) + dimensions.arrow.height + options.offset, + left: dimensions.left + (dimensions.width / 2) - (elements.container.outerWidth() / 2) + }; + break; + case 't': + elements.container.addClass('arrow-t'); + offsets = { + top: (dimensions.top - elements.container.outerHeight(true)) - dimensions.arrow.height - options.offset, + left: dimensions.left + (dimensions.width / 2) - (elements.container.outerWidth() / 2) + }; + break; + case 'l': + elements.container.addClass('arrow-l'); + offsets = { + top: dimensions.top + (dimensions.height / 2) - (elements.container.outerHeight() / 2), + left: dimensions.left - elements.container.outerWidth(true) - dimensions.arrow.width - options.offset + }; + break; + case 'r': + elements.container.addClass('arrow-r'); + offsets = { + top: dimensions.top + (dimensions.height / 2) - (elements.container.outerHeight() / 2), + left: dimensions.left + dimensions.width + dimensions.arrow.width + options.offset + }; + break; + } + + if(options.position == 'topLeft' || options.position == 'bottomLeft') + { + offsets.left += (elements.container.outerWidth() / 2) - (dimensions.width / 2); + + elements.arrow.css('left', elements.arrow.outerWidth()); + } + else if(options.position == 'topRight' || options.position == 'bottomRight') + { + offsets.left = (dimensions.left + dimensions.width) - elements.container.outerWidth(); + + elements.arrow.css('left', elements.container.outerWidth() - elements.arrow.outerWidth()); + } + else if(options.position == 'rightTop' || options.position == 'leftTop') + { + offsets.top = dimensions.top; + elements.arrow.css('top', dimensions.height / 2); + } + + elements.container.css(offsets).show(); + } + }, + mouseout: function(){ + elements.container.hide(); + + $(this).attr(options.attr, title); + } + }); + + return this; + }; + + // Default Options + $.fn.tooltip.defaults = { + width: 300, + offset: 0, + live: false, + position: 'top', + attr: 'title' + }; +})(jQuery); \ No newline at end of file diff --git a/themes/basic/start.php b/themes/basic/start.php new file mode 100644 index 0000000..4acb76d --- /dev/null +++ b/themes/basic/start.php @@ -0,0 +1,27 @@ +add('theme', 'css/theme.css'); + +/* +|-------------------------------------------------------------------------- +| Theme Scripts +|-------------------------------------------------------------------------- +| +| Register the themes scripts. +| +*/ + +Asset::container('theme')->add('jquery', 'js/jquery.js') + ->add('jquery-ui', 'js/jquery-ui.js') + ->add('leaner', 'js/leaner.js') + ->add('tooltip', 'js/tooltip.js') + ->add('theme', 'js/theme.js'); \ No newline at end of file diff --git a/themes/basic/views/misc/authorization.blade.php b/themes/basic/views/misc/authorization.blade.php new file mode 100644 index 0000000..7532db1 --- /dev/null +++ b/themes/basic/views/misc/authorization.blade.php @@ -0,0 +1,7 @@ +{{ HTML::link('', '', array('class' => 'openid tooltip-ui', 'title' => 'OpenID')) }} + +{{ HTML::link('', '', array('class' => 'google tooltip-ui', 'title' => 'Google')) }} + +{{ HTML::link('', '', array('class' => 'facebook tooltip-ui', 'title' => 'Facebook')) }} + +{{ HTML::link('', '', array('class' => 'twitter tooltip-ui', 'title' => 'Twitter')) }} \ No newline at end of file diff --git a/themes/basic/views/template.blade.php b/themes/basic/views/template.blade.php new file mode 100644 index 0000000..3b093e2 --- /dev/null +++ b/themes/basic/views/template.blade.php @@ -0,0 +1,90 @@ + + + + + + + @event('view: before template.title') {{ $title }} – {{ $app->title }} + + + {{ Asset::container('theme')->styles() }} + + + + + + +
    + +
    + +
    + @if(Feather\Auth::online()) + @include('feather::menus.user') + @else + @include('feather::menus.guest') + @endif +
    + + + + + +
    + +
    + + + @if($alert) +
    + {{ $alert->message }} +
    + @endif + +
    + {{ $content }} +
    +
    + + + +
    + + {{ Asset::container('theme')->scripts() }} + + + + \ No newline at end of file From 60309c69e029c29d17bad4f647278422519fb609 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:54:56 +0930 Subject: [PATCH 015/136] Allow components to be registered with Feather. Signed-off-by: Jason Lewis --- start.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/start.php b/start.php index e33634f..f361715 100644 --- a/start.php +++ b/start.php @@ -66,6 +66,24 @@ $feather['config']->db(); +/* +|-------------------------------------------------------------------------- +| Load Feather Components +|-------------------------------------------------------------------------- +| +| Load in the Feather components. +| +*/ + +foreach($feather['config']->get('feather: feather.components') as $component => $closure) +{ + if(is_callable($closure)) + { + $closure($feather); + } +} + + /* |-------------------------------------------------------------------------- | Load Feather Facades From 24baf468247462ffc9aa7066e3f0c88d3196a965 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:55:28 +0930 Subject: [PATCH 016/136] Removed old register method. Signed-off-by: Jason Lewis --- components/feather/application.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/feather/application.php b/components/feather/application.php index 320e653..89b6820 100644 --- a/components/feather/application.php +++ b/components/feather/application.php @@ -13,17 +13,6 @@ class Application implements ArrayAccess { */ private $container = array(); - /** - * Register a provider with the application. - * - * @param object $provider - * @return void - */ - public function register($provider) - { - $provider->register($this); - } - /** * Share a closure such that it is a singleton. * From 4bb59e7a7e5c8e9e228505a4d92376b1fc3be0f7 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 01:57:27 +0930 Subject: [PATCH 017/136] Initial commit of auth component. Signed-off-by: Jason Lewis --- components/auth/driver.php | 5 +++++ components/auth/protector.php | 10 ++++++++++ config/feather.php | 10 +++++++++- start/facades.php | 11 +++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 components/auth/driver.php create mode 100644 components/auth/protector.php diff --git a/components/auth/driver.php b/components/auth/driver.php new file mode 100644 index 0000000..b46831f --- /dev/null +++ b/components/auth/driver.php @@ -0,0 +1,5 @@ + array(), + 'components' => array( + 'auth' => function($feather) + { + $feather['auth'] = $feather->share(function($feather) + { + return new Feather\Components\Auth\Protector; + }); + } + ), /* |-------------------------------------------------------------------------- diff --git a/start/facades.php b/start/facades.php index 3054823..df4521b 100644 --- a/start/facades.php +++ b/start/facades.php @@ -9,4 +9,15 @@ class Config extends Components\Support\Facade { */ protected static function accessor(){ return 'config'; } +} + +class Auth extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'auth'; } + } \ No newline at end of file From 775c4f7e7cdf393257d0337f7cbeaa10eea23317 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 02:16:50 +0930 Subject: [PATCH 018/136] Cleanup of protecter class. Signed-off-by: Jason Lewis --- components/auth/protector.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/auth/protector.php b/components/auth/protector.php index e205370..46db9ba 100644 --- a/components/auth/protector.php +++ b/components/auth/protector.php @@ -2,9 +2,6 @@ class Protector { - public function something() - { - return 'rabbits'; - } + } \ No newline at end of file From 2f2bd4fb6caf88b4562055a9411244bff1cb9c63 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 02:17:04 +0930 Subject: [PATCH 019/136] Initial commut of gear component. Signed-off-by: Jason Lewis --- components/gear/manager.php | 7 +++++++ config/feather.php | 7 +++++++ start/facades.php | 11 +++++++++++ 3 files changed, 25 insertions(+) create mode 100644 components/gear/manager.php diff --git a/components/gear/manager.php b/components/gear/manager.php new file mode 100644 index 0000000..d74bf91 --- /dev/null +++ b/components/gear/manager.php @@ -0,0 +1,7 @@ + function($feather) + { + $feather['gear'] = $feather->share(function($feather) + { + return new Feather\Components\Gear\Manager; + }); } ), diff --git a/start/facades.php b/start/facades.php index df4521b..93e8278 100644 --- a/start/facades.php +++ b/start/facades.php @@ -20,4 +20,15 @@ class Auth extends Components\Support\Facade { */ protected static function accessor(){ return 'auth'; } +} + +class Gear extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'gear'; } + } \ No newline at end of file From 3708a17015df0e7b2bcec18bec1f29c257389f98 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 13:47:55 +0930 Subject: [PATCH 020/136] Removed start directory, autoloading is now in start.php Signed-off-by: Jason Lewis --- start/facades.php => facades.php | 0 start.php | 7 +++++-- start/autoloading.php | 16 ---------------- 3 files changed, 5 insertions(+), 18 deletions(-) rename start/facades.php => facades.php (100%) delete mode 100644 start/autoloading.php diff --git a/start/facades.php b/facades.php similarity index 100% rename from start/facades.php rename to facades.php diff --git a/start.php b/start.php index f361715..b07830e 100644 --- a/start.php +++ b/start.php @@ -2,6 +2,7 @@ use Bundle; use Request; +use Autoloader; /* |-------------------------------------------------------------------------- @@ -31,7 +32,9 @@ | */ -require path('feather') . 'start' . DS . 'autoloading' . EXT; +Autoloader::namespaces(array( + 'Feather' => path('feather') +)); /* |-------------------------------------------------------------------------- @@ -96,7 +99,7 @@ Components\Support\Facade::application($feather); -require path('feather') . 'start' . DS . 'facades' . EXT; +require path('feather') . 'facades' . EXT; /* |-------------------------------------------------------------------------- diff --git a/start/autoloading.php b/start/autoloading.php deleted file mode 100644 index 6f453f0..0000000 --- a/start/autoloading.php +++ /dev/null @@ -1,16 +0,0 @@ - path('feather') -)); \ No newline at end of file From 705df1b19ebbb1a27515ab485389f2bc884880c0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 13:49:01 +0930 Subject: [PATCH 021/136] Fixed a comment. Signed-off-by: Jason Lewis --- start.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.php b/start.php index b07830e..3462e43 100644 --- a/start.php +++ b/start.php @@ -28,7 +28,7 @@ | Core Autoloading |-------------------------------------------------------------------------- | -| Register the Feather namespaces and mappings Laravel's autoloader. +| Register the Feather namespaces and mappings with Laravel's autoloader. | */ From ce61b484fb6e94b78a89589e8243f68ee4de36c7 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 13:52:39 +0930 Subject: [PATCH 022/136] Fixed bug with old applications directory naming. Signed-off-by: Jason Lewis --- start.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start.php b/start.php index 3462e43..cd73c43 100644 --- a/start.php +++ b/start.php @@ -15,9 +15,9 @@ set_path('feather', __DIR__ . DS); -set_path('core', path('feather') . 'app' . DS . 'core' . DS); +set_path('core', path('feather') . 'applications' . DS . 'core' . DS); -set_path('admin', path('feather') . 'app' . DS . 'admin' . DS); +set_path('admin', path('feather') . 'applications' . DS . 'admin' . DS); set_path('gears', path('feather') . 'gears' . DS); From d4cfbcd8009bc9d1a0dac5295739378a3cac177c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 14:36:43 +0930 Subject: [PATCH 023/136] Load Feather instance and register autoloaders in core start. Signed-off-by: Jason Lewis --- applications/core/start.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/applications/core/start.php b/applications/core/start.php index a814366..d4e6476 100644 --- a/applications/core/start.php +++ b/applications/core/start.php @@ -1 +1,27 @@ - path('core') . 'models' +)); \ No newline at end of file From ab034c130f7b30169c627de1747cb496da3964bb Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 14:36:57 +0930 Subject: [PATCH 024/136] Initial commit of core models. Signed-off-by: Jason Lewis --- applications/core/models/base.php | 39 +++++++++++++++++++++++++++++++ applications/core/models/gear.php | 19 +++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 applications/core/models/base.php create mode 100644 applications/core/models/gear.php diff --git a/applications/core/models/base.php b/applications/core/models/base.php new file mode 100644 index 0000000..903ac5c --- /dev/null +++ b/applications/core/models/base.php @@ -0,0 +1,39 @@ + Date: Sat, 22 Sep 2012 14:37:29 +0930 Subject: [PATCH 025/136] Allow the facade instance to be returned. Signed-off-by: Jason Lewis --- components/support/facade.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/support/facade.php b/components/support/facade.php index 0d800c5..3b982e9 100644 --- a/components/support/facade.php +++ b/components/support/facade.php @@ -12,13 +12,18 @@ abstract class Facade { protected static $feather; /** - * Set the application facade instance. + * Set or get the application facade instance. * * @param Feather\Components\Feather\Application $app - * @return void + * @return Feather\Components\Feather\Application */ - public static function application($app) + public static function application($app = null) { + if(is_null($app)) + { + return static::$feather; + } + static::$feather = $app; } From 70b5f116cccca4ca642b7497a7931b3fdfe04203 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 14:38:11 +0930 Subject: [PATCH 026/136] Gears can be registered and events can be registered. Signed-off-by: Jason Lewis --- components/gear/container.php | 73 +++++++++++++++++++ components/gear/foundation.php | 89 +++++++++++++++++++++++ components/gear/manager.php | 125 ++++++++++++++++++++++++++++++++- 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 components/gear/container.php create mode 100644 components/gear/foundation.php diff --git a/components/gear/container.php b/components/gear/container.php new file mode 100644 index 0000000..abb3ce8 --- /dev/null +++ b/components/gear/container.php @@ -0,0 +1,73 @@ +container[$key]); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + if(!$this->offsetExists($key)) + { + throw new InvalidArgumentException("Identifier {$key} is not in the container."); + } + + return $this->container[$key]($this); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + if(!$value instanceof Closure) + { + $value = function() use ($value) + { + return $value; + }; + } + + $this->container[$key] = $value; + } + + /** + * Unset a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->container[$key]); + } + +} \ No newline at end of file diff --git a/components/gear/foundation.php b/components/gear/foundation.php new file mode 100644 index 0000000..2abb1ac --- /dev/null +++ b/components/gear/foundation.php @@ -0,0 +1,89 @@ +event('listen', $event, $handler); + } + + /** + * Overrides existing events listening for an event and fire + * a method or closure handler. + * + * @param string $event + * @param string|Closure $handler + * @return void + */ + public function override($event, $handler) + { + $this->event('override', $event, $handler); + } + + /** + * Binds different types of events. + * + * @param string $type + * @param string $event + * @param string|Closure $handler + * @return void + */ + private function event($type, $event, $handler) + { + $gear = $this; + + Event::$type($event, function($parameters = array()) use ($gear, $handler) + { + // If the handler is a callable closure then we'll execute the closure + // and return its result. + if(is_callable($handler)) + { + return $handler($parameters); + } + + // If the handler is a method that exists on the gear object then execute + // the method and return its result. + elseif(method_exists($gear, $handler)) + { + return call_user_func_array(array($gear, $handler), $parameters); + } + + // By default we'll return null, as the event manager will expect a non + // null result. + return null; + }); + + Event::fire('event'); + } + + /** + * Executed when a gear is enabled. + * + * @return void + */ + public function enabled(){} + + /** + * Executed when a gear is disabled. + * + * @return void + */ + public function disabled(){} + + /** + * Executed when a gear is removed. + * + * @return void + */ + public function remove(){} + +} \ No newline at end of file diff --git a/components/gear/manager.php b/components/gear/manager.php index d74bf91..8ff5994 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -1,7 +1,130 @@ location)) + { + $this->gears[$gear->identifier] = $gear; + + // Start any gears that have been set to automatically start. + if($gear->auto) + { + return $this->start($gear->identifier); + } + } + + return true; + } + + /** + * Determines if a Gear has been registered. + * + * @param string $gear + * @return bool + */ + public function registered($gear) + { + return isset($this->gears[$gear]); + } + + /** + * Start a registered Gear. + * + * @param string $gear + * @return bool + */ + public function start($gear) + { + if($this->started($gear)) + { + return $this->started[$gear]; + } + elseif(!$this->registered($gear)) + { + return false; + } + + $this->started[$gear] = new Container; + + // Spin through all the files within the gears directory and those that have + // the .gear.php suffix we'll require and hopefully be able to instantiate + // a new object. + foreach(new FilesystemIterator(path('gears') . $this->gears[$gear]->location) as $file) + { + if(ends_with($file->getFilename(), '.gear.php')) + { + require $file->getPathname(); + + $name = str_replace('.gear.php', null, $file->getFilename()); + + $class = "\\Feather\\Gear\\{$this->gears[$gear]->identifier}\\{$name}"; + + if(class_exists($class)) + { + $this->started[$gear][$name] = new $class; + } + } + } + + return $this->started[$gear]; + } + + /** + * Determines if a Gear has been started. + * + * @param string $gear + * @return bool + */ + public function started($gear) + { + return isset($this->started[$gear]); + } + + /** + * Gets a Gears container. + * + * @param string $gear + * @return object + */ + public function get($gear) + { + if(!$this->started($gear) or !$this->registered($gear)) + { + return false; + } + + return $this->started[$gear]; + } } \ No newline at end of file From 329016eeae412a7941eeeeefc7233cc2cc950df6 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:43:34 +0930 Subject: [PATCH 027/136] Fixed bug with manager. Signed-off-by: Jason Lewis --- components/gear/manager.php | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/components/gear/manager.php b/components/gear/manager.php index 8ff5994..9a79ba7 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -11,14 +11,14 @@ class Manager { * * @var array */ - protected $gears = array(); + public $gears = array(); /** * Started Gears. * * @var array */ - protected $started = array(); + public $started = array(); /** * Register a Gear with the manager. @@ -31,9 +31,18 @@ public function register(Gear $gear) if(!$gear instanceof Gear) { throw new InvalidArgumentException('Supplied Gear is a not supported type.'); - } + } + + if(starts_with($gear->location, 'path: ')) + { + $path = substr($gear->location, 6); + } + else + { + $path = path('gears') . $gear->location; + } - if(file_exists($path = path('gears') . $gear->location)) + if(file_exists($path)) { $this->gears[$gear->identifier] = $gear; @@ -80,7 +89,16 @@ public function start($gear) // Spin through all the files within the gears directory and those that have // the .gear.php suffix we'll require and hopefully be able to instantiate // a new object. - foreach(new FilesystemIterator(path('gears') . $this->gears[$gear]->location) as $file) + if(starts_with($this->gears[$gear]->location, 'path: ')) + { + $path = substr($this->gears[$gear]->location, 6); + } + else + { + $path = path('gears') . $this->gears[$gear]->location; + } + + foreach(new FilesystemIterator($path) as $file) { if(ends_with($file->getFilename(), '.gear.php')) { From adf3b7a41155daa8a2091493f8acd74fb463655e Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:43:50 +0930 Subject: [PATCH 028/136] Allow mockery to be autoloaded when executed via CLI. Signed-off-by: Jason Lewis --- start.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/start.php b/start.php index cd73c43..9cb3ade 100644 --- a/start.php +++ b/start.php @@ -122,4 +122,24 @@ )); starts_with(Request::uri(), $handles) and Bundle::start("feather/{$application}"); +} + +/* +|-------------------------------------------------------------------------- +| Feather CLI +|-------------------------------------------------------------------------- +| +| When Feather is running via the CLI we'll automatically load in Mockery +| for testing purposes only. +| +*/ + +if(Request::cli()) +{ + require 'Mockery/Loader.php'; + require 'Hamcrest/Hamcrest.php'; + + with(new \Mockery\Loader)->register(); + + var_dump('here'); } \ No newline at end of file From 2d4c6501baeea6e41ab6153585529f31f207c3e8 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:44:08 +0930 Subject: [PATCH 029/136] Refactoring config tests. Signed-off-by: Jason Lewis --- tests/config.test.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/config.test.php b/tests/config.test.php index 9899485..043bbea 100644 --- a/tests/config.test.php +++ b/tests/config.test.php @@ -2,11 +2,6 @@ class ConfigTest extends PHPUnit_Framework_TestCase { - public function setUp() - { - Bundle::start('feather'); - } - public function testItemsCanBeSet() { $config = $this->getRepository(); From 2cb9184134c0dda1429d9782d773ec00eadad854 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:44:18 +0930 Subject: [PATCH 030/136] Refactoring facade tests. Signed-off-by: Jason Lewis --- tests/facade.test.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/facade.test.php b/tests/facade.test.php index 150db8f..37f799c 100644 --- a/tests/facade.test.php +++ b/tests/facade.test.php @@ -6,24 +6,29 @@ class FacadeTest extends PHPUnit_Framework_TestCase { public function testFacadeCallsApplication() { - FacadeStub::application(array('foo' => new ApplicationStub)); + FacadeStub::application(array('stub' => new ApplicationStub)); - $this->assertEquals('apple', FacadeStub::bar()); + $this->assertEquals('dog', FacadeStub::cat()); } } class FacadeStub extends Feather\Components\Support\Facade { - protected static function accessor(){ return 'foo'; } + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'stub'; } } class ApplicationStub { - public function bar() + public static function cat() { - return 'apple'; + return 'dog'; } } \ No newline at end of file From 01c340329f3217f65255fa04813f4ad46143c9c0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:44:25 +0930 Subject: [PATCH 031/136] Initial commit of gear tests. Signed-off-by: Jason Lewis --- tests/gear.test.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/gear.test.php diff --git a/tests/gear.test.php b/tests/gear.test.php new file mode 100644 index 0000000..0013cf2 --- /dev/null +++ b/tests/gear.test.php @@ -0,0 +1,37 @@ + 'stub', + 'location' => 'path: ' . __DIR__ . DS . 'mock', + 'auto' => false + )); + + $manager = new Feather\Components\Gear\Manager; + + $manager->register($gear); + + $this->assertTrue($manager->registered('stub')); + } + + public function testCanStartGear() + { + $gear = new Feather\Models\Gear(array( + 'identifier' => 'stub', + 'location' => 'path: ' . __DIR__ . DS . 'mock', + 'auto' => false + )); + + $manager = new Feather\Components\Gear\Manager; + + $manager->register($gear); + + $this->assertTrue($manager->start('stub') instanceof Feather\Components\Gear\Container); + } + + + +} \ No newline at end of file From 53f563d0d1772861de1b2aa341c058968b8596a1 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:45:04 +0930 Subject: [PATCH 032/136] Removed some debugging things. Signed-off-by: Jason Lewis --- start.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/start.php b/start.php index 9cb3ade..3b5781f 100644 --- a/start.php +++ b/start.php @@ -140,6 +140,4 @@ require 'Hamcrest/Hamcrest.php'; with(new \Mockery\Loader)->register(); - - var_dump('here'); } \ No newline at end of file From 191041c9bad0b51b73868cb253a9e7a03825416c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 18:53:42 +0930 Subject: [PATCH 033/136] Added mock directory and mock gear. Signed-off-by: Jason Lewis --- tests/mock/mock.gear.php | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/mock/mock.gear.php diff --git a/tests/mock/mock.gear.php b/tests/mock/mock.gear.php new file mode 100644 index 0000000..b6c4ca1 --- /dev/null +++ b/tests/mock/mock.gear.php @@ -0,0 +1,3 @@ + Date: Sat, 22 Sep 2012 18:53:56 +0930 Subject: [PATCH 034/136] More refined testing for gear starting and registering. Signed-off-by: Jason Lewis --- tests/gear.test.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/gear.test.php b/tests/gear.test.php index 0013cf2..276a4c2 100644 --- a/tests/gear.test.php +++ b/tests/gear.test.php @@ -29,9 +29,9 @@ public function testCanStartGear() $manager->register($gear); - $this->assertTrue($manager->start('stub') instanceof Feather\Components\Gear\Container); + $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $manager->start('stub')); + $this->assertTrue($manager->started('stub')); + $this->assertInstanceOf('Feather\\Gear\\Stub\\Mock', $gear['mock']); } - - } \ No newline at end of file From 442bb58eaa349df081b548c47b74c8e82a4c4d15 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 19:04:24 +0930 Subject: [PATCH 035/136] Initial commit of skeleton gear, just an example. Signed-off-by: Jason Lewis --- gears/skeleton/bone.gear.php | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 gears/skeleton/bone.gear.php diff --git a/gears/skeleton/bone.gear.php b/gears/skeleton/bone.gear.php new file mode 100644 index 0000000..15b1a9d --- /dev/null +++ b/gears/skeleton/bone.gear.php @@ -0,0 +1,38 @@ +listen('event', function() + { + // Depending on the event you may be required to return a value or null. + }); + + // Listen for an event and execute a method on the class. + $this->listen('event', 'method'); + + // Override existing events that are listening for the same event. + $this->override('event', 'method'); + } + + /** + * This method will be executed when the event is fired by Feather. + * + * @return void + */ + public function method(){} + +} \ No newline at end of file From 01fca694a8c7b17b5d784a054ed01a4faaa19b4b Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 19:42:01 +0930 Subject: [PATCH 036/136] Fixed some issues with gears and allow gears to be disabled. Signed-off-by: Jason Lewis --- components/gear/manager.php | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/components/gear/manager.php b/components/gear/manager.php index 9a79ba7..47e666d 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -1,5 +1,6 @@ getFilename(), '.gear.php')) { - require $file->getPathname(); + require_once $file->getPathname(); $name = str_replace('.gear.php', null, $file->getFilename()); @@ -129,6 +130,20 @@ public function started($gear) return isset($this->started[$gear]); } + /** + * Disable a gear for this run only. + * + * @param string $gear + * @return void + */ + public function disable($gear) + { + if($this->registered($gear)) + { + unset($this->gears[$gear]); + } + } + /** * Gets a Gears container. * @@ -145,4 +160,28 @@ public function get($gear) return $this->started[$gear]; } + /** + * Fire an event and implodes the returned results. + * + * @param string $event + * @param array $parameters + * @return string + */ + public function fire($event, $parameters = array()) + { + return implode(PHP_EOL, array_filter(Event::fire($event, array($parameters)))); + } + + /** + * Fire the first event in the queue. + * + * @param string $event + * @param array $parameters + * @return string + */ + public function first($event, $parameters = array()) + { + return Event::first($event, $parameters); + } + } \ No newline at end of file From 3f427698f5b66d29e880545c220270e92a8d5818 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 19:42:14 +0930 Subject: [PATCH 037/136] More work on the gear tests. Signed-off-by: Jason Lewis --- tests/gear.test.php | 44 ++++++++++++++++++++++++++-------------- tests/mock/mock.gear.php | 21 ++++++++++++++++++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/tests/gear.test.php b/tests/gear.test.php index 276a4c2..7c808d8 100644 --- a/tests/gear.test.php +++ b/tests/gear.test.php @@ -2,36 +2,50 @@ class GearTest extends PHPUnit_Framework_TestCase { - public function testCanRegisterGear() + public $gear; + + public $manager; + + public function setUp() { - $gear = new Feather\Models\Gear(array( + $this->gear = new Feather\Models\Gear(array( 'identifier' => 'stub', 'location' => 'path: ' . __DIR__ . DS . 'mock', 'auto' => false )); - $manager = new Feather\Components\Gear\Manager; + $this->manager = new Feather\Components\Gear\Manager; - $manager->register($gear); + $this->manager->register($this->gear); - $this->assertTrue($manager->registered('stub')); + $this->manager->start('stub'); + } + + public function testCanRegisterGear() + { + $this->assertTrue($this->manager->registered('stub')); } public function testCanStartGear() { - $gear = new Feather\Models\Gear(array( - 'identifier' => 'stub', - 'location' => 'path: ' . __DIR__ . DS . 'mock', - 'auto' => false - )); + $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $this->manager->start('stub')); + $this->assertTrue($this->manager->started('stub')); + $this->assertInstanceOf('Feather\\Gear\\Stub\\Mock', $gear['mock']); + } - $manager = new Feather\Components\Gear\Manager; + public function testCanDisableGear() + { + $this->assertTrue($this->manager->registered('stub')); - $manager->register($gear); + $this->manager->disable('stub'); - $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $manager->start('stub')); - $this->assertTrue($manager->started('stub')); - $this->assertInstanceOf('Feather\\Gear\\Stub\\Mock', $gear['mock']); + $this->assertTrue(!$this->manager->registered('stub')); + } + + public function testGearEventsFire() + { + $this->assertEquals('cat', Feather\Components\Gear\Manager::first('mock.callable')); + $this->assertEquals('dog', Feather\Components\Gear\Manager::first('mock.method')); } } \ No newline at end of file diff --git a/tests/mock/mock.gear.php b/tests/mock/mock.gear.php index b6c4ca1..68b570d 100644 --- a/tests/mock/mock.gear.php +++ b/tests/mock/mock.gear.php @@ -1,3 +1,22 @@ listen('mock.callable', function() + { + return 'cat'; + }); + + $this->listen('mock.method', 'cat'); + } + + public function cat() + { + return 'dog'; + } + +} \ No newline at end of file From fd783d73dffcb35eb896d4d564b9494cb727356e Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 20:06:58 +0930 Subject: [PATCH 038/136] Renamed the feather component to foundation. Signed-off-by: Jason Lewis --- .../{feather => foundation}/application.php | 2 +- components/foundation/component.php | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) rename components/{feather => foundation}/application.php (97%) create mode 100644 components/foundation/component.php diff --git a/components/feather/application.php b/components/foundation/application.php similarity index 97% rename from components/feather/application.php rename to components/foundation/application.php index 89b6820..54e7e8a 100644 --- a/components/feather/application.php +++ b/components/foundation/application.php @@ -1,4 +1,4 @@ -feather = $feather; + } + +} \ No newline at end of file From 50da35d722c4b1bafb59f1c8177d869e87668900 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 20:07:27 +0930 Subject: [PATCH 039/136] Components now extend the base component class. Signed-off-by: Jason Lewis --- components/auth/protector.php | 17 +++++++++++++++-- components/config/repository.php | 8 ++++++-- components/gear/manager.php | 3 ++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/components/auth/protector.php b/components/auth/protector.php index 46db9ba..6b3567c 100644 --- a/components/auth/protector.php +++ b/components/auth/protector.php @@ -1,7 +1,20 @@ feather['config']->get('feather: db.auth.driver'); + + dd($authenticator); + } } \ No newline at end of file diff --git a/components/config/repository.php b/components/config/repository.php index c24fbab..10d88fb 100644 --- a/components/config/repository.php +++ b/components/config/repository.php @@ -5,8 +5,9 @@ use Cache; use Event; use Config; +use Feather\Components\Foundation; -class Repository { +class Repository extends Foundation\Component { /** * Dirty configuration items. @@ -19,10 +20,13 @@ class Repository { * Bootstrap the config repository, override the Laravel event for configuration * file loading so that Gear and Theme items can be picked up. * + * @param Feather\Components\Foundation\Application * @return void */ - public function __construct() + public function __construct(Foundation\Application $feather) { + parent::__construct($feather); + Event::override(Config::loader, function($bundle, $file) { if(str_contains($file, ':')) diff --git a/components/gear/manager.php b/components/gear/manager.php index 47e666d..cee4ee2 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -4,8 +4,9 @@ use FilesystemIterator; use Feather\Models\Gear; use InvalidArgumentException; +use Feather\Components\Foundation\Component; -class Manager { +class Manager extends Component { /** * Registered Gears. From b52bdb630d0243c80d48cae6262de41f230cf06b Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 20:07:52 +0930 Subject: [PATCH 040/136] Pass in the feather instance to each component. Signed-off-by: Jason Lewis --- config/feather.php | 4 ++-- start.php | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/config/feather.php b/config/feather.php index b0814ae..062902d 100644 --- a/config/feather.php +++ b/config/feather.php @@ -47,14 +47,14 @@ { $feather['auth'] = $feather->share(function($feather) { - return new Feather\Components\Auth\Protector; + return new Feather\Components\Auth\Protector($feather); }); }, 'gear' => function($feather) { $feather['gear'] = $feather->share(function($feather) { - return new Feather\Components\Gear\Manager; + return new Feather\Components\Gear\Manager($feather); }); } ), diff --git a/start.php b/start.php index 3b5781f..80eaee4 100644 --- a/start.php +++ b/start.php @@ -45,7 +45,7 @@ | */ -$feather = new Components\Feather\Application; +$feather = new Components\Foundation\Application; /* |-------------------------------------------------------------------------- @@ -58,9 +58,9 @@ | */ -$feather['config'] = $feather->share(function() +$feather['config'] = $feather->share(function($feather) { - return new Components\Config\Repository; + return new Components\Config\Repository($feather); }); define('FEATHER_DATABASE', 'feather'); @@ -124,6 +124,17 @@ starts_with(Request::uri(), $handles) and Bundle::start("feather/{$application}"); } +/* +|-------------------------------------------------------------------------- +| Feather Authentication +|-------------------------------------------------------------------------- +| +| Boostrap the authentication component. +| +*/ + +$feather['auth']->bootstrap(); + /* |-------------------------------------------------------------------------- | Feather CLI From 69d0e757719c62b66baef903412ccd1eec957a19 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 21:49:52 +0930 Subject: [PATCH 041/136] Renamed the protector class to authorizer. Signed-off-by: Jason Lewis --- components/auth/authorizer.php | 208 +++++++++++++++++++++++++++++++++ components/auth/protector.php | 20 ---- 2 files changed, 208 insertions(+), 20 deletions(-) create mode 100644 components/auth/authorizer.php delete mode 100644 components/auth/protector.php diff --git a/components/auth/authorizer.php b/components/auth/authorizer.php new file mode 100644 index 0000000..ac61fbe --- /dev/null +++ b/components/auth/authorizer.php @@ -0,0 +1,208 @@ +feather['config']->set('laravel: auth.driver', 'feather'); + + $authenticator = $this->feather['config']->get('feather: db.auth.driver'); + + if(!$this->online() and ($response = $this->feather['gear']->first("auth: bootstrap {$authenticator}"))) + { + Route::filter('feather::before', function() use ($response) + { + return $response; + }); + } + + $this->feather['gear']->fire('auth: started'); + } + + /** + * Access control prevents users without specific permissions from accessing certain parts + * of Feather. Actions can have rules defined in the configuration file. This method will + * check a user to see if they have the correct permissions to perform the given action. + * + * @param string $action + * @param object $resource + * @return bool + */ + public function can($action, $resource) + { + if(!str_contains($action, ': ')) + { + throw new InvalidArgumentException("Invalid action [{$action}] supplied to ACL."); + } + + list($verb, $action) = explode(': ', $action); + + // To make things look lovely when writing actions we'll replace any spaces with underscores + // as that's how role permissions are stored in the database. + $action = str_replace(' ', '_', $action); + + foreach($this->user()->roles as $role) + { + if($role->{"{$verb}_{$action}"}) + { + // If we have a valid resource then we'll need to see if we have a rule in the configuration + // for this action. If there is no rule then there is further checking that needs to be done + // on the resource. + if(!is_null($resource)) + { + if($this->feather['config']->has("feather: auth.rules.{$verb}.{$action}")) + { + $callback = $this->feather['config']->get("feather: auth.rules.{$verb}.{$action}"); + + return $callback($resource); + } + + continue; + } + + // If there was no valid resource then the action was simply a role based action. We + // can return true here because the user has the correct permissions for this action. + return true; + } + } + + // Sorry chum, but you can't do that! + return false; + } + + /** + * The complete opposite of above, determines if a user is not able to perform an action. + * + * @param string $action + * @param object $resource + * @return bool + */ + public function cannot($action, $resource) + { + return !$this->can($action, $resource); + } + + /** + * Determines if a user is a given role or roles. + * + * @param array $roles + * @return bool + */ + public function is($roles) + { + // If the role has an aliased name then make sure we grab the correct name of + // the role from the aliases array. + $aliases = $this->feather['config']->get('feather: auth.aliases'); + + foreach((array) $roles as $key => $role) + { + if(array_key_exists(strtolower($role), $aliases)) + { + $roles[$key] = $aliases[strtolower($role)]; + } + } + + foreach($this->user()->roles as $role) + { + if(in_array(strtolower($role->name), (array) $roles)) + { + return true; + } + + // Because some roles can be granted a super moderator permission, if we + // are checking that a user is a moderator we'll also check that the role + // is a super moderator. + if(in_array('moderator', $roles) and $role->super_moderator) + { + return true; + } + } + + return false; + } + + /** + * Determines if a user is not a given role or roles. + * + * @param array $roles + * @return bool + */ + public function not($roles) + { + return !$this->is($roles); + } + + /** + * Determines if a user is online. + * + * @return bool + */ + public function online() + { + return !$this->is('guest'); + } + + /** + * Determines if a user is offline. + * + * @return bool + */ + public function offline() + { + return !$this->online(); + } + + /** + * Determines if a user has activated their account. + * + * @return bool + */ + public function activated() + { + return (bool) $this->user()->activated; + } + + /** + * Logs a user out and flushes the session. + * + * @return void + */ + public function logout() + { + Session::flush(); + + Auth::logout(); + } + + /** + * Route methods that are uncallable through to Laravel's Auth class. This provids a clean + * interface for accessing methods on that class. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array(array('Auth', $method), $parameters); + } + +} \ No newline at end of file diff --git a/components/auth/protector.php b/components/auth/protector.php deleted file mode 100644 index 6b3567c..0000000 --- a/components/auth/protector.php +++ /dev/null @@ -1,20 +0,0 @@ -feather['config']->get('feather: db.auth.driver'); - - dd($authenticator); - } - -} \ No newline at end of file From 4ad159583369b0b68c7aeafcf11a91437698b216 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 21:50:05 +0930 Subject: [PATCH 042/136] Initial commit of auth config. Signed-off-by: Jason Lewis --- config/auth.php | 174 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 config/auth.php diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..186a23b --- /dev/null +++ b/config/auth.php @@ -0,0 +1,174 @@ + array( + 'admin' => 'administrator' + ), + + /* + |-------------------------------------------------------------------------- + | Action Rules + |-------------------------------------------------------------------------- + | + | Auth uses action rules when checking a user has permission to perform + | a given action. + | + | For example, a user may delete users but may not delete themselves. To + | do this we create a rule for that verb and action. + | + | Rules a placed within the verb for an action. The verbs are: 'access', + | 'use', 'start', 'edit', 'delete', 'approve', and 'moderate'. + | + */ + + 'rules' => array( + + /* + |-------------------------------------------------------------------------- + | Delete Verb + |-------------------------------------------------------------------------- + */ + + 'delete' => array( + + /* + |-------------------------------------------------------------------------- + | Deleting Users + |-------------------------------------------------------------------------- + | + | Users that can delete other users are not able to delete themselves. + | + */ + + 'users' => function($deleting) + { + return ($deleting->id != Feather\Auth::user()->id); + } + ), + + /* + |-------------------------------------------------------------------------- + | Edit Verb + |-------------------------------------------------------------------------- + */ + + 'edit' => array( + + /* + |-------------------------------------------------------------------------- + | Editing Discussion + |-------------------------------------------------------------------------- + | + | Users can edit their own discussions, unless they are Administrator or + | Moderator, they can edit all. + | + */ + + 'discussion' => function($discussion) + { + $user = Feather\Auth::user(); + + if(Feather\Auth::is('admin')) + { + return true; + } + elseif(Feather\Auth::is('moderator')) + { + return true; + } + + // The easy checks have been done, now to see if the user is a place specific + // moderator on the discussions place. + if($discussion->place->moderators) + { + foreach($discussion->place->moderators as $moderator) + { + if($user->id == $moderator->user_id) + { + return true; + } + } + } + + // Lastly if this discussion belongs to this user and they have permission to + // edit their own discussions we'll allow them to edit this discussion. + if($user->id == $discussion->user_id and Feather\Auth::can('edit: own discussions')) + { + return true; + } + + return false; + } + + ), + + /* + |-------------------------------------------------------------------------- + | View Verb + |-------------------------------------------------------------------------- + */ + + 'view' => array( + + /* + |-------------------------------------------------------------------------- + | Viewing Discussion + |-------------------------------------------------------------------------- + | + | Users can view all discussions unless it is either private or a draft. + | + */ + + 'discussion' => function($discussion) + { + $user = Feather\Auth::user(); + + // Private discussions can only be viewed by participants, the original + // poster, and administrators/moderators. + if($discussion->private) + { + if($discussion->user_id == $user->id or Feather\Auth::is(array('admin', 'moderator'))) + { + return true; + } + + foreach($discussion->participants as $participant) + { + if(Feather\Auth::online() and $participant->user_id == $user->id) + { + return true; + } + } + + return false; + } + + // Draft discussions can only be viewed by the original author and + // administrators/moderators. + elseif($discussion->draft) + { + if($discussion->user_id == $user->id or Feather\Auth::is(array('admin', 'moderator'))) + { + return true; + } + + return false; + } + + return true; + } + + ) + ) + +); \ No newline at end of file From 3bfdce4984bf510942bbcc43d0e86087e55522c0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 21:50:28 +0930 Subject: [PATCH 043/136] Initial commit of the auth dispatcher. Signed-off-by: Jason Lewis --- components/auth/dispatcher.php | 7 +++++++ config/feather.php | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 components/auth/dispatcher.php diff --git a/components/auth/dispatcher.php b/components/auth/dispatcher.php new file mode 100644 index 0000000..485de48 --- /dev/null +++ b/components/auth/dispatcher.php @@ -0,0 +1,7 @@ +share(function($feather) { - return new Feather\Components\Auth\Protector($feather); + return new Feather\Components\Auth\Authorizer($feather); + }); + }, + 'sso' => function($feather) + { + $feather['sso'] = $feather->share(function($feather) + { + return new Feather\Components\Auth\Dispactcher($feather); }); }, 'gear' => function($feather) From c2e10d983a3b56a5d47f2088c6a72eecf7eb97b3 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 21:50:44 +0930 Subject: [PATCH 044/136] Initial commit of role and user models. Signed-off-by: Jason Lewis --- applications/core/models/role.php | 12 ++++++++++++ applications/core/models/user.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 applications/core/models/role.php create mode 100644 applications/core/models/user.php diff --git a/applications/core/models/role.php b/applications/core/models/role.php new file mode 100644 index 0000000..e369d76 --- /dev/null +++ b/applications/core/models/role.php @@ -0,0 +1,12 @@ +has_many_and_belongs_to('Feather\\Models\\Role', 'user_roles', 'user_id', 'role_id'); + } + +} \ No newline at end of file From 1b0b567c5732651bd33f747b0cf8d131355e02c9 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 22:20:33 +0930 Subject: [PATCH 045/136] Polishing off the auth driver. Signed-off-by: Jason Lewis --- components/auth/driver.php | 116 +++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/components/auth/driver.php b/components/auth/driver.php index b46831f..96f21c9 100644 --- a/components/auth/driver.php +++ b/components/auth/driver.php @@ -1,5 +1,121 @@ find($id); + + // To provide a cleaner syntax when accessing roles, we'll make each of the roles keys + // the same as its ID. + $roles = array(); + + foreach($user->roles as $role) $roles[$role->id] = $role; + + $user->relationships['roles'] = $roles; + + return $user; + }, Models\User::cache_time); + } + else + { + return Cache::remember('guest', function() + { + return new Models\User(array( + 'roles' => array(3 => Models\Role::find(3)) + ), true); + }, Models\User::cache_time); + } + } + + /** + * Attempt to log a user in and if need be migrate them from an older system. + * + * @param array $credentials + * @return bool|object + */ + public function attempt($credentials = array()) + { + // Find the user in the database with the username they have provided in the + // login form. If no user can be found then that's as far as we go. + if(!$user = Models\User::where_username($credentials['username'])->first()) + { + return false; + } + + // If there is an active migration from another forum we'll need to perform + // a couple of extra checks. A migrating user has only a single role, which + // is the migrating roll. If the user has not been migrated then we'll + // attempt to migrate them. + if($migration = Models\Migration::active()) + { + $driver = Migrator\Drivers\Driver::make($migration); + + if($user->roles()->only('roles.id') == 6 and ($old = $driver->login($credentials))) + { + // Because the users password is most likely hashed - it damn + // well better be - we'll replace the password key in the array + // with the password they logged in with. That way Feather can + // re-hash it when storing it. + $old = (object) array_merge((array) $old, array('password' => $credentials['password'])); + + // Finally attempt to migrate the user. If the migration fails it's + // logged so that the administrator can take further action if + // required. + if(!$user = $driver->migrate_user($old, $user)) + { + dd('Failed to migrate user.'); + + return false; + } + } + else + { + return false; + } + } + else + { + // For a user that doesn't need to be migrated a simple hash check is in order. + if(!Hash::check($credentials['password'], $user->password)) + { + return false; + } + } + + return $this->login($user, array_get($credentials, 'remember')); + } + + /** + * Log a user in to Feather. + * + * @param int|Feather\Models\User $user + * @param bool $remember + * @return bool + */ + public function login($user, $remember = false) + { + if($user instanceof Models\User) + { + $user = $user->id; + } + + return parent::login($user, $remember); + } + } \ No newline at end of file From f7200e991efa4602d47774300b5b5c835be0f893 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 22:21:13 +0930 Subject: [PATCH 046/136] Initial commit of the auth dispatcher. Signed-off-by: Jason Lewis --- components/auth/dispatcher.php | 2 +- facades.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/components/auth/dispatcher.php b/components/auth/dispatcher.php index 485de48..695869b 100644 --- a/components/auth/dispatcher.php +++ b/components/auth/dispatcher.php @@ -2,6 +2,6 @@ use Feather\Components\Foundation\Component; -class Authorizer extends Component { +class Dispatcher extends Component { } \ No newline at end of file diff --git a/facades.php b/facades.php index 93e8278..966bc7c 100644 --- a/facades.php +++ b/facades.php @@ -22,6 +22,17 @@ protected static function accessor(){ return 'auth'; } } +class SSO extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'sso'; } + +} + class Gear extends Components\Support\Facade { /** From f44d2f4a9bd96ba05b3b1a9fd89528f83ab94b54 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 22:21:34 +0930 Subject: [PATCH 047/136] Initial commit of the migrations for authentication. Signed-off-by: Jason Lewis --- applications/core/models/migration.php | 22 +++++++ components/auth/migrator/drivers/driver.php | 72 +++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 applications/core/models/migration.php create mode 100644 components/auth/migrator/drivers/driver.php diff --git a/applications/core/models/migration.php b/applications/core/models/migration.php new file mode 100644 index 0000000..0bc0e1b --- /dev/null +++ b/applications/core/models/migration.php @@ -0,0 +1,22 @@ +first(); + } + +} \ No newline at end of file diff --git a/components/auth/migrator/drivers/driver.php b/components/auth/migrator/drivers/driver.php new file mode 100644 index 0000000..c5a970a --- /dev/null +++ b/components/auth/migrator/drivers/driver.php @@ -0,0 +1,72 @@ +database)) + { + $feather['config']->set('laravel: database.connections.feather:migration', array( + 'driver' => 'mysql', + 'host' => $migration->host, + 'database' => $migration->database, + 'username' => $migration->username, + 'password' => $migration->password, + 'charset' => 'utf8', + 'prefix' => '' + )); + + $connection = DB::connection('feather:migration'); + } + + if(isset(static::$extenders[$migration->driver])) + { + $resolver = static::$extenders[$migration->driver]; + + return static::$driver = $resolver($connection, $feather); + } + + switch($migration->driver) + { + case 'flux': + return static::$driver = new Flux($connection, $feather); + break; + } + } + + /** + * Register a custom migration driver. + * + * @param string $driver + * @param Closure $resolver + * @return void + */ + public static function extend($driver, $resolver) + { + static::$extenders[$driver] = $resolver; + } + +} \ No newline at end of file From b2b725f49fce68b3e80d592dc6909b8f8658c8e8 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 22:27:22 +0930 Subject: [PATCH 048/136] Added a FluxBB migration driver. Signed-off-by: Jason Lewis --- components/auth/migrator/drivers/driver.php | 46 +++++++++++++++++++++ components/auth/migrator/drivers/flux.php | 23 +++++++++++ 2 files changed, 69 insertions(+) create mode 100644 components/auth/migrator/drivers/flux.php diff --git a/components/auth/migrator/drivers/driver.php b/components/auth/migrator/drivers/driver.php index c5a970a..a3fea87 100644 --- a/components/auth/migrator/drivers/driver.php +++ b/components/auth/migrator/drivers/driver.php @@ -16,6 +16,34 @@ abstract class Driver { */ public static $extenders = array(); + /** + * The drivers database connection. + * + * @var Laravel\Database\Connection + */ + protected $connection; + + /** + * The feather instance. + * + * @var Feather\Components\Foundation\Application + */ + protected $feather; + + /** + * Creates a new migration driver instance. + * + * @param Laravel\Database\Connection $connection + * @param Feather\Components\Foundation\Application $feather + * @return void + */ + public function __construct(\Laravel\Database\Connection $connection, \Feather\Components\Foundation\Application $feather) + { + $this->connection = $connection; + + $this->feather = $feather; + } + /** * Loads a migration driver. * @@ -69,4 +97,22 @@ public static function extend($driver, $resolver) static::$extenders[$driver] = $resolver; } + /** + * Return the database connection for method chaining. + * + * @return Laravel\Database\Connection + */ + public function db() + { + return $this->connection; + } + + /** + * Attempt to log a user in on the system. + * + * @param array $credentials + * @return bool|object + */ + abstract public function login($credentials); + } \ No newline at end of file diff --git a/components/auth/migrator/drivers/flux.php b/components/auth/migrator/drivers/flux.php new file mode 100644 index 0000000..e54ba27 --- /dev/null +++ b/components/auth/migrator/drivers/flux.php @@ -0,0 +1,23 @@ +db()->where_username($credentials['username'])->first(); + + if(is_null($user) or sha1($credentials['password']) != $user->password) + { + return false; + } + + return $user; + } + +} \ No newline at end of file From 64029dcc9a970658c984ed61e7209e66670ede79 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 23:40:26 +0930 Subject: [PATCH 049/136] Removed the bootstrapping of the authenticator. Signed-off-by: Jason Lewis --- start.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/start.php b/start.php index 80eaee4..5cf8465 100644 --- a/start.php +++ b/start.php @@ -124,17 +124,6 @@ starts_with(Request::uri(), $handles) and Bundle::start("feather/{$application}"); } -/* -|-------------------------------------------------------------------------- -| Feather Authentication -|-------------------------------------------------------------------------- -| -| Boostrap the authentication component. -| -*/ - -$feather['auth']->bootstrap(); - /* |-------------------------------------------------------------------------- | Feather CLI From 302149571f4eacc28df5d05f16b65ab7c4c56e92 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 23:41:19 +0930 Subject: [PATCH 050/136] Fixed a number of bugs with the authorizer. Signed-off-by: Jason Lewis --- components/auth/authorizer.php | 42 ++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/components/auth/authorizer.php b/components/auth/authorizer.php index ac61fbe..16586f0 100644 --- a/components/auth/authorizer.php +++ b/components/auth/authorizer.php @@ -4,18 +4,27 @@ use Route; use Session; use InvalidArgumentException; -use Feather\Components\Foundation\Component; +use Feather\Components\Foundation; -class Authorizer extends Component { +class Authorizer extends Foundation\Component { /** - * Bootstrap the authentication by extending with a custom driver and firing - * any registerd authenticator events. + * The current logged in user. * + * @var mixed + */ + public $user; + + /** + * Overload the constructor, bootstrap the authenticator. + * + * @param Feather\Components\Foundation\Application $feather * @return void */ - public function bootstrap() + public function __construct(Foundation\Application $feather) { + parent::__construct($feather); + Auth::extend('feather', function() { return new Driver; @@ -24,6 +33,10 @@ public function bootstrap() // Override the Laravel authentication driver for this run only. $this->feather['config']->set('laravel: auth.driver', 'feather'); + $this->user = $this->user(); + + // Depending on the authenticator being used their may be a response thrown back that + // interupts the normal response of Feather. $authenticator = $this->feather['config']->get('feather: db.auth.driver'); if(!$this->online() and ($response = $this->feather['gear']->first("auth: bootstrap {$authenticator}"))) @@ -46,7 +59,7 @@ public function bootstrap() * @param object $resource * @return bool */ - public function can($action, $resource) + public function can($action, $resource = null) { if(!str_contains($action, ': ')) { @@ -59,7 +72,7 @@ public function can($action, $resource) // as that's how role permissions are stored in the database. $action = str_replace(' ', '_', $action); - foreach($this->user()->roles as $role) + foreach($this->user->roles as $role) { if($role->{"{$verb}_{$action}"}) { @@ -95,7 +108,7 @@ public function can($action, $resource) * @param object $resource * @return bool */ - public function cannot($action, $resource) + public function cannot($action, $resource = null) { return !$this->can($action, $resource); } @@ -108,11 +121,16 @@ public function cannot($action, $resource) */ public function is($roles) { + if(!is_array($roles)) + { + $roles = array($roles); + } + // If the role has an aliased name then make sure we grab the correct name of // the role from the aliases array. $aliases = $this->feather['config']->get('feather: auth.aliases'); - foreach((array) $roles as $key => $role) + foreach($roles as $key => $role) { if(array_key_exists(strtolower($role), $aliases)) { @@ -120,9 +138,9 @@ public function is($roles) } } - foreach($this->user()->roles as $role) + foreach($this->user->roles as $role) { - if(in_array(strtolower($role->name), (array) $roles)) + if(in_array(strtolower($role->name), $roles)) { return true; } @@ -177,7 +195,7 @@ public function offline() */ public function activated() { - return (bool) $this->user()->activated; + return (bool) $this->user->activated; } /** From 212ef3161e0d4df8eafa2d28b1d7ff4dd6521a48 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 23:41:29 +0930 Subject: [PATCH 051/136] Initial commit of auth tests. Signed-off-by: Jason Lewis --- tests/auth.test.php | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/auth.test.php diff --git a/tests/auth.test.php b/tests/auth.test.php new file mode 100644 index 0000000..3e2f6a6 --- /dev/null +++ b/tests/auth.test.php @@ -0,0 +1,60 @@ +feather = Feather\Components\Support\Facade::application(); + + $this->feather['auth']->user = $this->user(); + } + + public function tearDown() + { + M::close(); + } + + public function testCanDoAction() + { + $this->assertTrue($this->feather['auth']->can('do: something')); + } + + public function testCannotDoAction() + { + $this->assertTrue($this->feather['auth']->cannot('view: something')); + } + + public function testCanCheckRole() + { + $this->assertTrue($this->feather['auth']->is('administrator')); + $this->assertTrue($this->feather['auth']->is('admin')); + $this->assertTrue($this->feather['auth']->not('boggyman')); + } + + public function testCanCheckOnline() + { + $this->assertTrue($this->feather['auth']->online()); + } + + public function testCanCheckActivation() + { + $this->assertTrue($this->feather['auth']->activated()); + } + + public function user() + { + return new Feather\Models\User(array( + 'roles' => array( + new Feather\Models\Role(array('do_something' => 1)), + new Feather\Models\Role(array('name' => 'Administrator')), + new Feather\Models\Role(array('view_something' => 0)) + ), + 'activated' => 1 + )); + } + +} \ No newline at end of file From 973b6bc286075f03dba618203182773414b92595 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sat, 22 Sep 2012 23:41:51 +0930 Subject: [PATCH 052/136] Refactoring of tests. Signed-off-by: Jason Lewis --- tests/config.test.php | 4 +++- tests/facade.test.php | 5 +++- tests/gear.test.php | 56 ++++++++++++++++++++++--------------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/tests/config.test.php b/tests/config.test.php index 043bbea..2c487e9 100644 --- a/tests/config.test.php +++ b/tests/config.test.php @@ -79,7 +79,9 @@ public function testItemsCanBeDeleted() private function getRepository() { - $config = new Feather\Components\Config\Repository; + $feather = Feather\Components\Support\Facade::application(); + + $config = $feather['config']; $config->set('test', $this->getItems()); diff --git a/tests/facade.test.php b/tests/facade.test.php index 37f799c..f426a89 100644 --- a/tests/facade.test.php +++ b/tests/facade.test.php @@ -6,9 +6,12 @@ class FacadeTest extends PHPUnit_Framework_TestCase { public function testFacadeCallsApplication() { - FacadeStub::application(array('stub' => new ApplicationStub)); + $feather = Feather\Components\Support\Facade::application(); + FacadeStub::application(array('stub' => new ApplicationStub)); $this->assertEquals('dog', FacadeStub::cat()); + + Feather\Components\Support\Facade::application($feather); } } diff --git a/tests/gear.test.php b/tests/gear.test.php index 7c808d8..15ac5e4 100644 --- a/tests/gear.test.php +++ b/tests/gear.test.php @@ -2,50 +2,52 @@ class GearTest extends PHPUnit_Framework_TestCase { - public $gear; - - public $manager; - - public function setUp() - { - $this->gear = new Feather\Models\Gear(array( - 'identifier' => 'stub', - 'location' => 'path: ' . __DIR__ . DS . 'mock', - 'auto' => false - )); - - $this->manager = new Feather\Components\Gear\Manager; - - $this->manager->register($this->gear); - - $this->manager->start('stub'); - } - public function testCanRegisterGear() { - $this->assertTrue($this->manager->registered('stub')); + $feather = $this->stub(); + + $this->assertTrue($feather['gear']->registered('stub')); } public function testCanStartGear() { - $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $this->manager->start('stub')); - $this->assertTrue($this->manager->started('stub')); + $feather = $this->stub(); + + $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $feather['gear']->start('stub')); + $this->assertTrue($feather['gear']->started('stub')); $this->assertInstanceOf('Feather\\Gear\\Stub\\Mock', $gear['mock']); } public function testCanDisableGear() { - $this->assertTrue($this->manager->registered('stub')); + $feather = $this->stub(); + + $this->assertTrue($feather['gear']->registered('stub')); - $this->manager->disable('stub'); + $feather['gear']->disable('stub'); - $this->assertTrue(!$this->manager->registered('stub')); + $this->assertTrue(!$feather['gear']->registered('stub')); } public function testGearEventsFire() { - $this->assertEquals('cat', Feather\Components\Gear\Manager::first('mock.callable')); - $this->assertEquals('dog', Feather\Components\Gear\Manager::first('mock.method')); + $feather = $this->stub(); + + $this->assertEquals('cat', $feather['gear']->first('mock.callable')); + $this->assertEquals('dog', $feather['gear']->first('mock.method')); + } + + public function stub() + { + $feather = Feather\Components\Support\Facade::application(); + + $feather['gear']->register(new Feather\Models\Gear(array( + 'identifier' => 'stub', + 'location' => 'path: ' . __DIR__ . DS . 'mock', + 'auto' => false + ))); + + return $feather; } } \ No newline at end of file From b09413df332a35d739fc091ac1938f0264ad4565 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 00:13:15 +0930 Subject: [PATCH 053/136] Refactoring config tests. Signed-off-by: Jason Lewis --- tests/config.test.php | 88 ++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/tests/config.test.php b/tests/config.test.php index 2c487e9..708a96c 100644 --- a/tests/config.test.php +++ b/tests/config.test.php @@ -2,93 +2,79 @@ class ConfigTest extends PHPUnit_Framework_TestCase { - public function testItemsCanBeSet() + public $feather; + + public function setUp() { - $config = $this->getRepository(); + $this->feather = Feather\Components\Support\Facade::application(); - $config->set('name', 'jason'); - $this->assertEquals('jason', $config->get('name')); + $this->feather['config']->set('test', $this->items()); + } - $config->set('person.name', 'jason'); - $this->assertEquals('jason', $config->get('person.name')); + public function testItemsCanBeSet() + { + $this->feather['config']->set('name', 'jason'); + $this->assertEquals('jason', $this->feather['config']->get('name')); - $config->set('namespace::person.name', 'jason'); - $this->assertEquals('jason', $config->get('namespace::person.name')); + $this->feather['config']->set('person.name', 'jason'); + $this->assertEquals('jason', $this->feather['config']->get('person.name')); - $config->set('gear: mock person.name', 'jason'); - $this->assertEquals('jason', $config->get('gear: mock person.name')); + $this->feather['config']->set('namespace::person.name', 'jason'); + $this->assertEquals('jason', $this->feather['config']->get('namespace::person.name')); - $config->set('theme: mock person.name', 'jason'); - $this->assertEquals('jason', $config->get('theme: mock person.name')); + $this->feather['config']->set('gear: mock person.name', 'jason'); + $this->assertEquals('jason', $this->feather['config']->get('gear: mock person.name')); + + $this->feather['config']->set('theme: mock person.name', 'jason'); + $this->assertEquals('jason', $this->feather['config']->get('theme: mock person.name')); } public function testGetBasicItems() { - $config = $this->getRepository(); - - $this->assertEquals('bar', $config->get('test.foo')); - $this->assertEquals('orange', $config->get('test.apple')); + $this->assertEquals('bar', $this->feather['config']->get('test.foo')); + $this->assertEquals('orange', $this->feather['config']->get('test.apple')); } public function testEntireArrayCanBeReturned() { - $config = $this->getRepository(); - - $this->assertEquals($this->getItems(), $config->get('test')); + $this->assertEquals($this->items(), $this->feather['config']->get('test')); } public function testCheckItemExistance() { - $config = $this->getRepository(); - - $config->set('foo', 'bar'); - $this->assertTrue($config->has('foo')); + $this->feather['config']->set('foo', 'bar'); - $this->assertTrue(!$config->has('apple')); + $this->assertTrue($this->feather['config']->has('foo')); + $this->assertTrue(!$this->feather['config']->has('apple')); } public function testItemsCanBeSaved() { - $config = $this->getRepository(); + $this->feather['config']->set('feather: db.test', 'foobar'); + $this->feather['config']->save('feather: db.test'); - $config->set('feather: db.test', 'foobar'); - $config->save('feather: db.test'); + $this->feather['config']->reload(); - $config->reload(); + $this->assertEquals('foobar', $this->feather['config']->get('feather: db.test')); - $this->assertEquals('foobar', $config->get('feather: db.test')); - - $config->delete('feather: db.test'); + $this->feather['config']->delete('feather: db.test'); } public function testItemsCanBeDeleted() { - $config = $this->getRepository(); - - $config->set('feather: db.test', 'foobar'); - $config->save('feather: db.test'); - - $config->reload(); - - $config->delete('feather: db.test'); + $this->feather['config']->set('feather: db.test', 'foobar'); + $this->feather['config']->save('feather: db.test'); - $config->reload(); - - $this->assertEquals(null, $config->get('feather: db.test')); - } - - private function getRepository() - { - $feather = Feather\Components\Support\Facade::application(); + $this->feather['config']->reload(); - $config = $feather['config']; + $this->feather['config']->delete('feather: db.test'); - $config->set('test', $this->getItems()); + $this->feather['config']->reload(); - return $config; + $this->assertEquals(null, $this->feather['config']->get('feather: db.test')); } - private function getItems() + public function items() { return array('foo' => 'bar', 'apple' => 'orange'); } From 19aaa2a4952e6d5febef4d0053011c2bd60f89a0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 00:14:44 +0930 Subject: [PATCH 054/136] Refactoring gears tests. Signed-off-by: Jason Lewis --- tests/gear.test.php | 51 +++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/tests/gear.test.php b/tests/gear.test.php index 15ac5e4..ec2d2ca 100644 --- a/tests/gear.test.php +++ b/tests/gear.test.php @@ -2,52 +2,43 @@ class GearTest extends PHPUnit_Framework_TestCase { - public function testCanRegisterGear() + public $feather; + + public function setUp() { - $feather = $this->stub(); + $this->feather = Feather\Components\Support\Facade::application(); - $this->assertTrue($feather['gear']->registered('stub')); + $this->feather['gear']->register(new Feather\Models\Gear(array( + 'identifier' => 'stub', + 'location' => 'path: ' . __DIR__ . DS . 'mock', + 'auto' => false + ))); } - public function testCanStartGear() + public function testCanRegisterGear() { - $feather = $this->stub(); + $this->assertTrue($this->feather['gear']->registered('stub')); + } - $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $feather['gear']->start('stub')); - $this->assertTrue($feather['gear']->started('stub')); + public function testCanStartGear() + { + $this->assertInstanceOf('Feather\\Components\\Gear\\Container', $gear = $this->feather['gear']->start('stub')); + $this->assertTrue($this->feather['gear']->started('stub')); $this->assertInstanceOf('Feather\\Gear\\Stub\\Mock', $gear['mock']); } public function testCanDisableGear() { - $feather = $this->stub(); - - $this->assertTrue($feather['gear']->registered('stub')); + $this->assertTrue($this->feather['gear']->registered('stub')); - $feather['gear']->disable('stub'); + $this->feather['gear']->disable('stub'); - $this->assertTrue(!$feather['gear']->registered('stub')); + $this->assertTrue(!$this->feather['gear']->registered('stub')); } public function testGearEventsFire() { - $feather = $this->stub(); - - $this->assertEquals('cat', $feather['gear']->first('mock.callable')); - $this->assertEquals('dog', $feather['gear']->first('mock.method')); + $this->assertEquals('cat', $this->feather['gear']->first('mock.callable')); + $this->assertEquals('dog', $this->feather['gear']->first('mock.method')); } - - public function stub() - { - $feather = Feather\Components\Support\Facade::application(); - - $feather['gear']->register(new Feather\Models\Gear(array( - 'identifier' => 'stub', - 'location' => 'path: ' . __DIR__ . DS . 'mock', - 'auto' => false - ))); - - return $feather; - } - } \ No newline at end of file From 40c68f645a71d06b7149c65bab991b8d03853e2a Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 00:54:37 +0930 Subject: [PATCH 055/136] Initial commit of redirector component. Signed-off-by: Jason Lewis --- components/foundation/redirector.php | 155 +++++++++++++++++++++++++++ config/feather.php | 11 +- 2 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 components/foundation/redirector.php diff --git a/components/foundation/redirector.php b/components/foundation/redirector.php new file mode 100644 index 0000000..2d0d89a --- /dev/null +++ b/components/foundation/redirector.php @@ -0,0 +1,155 @@ +with('alert', array('type' => $type, 'message' => __($key, $replacements))); + } + + /** + * Create a redirect response with the query string appended. + * + * @param string $url + * @return object + */ + public function with_query($url) + { + $url = URL::to($this->pattern($url)); + + if($query = urldecode(Request::getQueryString())) + { + $url = "{$url}?{$query}"; + } + + return $this->to($url); + } + + /** + * Create a redirect response to the current page. + * + * @return object + */ + public function to_self() + { + return $this->with_query(URI::current()); + } + + /** + * Create a redirect response to the previous page. + * + * @param string $default + * @return object + */ + public function to_previous($default = null) + { + return $this->to(Input::has('return') ? Input::get('return') : $this->pattern($default)); + } + + /** + * Create a redirect response after a logout. + * + * @return object + */ + public static function after_logout() + { + return $this->for_auth(Config::get('feather: auth.logout_url')); + } + + /** + * Create a redirect response before a registration. + * + * @return object + */ + public static function before_register() + { + return $this->for_auth(Config::get('feather: auth.register_url')); + } + + /** + * Create a redirect response before a login. + * + * @return object + */ + public static function before_login() + { + return $this->for_auth(Config::get('feather: auth.login_url')); + } + + /** + * Create a redirect response to an authentication page. + * + * @param string $url + * @return object + */ + protected function for_auth($url) + { + $replace = array( + '{feather}' => Bundle::option('feather', 'handles'), + '{current}' => URL::to(Input::has('return') ? Input::get('return') : URI::current()), + '{token}' => Auth::online() ? Auth::user()->authenticator_token : null + ); + + if(Config::get('feather: auth.driver') == 'internal') + { + $url = URL::to_route('feather'); + } + else + { + $url = str_replace(array_keys($replace), array_values($replace), $url); + } + + return $this->to($url); + } + + /** + * Builds a URL based on defined patterns. + * + * @param string $url + * @return string + */ + protected function pattern($url) + { + if(starts_with($url, 'route: ')) + { + return URL::to_route(substr($url, 7)); + } + elseif(starts_with($url, 'action: ')) + { + return URL::to_action(substr($url, 8)); + } + + return $url; + } + + /** + * Flash a Validator's errors to the session data. + * + * @param object|string $container + * @return object + */ + public function with_errors($container) + { + $errors = !($container instanceof Messages) ? new Messages(array($container)) : $container; + + return $this->with('errors', $errors); + } + +} \ No newline at end of file diff --git a/config/feather.php b/config/feather.php index 665e690..333d314 100644 --- a/config/feather.php +++ b/config/feather.php @@ -54,7 +54,7 @@ { $feather['sso'] = $feather->share(function($feather) { - return new Feather\Components\Auth\Dispactcher($feather); + return new Feather\Components\Auth\SSO($feather); }); }, 'gear' => function($feather) @@ -63,7 +63,14 @@ { return new Feather\Components\Gear\Manager($feather); }); - } + }, + 'redirect' => function($feather) + { + $feather['redirect'] = $feather->share(function($feather) + { + return new Feather\Components\Foundation\Redirector(null); + }); + }, ), /* From fe011750368c22bff07b5b7e07c29e20dc996633 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 00:55:17 +0930 Subject: [PATCH 056/136] Renamed dispatcher to SSO. Signed-off-by: Jason Lewis --- components/auth/dispatcher.php | 7 ---- components/auth/sso.php | 75 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) delete mode 100644 components/auth/dispatcher.php create mode 100644 components/auth/sso.php diff --git a/components/auth/dispatcher.php b/components/auth/dispatcher.php deleted file mode 100644 index 695869b..0000000 --- a/components/auth/dispatcher.php +++ /dev/null @@ -1,7 +0,0 @@ - null, + 'username' => null, + 'email' => null, + 'token' => null + ); + + /** + * Authorizes a user from an external application. + * + * @param array $credentials + * @return mixed + */ + public function authorize(array $credentials) + { + $credentials = array_merge($this->defaults, $credentials); + + // If the current request is POST then the user is either registering or + // connecting an account. + if(Request::method() == 'POST') + { + $method = Input::has('create') ? 'register' : 'connect'; + + return $this->{$method}($credentials); + } + + extract($credentials); + + // If a user exists with an associated e-mail address that matches then this user has already + // been authenticated with a SSO service. + if($user = User::where_authenticator_associated_email($email)->first()) + { + // An authenticators token may change from time to time. If the tokens are no longer + // the same then the token must be updated. + if($user->authenticator_token != $token) + { + $user->authenticator_token = $token; + + $user->save(); + } + + $this->feather['auth']->login($user); + + return $this->feather['redirect']->to_self(); + } + + // If there is a user in the database who has the same e-mail or username as our + // user then they need to either link their account or create a new one. + elseif($user = User::where('username', '=', $username)->or_where('email', '=', $email)->first()) + { + Breadcrumbs::drop(__('feather::titles.connect_to_community')); + + return View::of('layout')->with('title', __('feather::titles.connect_to_community')) + ->nest('content', 'feather::user.associate', compact('user')); + } + + // If there is no user in the database then the associated user is our actual user. Does + // that make sense? Well... we basically just create them a new account. + return static::create($credentials, $credentials); + } + +} \ No newline at end of file From 93a8720913a0e0474542aa83e27a6c684fc259cf Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:00:44 +0930 Subject: [PATCH 057/136] Initial commit of exceptions. Signed-off-by: Jason Lewis --- exceptions.php | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ start.php | 11 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 exceptions.php diff --git a/exceptions.php b/exceptions.php new file mode 100644 index 0000000..c45d00d --- /dev/null +++ b/exceptions.php @@ -0,0 +1,69 @@ +errors = $errors; + } + + /** + * Get the errors. + * + * @return array + */ + public function get() + { + return $this->errors; + } + +} + +/* +|-------------------------------------------------------------------------- +| Feather Model Exception +|-------------------------------------------------------------------------- +| +| The exception class for Feather models. +| +*/ + +class FeatherModelException extends Exception {} + +/* +|-------------------------------------------------------------------------- +| Auth Exception +|-------------------------------------------------------------------------- +| +| The exception class for Feather's Authentication. +| +*/ + +class AuthException extends Exception {} \ No newline at end of file diff --git a/start.php b/start.php index 5cf8465..742a349 100644 --- a/start.php +++ b/start.php @@ -23,6 +23,17 @@ set_path('themes', path('feather') . 'themes' . DS); +/* +|-------------------------------------------------------------------------- +| Feather Exceptions +|-------------------------------------------------------------------------- +| +| Load in Feather's Exception Classes +| +*/ + +require path('feather') . 'exceptions' . EXT; + /* |-------------------------------------------------------------------------- | Core Autoloading From d98b45989ef92597897475e1d73d5a3830ebb316 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:01:00 +0930 Subject: [PATCH 058/136] Added some more facades. Signed-off-by: Jason Lewis --- facades.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/facades.php b/facades.php index 966bc7c..011ee46 100644 --- a/facades.php +++ b/facades.php @@ -42,4 +42,37 @@ class Gear extends Components\Support\Facade { */ protected static function accessor(){ return 'gear'; } +} + +class Crumbs extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'crumbs'; } + +} + +class Redirect extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'redirect'; } + +} + +class Validator extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'validator'; } + } \ No newline at end of file From af3284e6e98dc12a86606555168058969e75587d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:01:24 +0930 Subject: [PATCH 059/136] Initial commit of crumbs component. Signed-off-by: Jason Lewis --- components/support/crumbs.php | 103 ++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 components/support/crumbs.php diff --git a/components/support/crumbs.php b/components/support/crumbs.php new file mode 100644 index 0000000..58766af --- /dev/null +++ b/components/support/crumbs.php @@ -0,0 +1,103 @@ +place($crumb); + } + else + { + // If the crumb is an object, such as a Laravel Messages object, then convert it + // to a string as any objects being passed to this method will have a __toString() + if(is_object($crumb)) + { + $crumb = array((string) $crumb); + } + + // Fetch the title of the crumb and the crumbs link. If the crumb does not have + // a supplied link then use the current page. + $title = isset($crumb['title']) ? $crumb['title'] : array_shift($crumb); + + $link = isset($crumb['link']) ? $crumb['link'] : URL::current(); + + $this->crumbs[] = (object) compact('link', 'title'); + } + } + + /** + * Leaves a trail of crumbs. + * + * @return string + */ + public function trail($element = 'li') + { + $response = array( + $this->item(URL::to_route('feather'), $this->feather['config']->get('feather: db.forum.title'), $element) + ); + + foreach($this->crumbs as $crumb) + { + $response[] = $this->item($crumb->link, $crumb->title, $element); + } + + return implode(PHP_EOL, $response); + } + + /** + * Adds a place to the trail. + * + * @param Feather\Models\Place $place + * @return array + */ + protected function place($place) + { + // Add each of the ancestors of this place to the crumbs. + foreach($place->crumbs() as $crumb) + { + $this->crumbs[] = array( + 'link' => URL::to_route('place', array($crumb->id, $crumb->slug)), + 'title' => $crumb->name + ); + } + + return $this->crumbs[] = array( + 'link' => URL::to_route('place', array($place->id, $place->slug)), + 'title' => $place->name + ); + } + + /** + * Return a list item with the link. + * + * @param string $link + * @param string $text + * @param string $element + * @return string + */ + public function item($link, $text, $element = 'li') + { + return "<{$element}>" . HTML::link($link, $text) . ""; + } + +} \ No newline at end of file From 48bf9ce3446e86e96b4e9740f4a1af1d8f5dfed0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:01:59 +0930 Subject: [PATCH 060/136] Initial commit of validation component. Signed-off-by: Jason Lewis --- components/support/validation.php | 185 ++++++++++++++++++++++++++++++ config/validation.php | 5 + 2 files changed, 190 insertions(+) create mode 100644 components/support/validation.php create mode 100644 config/validation.php diff --git a/components/support/validation.php b/components/support/validation.php new file mode 100644 index 0000000..a414fbb --- /dev/null +++ b/components/support/validation.php @@ -0,0 +1,185 @@ +application = $application; + + return $this; + } + + /** + * Get a validator. + * + * @param string $validator + * @return Feather\Components\Support\Validation + */ + public function get($validator) + { + if(!$this->validating[$validator] = $this->feather['config']->get("feather: validation.{$validator}")) + { + throw new InvalidArgumentException("Invalid validator [{$validator}] supplied."); + } + + return $this; + } + + /** + * Sets input to validate against. + * + * @param array $input + * @return Feather\Components\Support\Validation + */ + public function against($input) + { + $this->input = $input; + + return $this; + } + + /** + * Executes validation and returns true on success. + * + * @return bool + */ + public function passes() + { + // If a response is returned from the closure then we have a custom error messages + // to throw with the exception. + if($responses = call_user_func(reset($this->validating), $this)) + { + if(!is_array(reset($responses))) $responses = array(array_shift($responses) => array()); + + foreach($responses as $response => $replacements) + { + $responses[$response] = __("{$this->application}::{$response}", $replacements)->get(); + } + + throw new FeatherValidationException(new Messages($responses)); + } + + $event = key($this->validating); + + $this->feather['gear']->fire("validation: before {$event}", array($this)); + + $validator = new Validator($this->input, $this->rules, $this->messages); + + if($validator->connection(DB::connection(FEATHER_DATABASE))->fails()) + { + throw new FeatherValidationException($validator->errors); + } + + return true; + } + + /** + * Adds a rule or array of rules to the rules array. + * + * @param string $name + * @param array|string $rule + * @return Feather\Components\Support\Validation + */ + public function rule($name, $rule) + { + if(!isset($this->rules[$name])) + { + $this->rules[$name] = array(); + } + + $this->rules[$name] = array_merge($this->rules[$name], (array) $rule); + + return $this; + } + + /** + * Adds a message to the messages array. + * + * @param string $rule + * @param array|string $message + * @return Feather\Components\Support\Validation + */ + public function message($rule, $message) + { + $replacements = array(); + + if(is_array($message)) + { + list($message, $replacements) = $message; + } + + $this->messages[str_replace('.', '_', $rule)] = __("{$this->application}::{$message}", $replacements)->get(); + + return $this; + } + + /** + * Adds a data key/value pair to the data array. + * + * @param string $key + * @param mixed $value + * @return Feather\Components\Support\Validation + */ + public function with($key, $value) + { + $this->data[$key] = $value; + + return $this; + } + +} \ No newline at end of file diff --git a/config/validation.php b/config/validation.php new file mode 100644 index 0000000..207cc1e --- /dev/null +++ b/config/validation.php @@ -0,0 +1,5 @@ + Date: Sun, 23 Sep 2012 02:02:24 +0930 Subject: [PATCH 061/136] Added the new components to be loaded at runtime. Signed-off-by: Jason Lewis --- config/feather.php | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/config/feather.php b/config/feather.php index 333d314..f923e5b 100644 --- a/config/feather.php +++ b/config/feather.php @@ -31,6 +31,20 @@ 'driver' => 'mysql' ), + /* + |-------------------------------------------------------------------------- + | Feather Applications + |-------------------------------------------------------------------------- + | + | Applications to be registered at runtime with Feather. It is advised you + | do not edit anything down there. + | + */ + + 'applications' => array( + 'admin' => '(:feather)/admin', + 'core' => '(:feather)' + ), /* |-------------------------------------------------------------------------- @@ -71,21 +85,19 @@ return new Feather\Components\Foundation\Redirector(null); }); }, + 'crumbs' => function($feather) + { + $feather['crumbs'] = $feather->share(function($feather) + { + return new Feather\Components\Support\Crumbs($feather); + }); + }, + 'validator' => function($feather) + { + $feather['validator'] = $feather->share(function($feather) + { + return new Feather\Components\Support\Validation($feather); + }); + } ), - - /* - |-------------------------------------------------------------------------- - | Feather Applications - |-------------------------------------------------------------------------- - | - | Applications to be registered at runtime with Feather. It is advised you - | do not edit anything down there. - | - */ - - 'applications' => array( - 'admin' => '(:feather)/admin', - 'core' => '(:feather)' - ), - ); \ No newline at end of file From de4bcd64a82acc39a16a04e22d106132c73dce73 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:03:51 +0930 Subject: [PATCH 062/136] Refactoring auth tests. Signed-off-by: Jason Lewis --- tests/auth.test.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/auth.test.php b/tests/auth.test.php index 3e2f6a6..6b39da9 100644 --- a/tests/auth.test.php +++ b/tests/auth.test.php @@ -1,7 +1,5 @@ feather['auth']->user = $this->user(); } - public function tearDown() - { - M::close(); - } - public function testCanDoAction() { $this->assertTrue($this->feather['auth']->can('do: something')); From 314ee2a8ec50b603175b9981bb68ee589bbb11b0 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:04:49 +0930 Subject: [PATCH 063/136] Fixed bug with crumbs, method was not meant to be static. Signed-off-by: Jason Lewis --- components/support/crumbs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/support/crumbs.php b/components/support/crumbs.php index 58766af..5b6e3f1 100644 --- a/components/support/crumbs.php +++ b/components/support/crumbs.php @@ -20,7 +20,7 @@ class Crumbs extends Component { * @param array|object $crumb * @return void */ - public static function drop($crumb) + public function drop($crumb) { if($crumb instanceof Models\Place) { From 66a8a16a3a1ba9023576f04bed90c0777d435b41 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:11:47 +0930 Subject: [PATCH 064/136] Initial commit of crumbs tests. Signed-off-by: Jason Lewis --- tests/crumbs.test.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/crumbs.test.php diff --git a/tests/crumbs.test.php b/tests/crumbs.test.php new file mode 100644 index 0000000..478aa9e --- /dev/null +++ b/tests/crumbs.test.php @@ -0,0 +1,24 @@ +feather = Feather\Components\Support\Facade::application(); + } + + public function testCanDropCrumb() + { + $this->feather['crumbs']->drop('stub'); + + $this->assertNotEmpty($this->feather['crumbs']->crumbs); + } + + public function testCanGetHTML() + { + $this->assertEquals('
  • stub
  • ', $this->feather['crumbs']->item('/', 'stub')); + } + +} From 49a3f634ad15f96af77d8c243f905c39202ed04c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:41:02 +0930 Subject: [PATCH 065/136] Initial commit of validation tests. Signed-off-by: Jason Lewis --- tests/validation.test.php | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/validation.test.php diff --git a/tests/validation.test.php b/tests/validation.test.php new file mode 100644 index 0000000..ba077fc --- /dev/null +++ b/tests/validation.test.php @@ -0,0 +1,88 @@ +feather = Feather\Components\Support\Facade::application(); + } + + public function tearDown() + { + $this->feather['validator']->app('core'); + + $this->feather['validator']->rules = array(); + + $this->feather['validator']->messages = array(); + } + + public function testCanChangeApplication() + { + $this->feather['validator']->app('cat'); + + $this->assertEquals('cat', $this->feather['validator']->application); + } + + public function testCanValidateAgainstInput() + { + $this->feather['validator']->against(array('foo' => 'bar')); + + $this->assertEquals(array('foo' => 'bar'), $this->feather['validator']->input); + } + + public function testCanSetCustomData() + { + $this->feather['validator']->with('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $this->feather['validator']->data); + } + + public function testCanLoadValidatorClosure() + { + $this->feather['config']->set('feather: validation.test', function($validator){}); + + $this->feather['validator']->get('test'); + + $this->assertArrayHasKey('test', $this->feather['validator']->validating); + } + + public function testCanAddRule() + { + $this->feather['validator']->rule('cat', 'dog'); + + $this->assertEquals(array('cat' => array('dog')), $this->feather['validator']->rules); + } + + public function testCanAddMessage() + { + $this->feather['validator']->message('cat', 'dog'); + + $this->assertEquals(array('cat' => 'core::dog'), $this->feather['validator']->messages); + } + + public function testValidationDoesPass() + { + $this->feather['config']->set('feather: validation.test', function($validator){ + $validator->rule('cat', 'required'); + }); + + $this->assertTrue($this->feather['validator']->get('test')->against(array('cat' => 'dog'))->passes()); + } + + public function testValidationExceptionIsThrown() + { + $this->feather['config']->set('feather: validation.test', function($validator){ + $validator->rule('cat', 'required'); + }); + + try { + $this->feather['validator']->get('test')->against(array())->passes(); + } + catch (FeatherValidationException $exception) {} + + $this->assertInstanceOf('FeatherValidationException', $exception); + } + +} From d110df92fdcba3d3c9d4f2cfccf10e931ef505bf Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:57:50 +0930 Subject: [PATCH 066/136] Initial commit of date component. Signed-off-by: Jason Lewis --- components/support/date.php | 203 ++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 components/support/date.php diff --git a/components/support/date.php b/components/support/date.php new file mode 100644 index 0000000..ca6cb19 --- /dev/null +++ b/components/support/date.php @@ -0,0 +1,203 @@ + array('second', 'minute', 'hour', 'day', 'week', 'month', 'year'), + 'lengths' => array(60, 60, 24, 7, 4.35, 12) + ); + + /** + * Alternate text to show if date is invalid. + * + * @var string + */ + public $alternate; + + /** + * Text to show before the returned formatted string. + * + * @var string + */ + public $prefix; + + /** + * Text to show after the returned formatted string. + * + * @var string + */ + public $suffix; + + /** + * The DateTime being used for the current instance. + * + * @var object + */ + public $date; + + /** + * Set the date to use. + * + * @param int|string $date + * @return Feather\Components\Support\Date + */ + public function set($date) + { + $this->date = new DateTime; + + if(is_numeric($date)) + { + $this->date->setTimestamp($date); + } + else + { + $this->date->setTimestamp(strtotime($date)); + } + + return $this; + } + + /** + * Format a date and return it for the discussion meta data. + * + * @param string|int $date + * @return string + */ + public function meta($date) + { + return '' . $date->relative('short') . ''; + } + + /** + * Sets the alternate text to display if the date is of an invalid format. + * + * @param string $text + * @return Feather\Components\Support\Date + */ + public function alternate($text) + { + $this->alternate = $text; + + return $this; + } + + /** + * Sets the prefix text. + * + * @param string $text + * @return Feather\Components\Support\Date + */ + public function prefix($text) + { + $this->prefix = $text; + + return $this; + } + + /** + * Sets the suffix text. + * + * @param string $text + * @return Feather\Components\Support\Date + */ + public function suffix($text) + { + $this->suffix = $text; + + return $this; + } + + /** + * Returns the formatted date. + * + * @param string $format + * @return string + */ + public function show($format) + { + $formats = array( + 'long' => $this->feather['config']->get('feather: db.datetime.long_date'), + 'short' => $this->feather['config']->get('feather: db.datetime.short_date'), + 'time' => $this->feather['config']->get('feather: db.datetime.time_only') + ); + + if(isset($formats[$format])) + { + $format = $formats[$format]; + } + + $errors = $this->date->getLastErrors(); + + if($this->date->getTimestamp() == 0 or $errors['warning_count'] > 0 or $errors['error_count'] > 0) + { + return $this->alternate ?: 'Invalid date supplied.'; + } + + return $this->prefix . $this->date->format($format) . $this->suffix; + } + + /** + * Returns a fuzzy formated date. + * + * @return string + */ + public function fuzzy($stop = 'week', $format = 'long') + { + $difference = time() - $this->date->getTimestamp(); + + $offset = array_search($stop, static::$fuzzy['periods']) + 1; + + $periods = array_slice(static::$fuzzy['periods'], 0, $offset); + + $lengths = array_slice(static::$fuzzy['lengths'], 0, $offset); + + for($i = 0; $difference >= $lengths[$i] and $i < count($lengths) - 1; $i++) + { + $difference = $difference / $lengths[$i]; + } + + $difference = round($difference); + + if($difference != 1) + { + $periods[$i] .= 's'; + } + + if($difference > $lengths[$i]) + { + return $this->show($format); + } + + return $this->prefix . number_format($difference) . ' ' . $periods[$i] . ' ago' . $this->suffix; + } + + /** + * Returns a relative formated date. + * + * @return string + */ + public function relative($format = 'long') + { + $days = intval((time() - $this->date->getTimestamp()) / 86400); + + if($days == 0) + { + return __('feather::common.today')->get() . ', ' . $this->show('time'); + } + elseif($days == 1) + { + return __('feather::common.yesterday')->get() . ', ' . $this->show('time'); + } + + return $this->show($format); + } + +} \ No newline at end of file From 8df0ab0945c140bbb4518bf0de4aca4020b6db78 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 02:58:03 +0930 Subject: [PATCH 067/136] Initial commit of date component. Signed-off-by: Jason Lewis --- config/feather.php | 7 +++++++ facades.php | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/config/feather.php b/config/feather.php index f923e5b..7f4aeaf 100644 --- a/config/feather.php +++ b/config/feather.php @@ -98,6 +98,13 @@ { return new Feather\Components\Support\Validation($feather); }); + }, + 'date' => function($feather) + { + $feather['date'] = $feather->share(function($feather) + { + return new Feather\Components\Support\Date($feather); + }); } ), ); \ No newline at end of file diff --git a/facades.php b/facades.php index 011ee46..450b92e 100644 --- a/facades.php +++ b/facades.php @@ -75,4 +75,15 @@ class Validator extends Components\Support\Facade { */ protected static function accessor(){ return 'validator'; } +} + +class Date extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'date'; } + } \ No newline at end of file From eb37098518dc5c5d5be5c7920c36b64125891f37 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 03:16:41 +0930 Subject: [PATCH 068/136] Date component is no longer shared. Signed-off-by: Jason Lewis --- config/feather.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/feather.php b/config/feather.php index 7f4aeaf..b569203 100644 --- a/config/feather.php +++ b/config/feather.php @@ -101,10 +101,10 @@ }, 'date' => function($feather) { - $feather['date'] = $feather->share(function($feather) + $feather['date'] = function($feather) { return new Feather\Components\Support\Date($feather); - }); + }; } ), ); \ No newline at end of file From ee66c8aed55286ffa823bd687ee6a7a3c2f7fd00 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 03:17:03 +0930 Subject: [PATCH 069/136] Refining the date tests. Signed-off-by: Jason Lewis --- tests/date.test.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/date.test.php diff --git a/tests/date.test.php b/tests/date.test.php new file mode 100644 index 0000000..1944d19 --- /dev/null +++ b/tests/date.test.php @@ -0,0 +1,42 @@ +feather = Feather\Components\Support\Facade::application(); + } + + public function testSetDateAsTimestamp() + { + $this->assertEquals('31/01/1991', $this->feather['date']->set(strtotime('31 January 1991'))->show('d/m/Y')); + } + + public function testSetDateAsString() + { + $this->assertEquals('31/01/1991', $this->feather['date']->set('31 January 1991')->show('d/m/Y')); + } + + public function testCanSetAlternateString() + { + $this->assertEquals('cat', $this->feather['date']->set('dog')->alternate('cat')->show('d/m/Y')); + } + + public function testCanSetPrefixString() + { + $this->assertEquals('cat31/01/1991', $this->feather['date']->set('31 January 1991')->prefix('cat')->show('d/m/Y')); + } + + public function testCanSetSuffixString() + { + $this->assertEquals('31/01/1991cat', $this->feather['date']->set('31 January 1991')->suffix('cat')->show('d/m/Y')); + } + + public function testCanGetFuzzyDate() + { + $this->assertEquals('1 day ago', $this->feather['date']->set('Yesterday')->fuzzy()); + } + +} From 3b59e49c7ee2373b8f8580a94a429f8336c5ce3a Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 03:17:25 +0930 Subject: [PATCH 070/136] Fixed a couple bugs with the date component. Signed-off-by: Jason Lewis --- components/support/date.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/support/date.php b/components/support/date.php index ca6cb19..113bc9a 100644 --- a/components/support/date.php +++ b/components/support/date.php @@ -153,11 +153,11 @@ public function fuzzy($stop = 'week', $format = 'long') { $difference = time() - $this->date->getTimestamp(); - $offset = array_search($stop, static::$fuzzy['periods']) + 1; + $offset = array_search($stop, $this->fuzzy['periods']) + 1; - $periods = array_slice(static::$fuzzy['periods'], 0, $offset); + $periods = array_slice($this->fuzzy['periods'], 0, $offset); - $lengths = array_slice(static::$fuzzy['lengths'], 0, $offset); + $lengths = array_slice($this->fuzzy['lengths'], 0, $offset); for($i = 0; $difference >= $lengths[$i] and $i < count($lengths) - 1; $i++) { From acf397fac0314aef1bcedd2c0047c050dacf793c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 12:48:34 +0930 Subject: [PATCH 071/136] Refactoring the date tests. Signed-off-by: Jason Lewis --- tests/date.test.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/date.test.php b/tests/date.test.php index 1944d19..2925336 100644 --- a/tests/date.test.php +++ b/tests/date.test.php @@ -9,27 +9,27 @@ public function setUp() $this->feather = Feather\Components\Support\Facade::application(); } - public function testSetDateAsTimestamp() + public function testDateSetAsTimestamp() { $this->assertEquals('31/01/1991', $this->feather['date']->set(strtotime('31 January 1991'))->show('d/m/Y')); } - public function testSetDateAsString() + public function testDateSetAsString() { $this->assertEquals('31/01/1991', $this->feather['date']->set('31 January 1991')->show('d/m/Y')); } - public function testCanSetAlternateString() + public function testAlternateTextOnError() { $this->assertEquals('cat', $this->feather['date']->set('dog')->alternate('cat')->show('d/m/Y')); } - public function testCanSetPrefixString() + public function testCanPrefixDate() { $this->assertEquals('cat31/01/1991', $this->feather['date']->set('31 January 1991')->prefix('cat')->show('d/m/Y')); } - public function testCanSetSuffixString() + public function testCanSuffixDate() { $this->assertEquals('31/01/1991cat', $this->feather['date']->set('31 January 1991')->suffix('cat')->show('d/m/Y')); } From 5be3203da757743188134484eda5a0b7043d6bfc Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 12:51:03 +0930 Subject: [PATCH 072/136] Moved the redirector into the support components. Signed-off-by: Jason Lewis --- components/{foundation => support}/redirector.php | 2 +- config/feather.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename components/{foundation => support}/redirector.php (98%) diff --git a/components/foundation/redirector.php b/components/support/redirector.php similarity index 98% rename from components/foundation/redirector.php rename to components/support/redirector.php index 2d0d89a..a05fd67 100644 --- a/components/foundation/redirector.php +++ b/components/support/redirector.php @@ -1,4 +1,4 @@ - function($feather) { - $feather['redirect'] = $feather->share(function($feather) + $feather['redirect'] = function($feather) { - return new Feather\Components\Foundation\Redirector(null); - }); + return new Feather\Components\Support\Redirector(null); + }; }, 'crumbs' => function($feather) { From 2a0784c39d9e1debe6c20b6eb2eb2c3bbe0718f8 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 12:53:15 +0930 Subject: [PATCH 073/136] Refactoring facade tests. Signed-off-by: Jason Lewis --- tests/facade.test.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/facade.test.php b/tests/facade.test.php index f426a89..e40bfe9 100644 --- a/tests/facade.test.php +++ b/tests/facade.test.php @@ -1,7 +1,5 @@ Date: Sun, 23 Sep 2012 13:13:49 +0930 Subject: [PATCH 074/136] Renamed crumbs to breadcrumbs. Signed-off-by: Jason Lewis --- components/support/{crumbs.php => breadcrumbs.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename components/support/{crumbs.php => breadcrumbs.php} (98%) diff --git a/components/support/crumbs.php b/components/support/breadcrumbs.php similarity index 98% rename from components/support/crumbs.php rename to components/support/breadcrumbs.php index 5b6e3f1..1ee2f93 100644 --- a/components/support/crumbs.php +++ b/components/support/breadcrumbs.php @@ -5,7 +5,7 @@ use Feather\Models; use Feather\Components\Foundation\Component; -class Crumbs extends Component { +class Breadcrumbs extends Component { /** * Crumbs to be dropped on the trail. From ee9e734b31c2fe86ec7ec3cb448427a49cdb9b66 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 13:14:22 +0930 Subject: [PATCH 075/136] Moved the validation component. Signed-off-by: Jason Lewis --- .../{support/validation.php => validation/validator.php} | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename components/{support/validation.php => validation/validator.php} (95%) diff --git a/components/support/validation.php b/components/validation/validator.php similarity index 95% rename from components/support/validation.php rename to components/validation/validator.php index a414fbb..d93e6a8 100644 --- a/components/support/validation.php +++ b/components/validation/validator.php @@ -1,13 +1,12 @@ -feather['gear']->fire("validation: before {$event}", array($this)); - $validator = new Validator($this->input, $this->rules, $this->messages); + $validator = new \Validator($this->input, $this->rules, $this->messages); if($validator->connection(DB::connection(FEATHER_DATABASE))->fails()) { From 3923182075c40a4d75f72d53d77506f30538b9fc Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 13:15:11 +0930 Subject: [PATCH 076/136] Initial commit of pagination component. Signed-off-by: Jason Lewis --- components/pagination/paginator.php | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 components/pagination/paginator.php diff --git a/components/pagination/paginator.php b/components/pagination/paginator.php new file mode 100644 index 0000000..4b961cd --- /dev/null +++ b/components/pagination/paginator.php @@ -0,0 +1,55 @@ + $last = ceil($total / $per_page)) + { + return ($last > 0) ? $last : 1; + } + + return (static::valid($page)) ? $page : 1; + } + + /** + * Create a HTML page link. + * + * @param int $page + * @param string $text + * @param string $class + * @return string + */ + protected function link($page, $text, $class) + { + $page = "p{$page}"; + + if(preg_match('/p([0-9]+)/', $page)) + { + $uri = preg_replace('/\/p([0-9]+)/', '', URI::current(), 1); + } + + return HTML::link($uri . '/' . $page, $text, compact('class'), Request::secure()); + } + +} \ No newline at end of file From 4fa2f53a1bd32f92c16169acc89e6d3952f68f2e Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 13:15:24 +0930 Subject: [PATCH 077/136] Refactoring of facades and components. Signed-off-by: Jason Lewis --- config/feather.php | 15 +++++++++++---- facades.php | 11 +++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/config/feather.php b/config/feather.php index 471b987..6f2ff22 100644 --- a/config/feather.php +++ b/config/feather.php @@ -89,15 +89,15 @@ { $feather['crumbs'] = $feather->share(function($feather) { - return new Feather\Components\Support\Crumbs($feather); + return new Feather\Components\Support\Breadcrumbs($feather); }); }, 'validator' => function($feather) { - $feather['validator'] = $feather->share(function($feather) + $feather['validator'] = function($feather) { - return new Feather\Components\Support\Validation($feather); - }); + return new Feather\Components\Validation\Validator($feather); + }; }, 'date' => function($feather) { @@ -105,6 +105,13 @@ { return new Feather\Components\Support\Date($feather); }; + }, + 'paginator' => function($feather) + { + $feather['paginator'] = function($feather) + { + return new Feather\Components\Pagination\Paginator($feather); + }; } ), ); \ No newline at end of file diff --git a/facades.php b/facades.php index 450b92e..afb6606 100644 --- a/facades.php +++ b/facades.php @@ -86,4 +86,15 @@ class Date extends Components\Support\Facade { */ protected static function accessor(){ return 'date'; } +} + +class Paginator extends Components\Support\Facade { + + /** + * Gets the name of the facade component. + * + * @return string + */ + protected static function accessor(){ return 'paginator'; } + } \ No newline at end of file From 58f8ab7d89401c725b6c800109292bf0d9c8b02f Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 13:29:48 +0930 Subject: [PATCH 078/136] Missed the controller method when porting. Signed-off-by: Jason Lewis --- components/gear/manager.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/gear/manager.php b/components/gear/manager.php index cee4ee2..5317c6b 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -1,6 +1,8 @@ method); + + return Event::first("controller: {$event} {$route->controller}@{$method}.{$route->controller_action}", array(array($controller))); + } + } \ No newline at end of file From 173fee196293fc2fe2cf325c98830498d1d120ff Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 14:58:43 +0930 Subject: [PATCH 079/136] Fixed problem with fuzzy date test. Signed-off-by: Jason Lewis --- tests/date.test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/date.test.php b/tests/date.test.php index 2925336..c6a43d7 100644 --- a/tests/date.test.php +++ b/tests/date.test.php @@ -36,7 +36,7 @@ public function testCanSuffixDate() public function testCanGetFuzzyDate() { - $this->assertEquals('1 day ago', $this->feather['date']->set('Yesterday')->fuzzy()); + $this->assertEquals('0 seconds ago', $this->feather['date']->set('Now')->fuzzy()); } } From 7a812aacbc720adb12b7059b854efaa60e2dfd40 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 14:58:59 +0930 Subject: [PATCH 080/136] Fixed issues with the validation tests. Signed-off-by: Jason Lewis --- tests/validation.test.php | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/tests/validation.test.php b/tests/validation.test.php index ba077fc..d7ed7b1 100644 --- a/tests/validation.test.php +++ b/tests/validation.test.php @@ -9,57 +9,48 @@ public function setUp() $this->feather = Feather\Components\Support\Facade::application(); } - public function tearDown() - { - $this->feather['validator']->app('core'); - - $this->feather['validator']->rules = array(); - - $this->feather['validator']->messages = array(); - } - public function testCanChangeApplication() { - $this->feather['validator']->app('cat'); + $validator = $this->feather['validator']->app('cat'); - $this->assertEquals('cat', $this->feather['validator']->application); + $this->assertEquals('cat', $validator->application); } public function testCanValidateAgainstInput() { - $this->feather['validator']->against(array('foo' => 'bar')); + $validator = $this->feather['validator']->against(array('foo' => 'bar')); - $this->assertEquals(array('foo' => 'bar'), $this->feather['validator']->input); + $this->assertEquals(array('foo' => 'bar'), $validator->input); } public function testCanSetCustomData() { - $this->feather['validator']->with('foo', 'bar'); + $validator = $this->feather['validator']->with('foo', 'bar'); - $this->assertEquals(array('foo' => 'bar'), $this->feather['validator']->data); + $this->assertEquals(array('foo' => 'bar'), $validator->data); } public function testCanLoadValidatorClosure() { $this->feather['config']->set('feather: validation.test', function($validator){}); - $this->feather['validator']->get('test'); + $validator = $this->feather['validator']->get('test'); - $this->assertArrayHasKey('test', $this->feather['validator']->validating); + $this->assertArrayHasKey('test', $validator->validating); } public function testCanAddRule() { - $this->feather['validator']->rule('cat', 'dog'); + $validator = $this->feather['validator']->rule('cat', 'dog'); - $this->assertEquals(array('cat' => array('dog')), $this->feather['validator']->rules); + $this->assertEquals(array('cat' => array('dog')), $validator->rules); } public function testCanAddMessage() { - $this->feather['validator']->message('cat', 'dog'); + $validator = $this->feather['validator']->message('cat', 'dog'); - $this->assertEquals(array('cat' => 'core::dog'), $this->feather['validator']->messages); + $this->assertEquals(array('cat' => 'core::dog'), $validator->messages); } public function testValidationDoesPass() From 5c8143f39407b006fb088fd83892ae823d61127f Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 15:13:49 +0930 Subject: [PATCH 081/136] Initial commit of views bootstrapping. Signed-off-by: Jason Lewis --- bootstrap/views.php | 108 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 bootstrap/views.php diff --git a/bootstrap/views.php b/bootstrap/views.php new file mode 100644 index 0000000..a9598f6 --- /dev/null +++ b/bootstrap/views.php @@ -0,0 +1,108 @@ +bundle('feather/themes/' . $feather['config']->get('feather: db.forum.theme', 'default')); + +set_path('theme', path('themes') . $feather['config']->get('feather: db.forum.theme', 'default') . DS); + +Bundle::register('feather theme', array( + 'location' => 'path: ' . path('theme') +)); + +/* +|-------------------------------------------------------------------------- +| View Event Override +|-------------------------------------------------------------------------- +| +| Feather views behave differently to standard Laravel views. Templates +| have the ability to override views found in applications. This allows +| templates to provide custom structuring depending on the features the +| template offers. +| +| To enable this functionality Feather must override the event loader of +| view files. +| +*/ + +Event::override(View::loader, function($bundle, $view) +{ + if(!str_contains($bundle, ' ')) + { + $bundle = "feather {$bundle}"; + } + + $path = Bundle::path($bundle) . 'views'; + + if(!is_null(View::file($bundle, $view, path('theme') . 'views'))) + { + $path = path('theme') . 'views'; + } + + if(str_contains($view, ': ')) + { + list($directory, $view) = explode(': ', $view); + + list($name, $view) = explode(' ', $view); + + if(!is_null(View::file($bundle, $view, path('feather') . Str::plural($directory) . DS . $name . DS . 'views'))) + { + $path = path('feather') . Str::plural($directory) . DS . $name . DS . 'views'; + } + } + + return View::file($bundle, $view, $path); +}); + +/* +|-------------------------------------------------------------------------- +| Global Feather Variable +|-------------------------------------------------------------------------- +| +| The feather global view variable is shared across all views. It contains +| some basic information about the forum as well as the currently logged in +| user. +| +*/ + +Event::listen('auth: started', function() use ($feather) +{ + $user = array(); + + if($feather['auth']->online()) + { + $user = $feather['auth']->user(); + } + + View::share('feather', (object) array_merge($feather['config']->get('feather: db.forum'), array('user' => (object) $user))); +}); + +/* +|-------------------------------------------------------------------------- +| Default Variable Assignment +|-------------------------------------------------------------------------- +| +| Some variables are used almost always inside a template. To ensure these +| varaiables are always set we'll set them if they are not currently set. +| +*/ + +$defaults = function($view) +{ + if(!isset($view->title)) $view->title = 'Index'; + + if(!isset($view->alert)) $view->alert = null; +}; + +View::composer('feather core::template', $defaults); +View::composer('feather admin::template', $defaults); \ No newline at end of file From e3df7121ba930d1845e500c4a149692ea213f35b Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 15:14:15 +0930 Subject: [PATCH 082/136] Moved the exceptions file into the bootstrap directory. Signed-off-by: Jason Lewis --- exceptions.php => bootstrap/exceptions.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exceptions.php => bootstrap/exceptions.php (100%) diff --git a/exceptions.php b/bootstrap/exceptions.php similarity index 100% rename from exceptions.php rename to bootstrap/exceptions.php From 0eeacabdb460a27bdcf374728683868e208da00d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 15:14:33 +0930 Subject: [PATCH 083/136] Fixed a couple issues with the facades and incorrect naming. Signed-off-by: Jason Lewis --- config/feather.php | 4 ++-- facades.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/feather.php b/config/feather.php index 6f2ff22..ff02cbf 100644 --- a/config/feather.php +++ b/config/feather.php @@ -85,9 +85,9 @@ return new Feather\Components\Support\Redirector(null); }; }, - 'crumbs' => function($feather) + 'breadcrumbs' => function($feather) { - $feather['crumbs'] = $feather->share(function($feather) + $feather['breadcrumbs'] = $feather->share(function($feather) { return new Feather\Components\Support\Breadcrumbs($feather); }); diff --git a/facades.php b/facades.php index afb6606..16e4b1f 100644 --- a/facades.php +++ b/facades.php @@ -44,14 +44,14 @@ protected static function accessor(){ return 'gear'; } } -class Crumbs extends Components\Support\Facade { +class Breadcrumbs extends Components\Support\Facade { /** * Gets the name of the facade component. * * @return string */ - protected static function accessor(){ return 'crumbs'; } + protected static function accessor(){ return 'breadcrumbs'; } } From faba56380deb5038a6e401197e39939e30912c6e Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 15:15:05 +0930 Subject: [PATCH 084/136] A bit of shuffling of code execution to ensure everything is loaded prior to it being needed. Signed-off-by: Jason Lewis --- start.php | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/start.php b/start.php index 742a349..13b6273 100644 --- a/start.php +++ b/start.php @@ -23,17 +23,6 @@ set_path('themes', path('feather') . 'themes' . DS); -/* -|-------------------------------------------------------------------------- -| Feather Exceptions -|-------------------------------------------------------------------------- -| -| Load in Feather's Exception Classes -| -*/ - -require path('feather') . 'exceptions' . EXT; - /* |-------------------------------------------------------------------------- | Core Autoloading @@ -80,6 +69,19 @@ $feather['config']->db(); +/* +|-------------------------------------------------------------------------- +| Load Feather Bootstrapping +|-------------------------------------------------------------------------- +| +| We can now bootstrap the remaining items. +| +*/ + +require path('feather') . 'bootstrap' . DS . 'views' . EXT; + +require path('feather') . 'bootstrap' . DS . 'exceptions' . EXT; + /* |-------------------------------------------------------------------------- | Load Feather Components @@ -89,15 +91,14 @@ | */ -foreach($feather['config']->get('feather: feather.components') as $component => $closure) +foreach($feather['config']->get('feather: feather.components') as $component => $resolver) { - if(is_callable($closure)) + if(is_callable($resolver)) { - $closure($feather); + $resolver($feather); } } - /* |-------------------------------------------------------------------------- | Load Feather Facades @@ -127,14 +128,26 @@ { $handles = str_replace('(:feather)', Bundle::option('feather', 'handles'), $handles); - Bundle::register("feather/{$application}", array( + Bundle::register("feather {$application}", array( 'handles' => $handles, 'location' => "feather/applications/{$application}" )); - starts_with(Request::uri(), $handles) and Bundle::start("feather/{$application}"); + starts_with(Request::uri(), $handles) and Bundle::start("feather {$application}"); } +/* +|-------------------------------------------------------------------------- +| Bootstrap Authentication +|-------------------------------------------------------------------------- +| +| Authentication needs to be bootstrapped so that we have the correct +| auth driver set, any authenticators are registered, and the user is set. +| +*/ + +$feather['auth']->bootstrap(); + /* |-------------------------------------------------------------------------- | Feather CLI From 035a1d6a6f02e653d2a32f2c27ab93f28fe254ca Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 15:15:35 +0930 Subject: [PATCH 085/136] Authorizer is now bootstrapped from the start.php file. Signed-off-by: Jason Lewis --- components/auth/authorizer.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/components/auth/authorizer.php b/components/auth/authorizer.php index 16586f0..dfd575f 100644 --- a/components/auth/authorizer.php +++ b/components/auth/authorizer.php @@ -4,9 +4,9 @@ use Route; use Session; use InvalidArgumentException; -use Feather\Components\Foundation; +use Feather\Components\Foundation\Component; -class Authorizer extends Foundation\Component { +class Authorizer extends Component { /** * The current logged in user. @@ -16,15 +16,12 @@ class Authorizer extends Foundation\Component { public $user; /** - * Overload the constructor, bootstrap the authenticator. + * Bootstrap the authenticator. * - * @param Feather\Components\Foundation\Application $feather * @return void */ - public function __construct(Foundation\Application $feather) + public function bootstrap() { - parent::__construct($feather); - Auth::extend('feather', function() { return new Driver; From c38a971f21e7e2eadcddd1224aed5536a3c55e9d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:51:04 +0930 Subject: [PATCH 086/136] Autoload core and admin models, shifting of logic, and theme development mode. Signed-off-by: Jason Lewis --- start.php | 65 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/start.php b/start.php index 13b6273..eaee54e 100644 --- a/start.php +++ b/start.php @@ -1,5 +1,6 @@ path('feather') + 'Feather\\Core' => path('core') . 'models', + 'Feather\\Admin' => path('admin') . 'models', + 'Feather' => path('feather') )); /* @@ -82,6 +85,8 @@ require path('feather') . 'bootstrap' . DS . 'exceptions' . EXT; +require path('feather') . 'bootstrap' . DS . 'ioc' . EXT; + /* |-------------------------------------------------------------------------- | Load Feather Components @@ -113,6 +118,18 @@ require path('feather') . 'facades' . EXT; +/* +|-------------------------------------------------------------------------- +| Bootstrap Authentication +|-------------------------------------------------------------------------- +| +| Authentication needs to be bootstrapped so that we have the correct +| auth driver set, any authenticators are registered, and the user is set. +| +*/ + +$feather['auth']->bootstrap(); + /* |-------------------------------------------------------------------------- | Register Feather Applications @@ -126,28 +143,16 @@ foreach($feather['config']->get('feather: feather.applications') as $application => $handles) { - $handles = str_replace('(:feather)', Bundle::option('feather', 'handles'), $handles); + $handles = trim(str_replace('(:feather)', Bundle::option('feather', 'handles'), $handles), '/'); Bundle::register("feather {$application}", array( 'handles' => $handles, 'location' => "feather/applications/{$application}" )); - starts_with(Request::uri(), $handles) and Bundle::start("feather {$application}"); + starts_with(Request::uri(), $handles ?: Request::uri()) and Bundle::start("feather {$application}"); } -/* -|-------------------------------------------------------------------------- -| Bootstrap Authentication -|-------------------------------------------------------------------------- -| -| Authentication needs to be bootstrapped so that we have the correct -| auth driver set, any authenticators are registered, and the user is set. -| -*/ - -$feather['auth']->bootstrap(); - /* |-------------------------------------------------------------------------- | Feather CLI @@ -164,4 +169,32 @@ require 'Hamcrest/Hamcrest.php'; with(new \Mockery\Loader)->register(); -} \ No newline at end of file +} + +/* +|-------------------------------------------------------------------------- +| Theme Development Mode +|-------------------------------------------------------------------------- +| +| When not running via the CLI and theme development mode is on we'll +| publish all the themes related assets. +| +*/ +if($feather['config']->get('feather: db.forum.theme_development_mode') and !Request::cli()) +{ + $publisher = IoC::resolve('feather: publisher'); + + ob_start() and $publisher->theme((array) $feather['config']->get('feather: db.forum.theme')) and ob_clean(); +} + +/* +|-------------------------------------------------------------------------- +| Theme Bootstrap +|-------------------------------------------------------------------------- +| +| Because themes are registered as a Laravel bundle they can contain a +| start script to register any theme related assets with the container. +| +*/ + +Bundle::start('feather theme'); \ No newline at end of file From a9e5d2a6df77390808201bf79420be43b8aa6c1a Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:51:29 +0930 Subject: [PATCH 087/136] Initial commit of base controller. Signed-off-by: Jason Lewis --- applications/core/controllers/base.php | 153 +++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 applications/core/controllers/base.php diff --git a/applications/core/controllers/base.php b/applications/core/controllers/base.php new file mode 100644 index 0000000..ecb5b2f --- /dev/null +++ b/applications/core/controllers/base.php @@ -0,0 +1,153 @@ +feather = Feather\Components\Support\Facade::application(); + } + + /** + * Determines if a place is valid, if it is return the place. + * + * @param int $id + * @return Feather\Models\Place + */ + protected function place($id) + { + return is_numeric($id) ? Feather\Models\Place::with('permissions')->find($id) : null; + } + + /** + * Determines if a discussion is valid, if it is return the discussion. + * + * @param int $id + * @return Feather\Models\Discussion + */ + protected function discussion($id) + { + return is_numeric($id) ? Feather\Models\Discussion::with('participants')->find($id) : null; + } + + /** + * Method to be run before each request. + * + * @return void + */ + public function before() + { + if($this->geared) + { + $this->feather['gear']->controller('before', $this); + } + } + + /** + * Method to be run after each request. + * + * @return void + */ + public function after($response) + { + if($this->geared) + { + $this->feather['gear']->controller('after', $this); + } + + $this->feather['gear']->fire('assets: change styles', array(Asset::container('theme'))); + + $this->feather['gear']->fire('assets: change scripts', array(Asset::container('theme'))); + } + + /** + * Overload the execute method on the controller. If the controller is geared we'll + * run the first override gear and return the result accordingly. + * + * @param string $method + * @param array $parameters + * @return string|object + */ + public function execute($method, $parameters = array()) + { + $response = $this->geared ? $this->feather['gear']->controller('override', $this) : null; + + // Only if the response is not null and we actually fired a gear event successfully. + if(!is_null($response) and $response) + { + $response = is_string($response) ? $response : $this->layout; + + return $response; + } + + return parent::execute($method, $parameters); + } + + /** + * Magic method for calling Feather components. + * + * @param string $component + * @return object + */ + public function __get($component) + { + if(isset($this->feather[$component])) + { + return $tihs->feather[$component]; + } + + throw new BadMethodCallException('Invalid component [{$component}] called on controller.'); + } + + /** + * Catch-all method for requests that can't be matched. + * + * @param string $method + * @param array $parameters + * @return Response + */ + public function __call($method, $parameters) + { + $response = $this->geared ? $this->feather['gear']->controller('create', $this) : null; + + if(is_null($response) or !$response) + { + return Response::error('404'); + } + } + +} \ No newline at end of file From 7b2f4ffa932a7fea6e0b78665ff17fbc6610e6b9 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:52:35 +0930 Subject: [PATCH 088/136] Renaming of core model namespace. Signed-off-by: Jason Lewis --- applications/core/models/base.php | 2 +- applications/core/models/gear.php | 2 +- applications/core/models/migration.php | 2 +- applications/core/models/role.php | 2 +- applications/core/models/user.php | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/core/models/base.php b/applications/core/models/base.php index 903ac5c..833f0be 100644 --- a/applications/core/models/base.php +++ b/applications/core/models/base.php @@ -1,4 +1,4 @@ -has_many_and_belongs_to('Feather\\Models\\Role', 'user_roles', 'user_id', 'role_id'); + return $this->has_many_and_belongs_to('Feather\\Core\\Role', 'user_roles', 'user_id', 'role_id'); } } \ No newline at end of file From 033160a882a8e6fbdb86c136255533f6ec656863 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:53:31 +0930 Subject: [PATCH 089/136] Moved model autoloading out of the core start script. Signed-off-by: Jason Lewis --- applications/core/start.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/core/start.php b/applications/core/start.php index d4e6476..034aa7b 100644 --- a/applications/core/start.php +++ b/applications/core/start.php @@ -22,6 +22,6 @@ | */ -Autoloader::namespaces(array( - 'Feather\\Models' => path('core') . 'models' +Autoloader::map(array( + 'Feather_Base_Controller' => path('core') . 'controllers' . DS . 'base' . EXT )); \ No newline at end of file From a4203215ef6ac8da39d1e317d498860e98be5517 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:57:00 +0930 Subject: [PATCH 090/136] Blade extenders and HTML macros added. Signed-off-by: Jason Lewis --- bootstrap/views.php | 99 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/bootstrap/views.php b/bootstrap/views.php index a9598f6..0302e41 100644 --- a/bootstrap/views.php +++ b/bootstrap/views.php @@ -1,9 +1,11 @@ $4', $value); +}); + +/* +|-------------------------------------------------------------------------- +| Blade Events +|-------------------------------------------------------------------------- +| +| Fire custom Gear events. +| +*/ + +Blade::extend(function($value) +{ + $matcher = Blade::matcher('event'); + + return preg_replace($matcher, '$1', $value); +}); + +/* +|-------------------------------------------------------------------------- +| Blade Inline Errors +|-------------------------------------------------------------------------- +| +| Display inline errors for a specific form element. +| +*/ + +Blade::extend(function($value) +{ + $matcher = Blade::matcher('error'); + + return preg_replace($matcher, '$1has$2 ? view("feather core::error.inline", array("error" => $errors->first$1)) : null; ?>', $value); +}); + +/* +|-------------------------------------------------------------------------- +| Blade Errors +|-------------------------------------------------------------------------- +| +| Display all errors for a form. +| +*/ + +Blade::extend(function($value) +{ + $matcher = Blade::matcher('errors'); + + return preg_replace($matcher, '$1all() ? view("feather core::error.page", array("errors" => $errors->all())) : null; ?>', $value); +}); + +/* +|-------------------------------------------------------------------------- +| HTML::link_to_new_discussion() Macro +|-------------------------------------------------------------------------- +| +| Custom HTML macro to link to the new discussion page. +| +*/ + +HTML::macro('link_to_new_discussion', function($title, $attributes = array()) use ($feather) +{ + $uri = URI::current(); + + if(str_contains($uri, 'place')) + { + $url = preg_replace('/\/p([0-9]+)/', '', $uri) . (ends_with($uri, 'start') ? null : '/start'); + + preg_match('/(\d+)-.*?/', $uri, $matches); + + // If the user cannot start discussions on the selected place, don't show the button. + if($feather['auth']->cannot('start: discussions', Feather\Models\Place::find(array_pop($matches)))) + { + return null; + } + } + else + { + $url = URL::to_route('start.discussion'); + } + + return HTML::link($url, $title, $attributes); +}); \ No newline at end of file From d940befc4a78ee8224a705104bf501457b0bc6ed Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:58:07 +0930 Subject: [PATCH 091/136] Renamed the breadcrumbs test. Signed-off-by: Jason Lewis --- tests/{crumbs.test.php => breadcrumbs.test.php} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename tests/{crumbs.test.php => breadcrumbs.test.php} (54%) diff --git a/tests/crumbs.test.php b/tests/breadcrumbs.test.php similarity index 54% rename from tests/crumbs.test.php rename to tests/breadcrumbs.test.php index 478aa9e..6e6756b 100644 --- a/tests/crumbs.test.php +++ b/tests/breadcrumbs.test.php @@ -1,6 +1,6 @@ feather['crumbs']->drop('stub'); + $this->feather['breadcrumbs']->drop('stub'); - $this->assertNotEmpty($this->feather['crumbs']->crumbs); + $this->assertNotEmpty($this->feather['breadcrumbs']->crumbs); } public function testCanGetHTML() { - $this->assertEquals('
  • stub
  • ', $this->feather['crumbs']->item('/', 'stub')); + $this->assertEquals('
  • stub
  • ', $this->feather['breadcrumbs']->item('/', 'stub')); } } From a156a2a5b8cc2910f2ebb4d803d3f0bfc7089171 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 18:58:27 +0930 Subject: [PATCH 092/136] Initial commit of IoC bootstrap. Signed-off-by: Jason Lewis --- bootstrap/ioc.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 bootstrap/ioc.php diff --git a/bootstrap/ioc.php b/bootstrap/ioc.php new file mode 100644 index 0000000..339940b --- /dev/null +++ b/bootstrap/ioc.php @@ -0,0 +1,31 @@ + Date: Sun, 23 Sep 2012 19:14:44 +0930 Subject: [PATCH 093/136] Fixed really bad spelling of 'this'. Signed-off-by: Jason Lewis --- applications/core/controllers/base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/core/controllers/base.php b/applications/core/controllers/base.php index ecb5b2f..c97b380 100644 --- a/applications/core/controllers/base.php +++ b/applications/core/controllers/base.php @@ -127,7 +127,7 @@ public function __get($component) { if(isset($this->feather[$component])) { - return $tihs->feather[$component]; + return $this->feather[$component]; } throw new BadMethodCallException('Invalid component [{$component}] called on controller.'); From 10dae1d2315c455832c3a08a731aae1469272774 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 19:15:04 +0930 Subject: [PATCH 094/136] Initial commit of publish task. Signed-off-by: Jason Lewis --- tasks/publish.php | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tasks/publish.php diff --git a/tasks/publish.php b/tasks/publish.php new file mode 100644 index 0000000..087a80f --- /dev/null +++ b/tasks/publish.php @@ -0,0 +1,128 @@ +theme() and $this->gear(); + + echo PHP_EOL . PHP_EOL . "All theme and plugin assets have been published..."; + + return true; + } + + /** + * Register all the gears and publish all their assets. + * + * @param array $parameters + * @return void + */ + public function gear($parameters = array()) + { + $publish = empty($parameters) ? null : array_shift($parameters); + + $this->publish($publish, path('gears') . DS, 'plugins'); + + return true; + } + + /** + * Register all the themes and publish all their assets. + * + * @param array $parameters + * @return void + */ + public function theme($parameters = array()) + { + $publish = empty($parameters) ? null : array_shift($parameters); + + $this->publish($publish, path('themes') . DS, 'themes'); + + return true; + } + + /** + * Publish a theme or plugins assets. + * + * @param string $publish + * @param string $path + * @param string $type + * @return void + */ + private function publish($publish, $path, $type) + { + if(file_exists($path)) + { + ob_start(); + + $publisher = new Laravel\CLI\Tasks\Bundle\Publisher; + + $published = 0; + + foreach(new FilesystemIterator($path) as $item) + { + if($item->isDir() and ($publish == $item->getFilename() or is_null($publish))) + { + $name = $item->getFilename(); + + if(file_exists($item->getPathname() . DS . 'public')) + { + // To publish the assets we must first register the bundle. Afterwards + // we can run the publish task. + Bundle::register($this->name($name, $type), array( + 'location' => 'path: ' . $item->getPathname(), + 'handles' => null, + 'auto' => false + )); + + ob_start(); + + $publisher->publish($this->name($name, $type)); + + if(str_contains(ob_get_clean(), 'published')) + { + echo "[{$type}] Assets for '{$name}' have been published." . PHP_EOL; + + $published++; + } + else + { + echo "[{$type}] Could not publish assets for '{$name}'." . PHP_EOL; + } + } + } + } + + if(($string = ob_get_clean()) == '') + { + echo "[{$type}] There were no assets to publish."; + } + else + { + echo $string . "[{$type}] Total published: {$published}" . PHP_EOL . PHP_EOL; + } + } + else + { + echo "Could not locate the Feather {$type} directory. Please check your installation."; + } + } + + /** + * Name given to theme or gear. + * + * @param string $name + * @param string $type + * @return string + */ + private function name($name, $type) + { + return "feather/{$type}/{$name}"; + } + +} \ No newline at end of file From 2e48dd52b8fef530b4a011e2f33d860c1c911b28 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 19:17:12 +0930 Subject: [PATCH 095/136] Bit of model refactoring. Signed-off-by: Jason Lewis --- components/auth/driver.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/auth/driver.php b/components/auth/driver.php index 96f21c9..a63b064 100644 --- a/components/auth/driver.php +++ b/components/auth/driver.php @@ -2,7 +2,7 @@ use Hash; use Cache; -use Feather\Models; +use Feather\Core; class Driver extends \Laravel\Auth\Drivers\Driver { @@ -19,7 +19,7 @@ public function retrieve($id) { return Cache::remember("user_{$id}", function() use ($id) { - $user = Models\User::with(array('roles'))->find($id); + $user = Core\User::with(array('roles'))->find($id); // To provide a cleaner syntax when accessing roles, we'll make each of the roles keys // the same as its ID. @@ -30,16 +30,16 @@ public function retrieve($id) $user->relationships['roles'] = $roles; return $user; - }, Models\User::cache_time); + }, Core\User::cache_time); } else { return Cache::remember('guest', function() { - return new Models\User(array( - 'roles' => array(3 => Models\Role::find(3)) + return new Core\User(array( + 'roles' => array(3 => Core\Role::find(3)) ), true); - }, Models\User::cache_time); + }, Core\User::cache_time); } } @@ -53,7 +53,7 @@ public function attempt($credentials = array()) { // Find the user in the database with the username they have provided in the // login form. If no user can be found then that's as far as we go. - if(!$user = Models\User::where_username($credentials['username'])->first()) + if(!$user = Core\User::where_username($credentials['username'])->first()) { return false; } @@ -62,7 +62,7 @@ public function attempt($credentials = array()) // a couple of extra checks. A migrating user has only a single role, which // is the migrating roll. If the user has not been migrated then we'll // attempt to migrate them. - if($migration = Models\Migration::active()) + if($migration = Core\Migration::active()) { $driver = Migrator\Drivers\Driver::make($migration); @@ -104,13 +104,13 @@ public function attempt($credentials = array()) /** * Log a user in to Feather. * - * @param int|Feather\Models\User $user + * @param int|Feather\Core\User $user * @param bool $remember * @return bool */ public function login($user, $remember = false) { - if($user instanceof Models\User) + if($user instanceof Core\User) { $user = $user->id; } From 9b7bc8da1e6825498f1b4347a100e6aa232aee6f Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 19:52:08 +0930 Subject: [PATCH 096/136] Initial commit of langauge files. Signed-off-by: Jason Lewis --- applications/core/language/en/common.php | 23 ++++++++++ applications/core/language/en/login.php | 58 ++++++++++++++++++++++++ applications/core/language/en/titles.php | 20 ++++++++ 3 files changed, 101 insertions(+) create mode 100644 applications/core/language/en/common.php create mode 100644 applications/core/language/en/login.php create mode 100644 applications/core/language/en/titles.php diff --git a/applications/core/language/en/common.php b/applications/core/language/en/common.php new file mode 100644 index 0000000..bb62e2c --- /dev/null +++ b/applications/core/language/en/common.php @@ -0,0 +1,23 @@ + 'Username', + 'password' => 'Password', + 'email' => 'E-mail', + 'login' => 'Sign In', + 'register' => 'Create Account', + 'connect_with_existing_service' => 'Connect with an existing service', + 'today' => 'Today', + 'yesterday' => 'Yesterday', + 'rules' => 'Rules' +); \ No newline at end of file diff --git a/applications/core/language/en/login.php b/applications/core/language/en/login.php new file mode 100644 index 0000000..360aa4b --- /dev/null +++ b/applications/core/language/en/login.php @@ -0,0 +1,58 @@ + 'Your username and/or password is incorrect.', + + /* + |-------------------------------------------------------------------------- + | Login Validation Messages + |-------------------------------------------------------------------------- + | + | English language messages for individual field errors. + | + */ + + 'messages' => array( + 'username' => array('is_required' => 'You did not enter a username.'), + 'password' => array('is_required' => 'You did not enter a password.') + ), + + /* + |-------------------------------------------------------------------------- + | Login Form Labels + |-------------------------------------------------------------------------- + | + | English language messages for login page form labels. + | + */ + + 'labels' => array( + 'remember' => array( + 'title' => 'Remember me', + 'helper' => 'If yes, when you next return you\'ll be automatically signed in.' + ), + 'register' => array( + 'title' => 'No account? Create a new account today!' + ) + ) + +); \ No newline at end of file diff --git a/applications/core/language/en/titles.php b/applications/core/language/en/titles.php new file mode 100644 index 0000000..d8488ef --- /dev/null +++ b/applications/core/language/en/titles.php @@ -0,0 +1,20 @@ + 'Home', + 'login' => 'Sign In', + 'register' => 'Create Account', + 'rules' => 'Rules', + 'connect_to_community' => 'Connect to Community', + 'start_discussion' => 'Start Discussion', + 'edit_discussion' => 'Edit Discussion' +); \ No newline at end of file From 2cb09d7701a281bdfbfb8b14b1ed3d43b2bd4d92 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 19:52:26 +0930 Subject: [PATCH 097/136] Validator loads validation configuration from specific application. Signed-off-by: Jason Lewis --- components/validation/validator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/validation/validator.php b/components/validation/validator.php index d93e6a8..ee1738a 100644 --- a/components/validation/validator.php +++ b/components/validation/validator.php @@ -9,7 +9,7 @@ class Validator extends Component { /** - * Application to load messages from. + * Application to load from. * * @var string */ @@ -71,7 +71,7 @@ public function app($application) */ public function get($validator) { - if(!$this->validating[$validator] = $this->feather['config']->get("feather: validation.{$validator}")) + if(!$this->validating[$validator] = $this->feather['config']->get("feather {$this->application}: validation.{$validator}")) { throw new InvalidArgumentException("Invalid validator [{$validator}] supplied."); } From f223af0886c30979984a4f31754ca76041fd9ab1 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:26:24 +0930 Subject: [PATCH 098/136] Config items can be loaded on a per application basis. Signed-off-by: Jason Lewis --- components/config/repository.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/components/config/repository.php b/components/config/repository.php index 10d88fb..263f01e 100644 --- a/components/config/repository.php +++ b/components/config/repository.php @@ -248,6 +248,15 @@ private function prefix($key) list($prefix, $key) = explode(': ', $key); + // Applications can refer to their own configuration files the same way that + // they are registered with Feather. + if(str_contains($prefix, ' ')) + { + list(, $app) = explode(' ', $prefix); + + $prefix = 'app'; + } + switch($prefix) { // The feather prefix is applied to both database and file-based @@ -263,6 +272,19 @@ private function prefix($key) return "feather::gear:{$gear} {$key}"; break; + + // The theme prefix is applied to Feather themes, the theme + // name should appear before the file and key to be used. + case 'theme': + list($theme, $key) = explode(' ', $key); + + return "feather::theme:{$theme} {$key}"; + break; + + case 'app': + return "feather {$app}::{$key}"; + break; + default: return $key; } From f30e80eb94dd296f023d6e56e942a022c064a9d4 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:26:37 +0930 Subject: [PATCH 099/136] Fixed a couple bugs in the gear manager. Signed-off-by: Jason Lewis --- components/gear/manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/gear/manager.php b/components/gear/manager.php index 5317c6b..0593b51 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -4,7 +4,7 @@ use Event; use Request; use FilesystemIterator; -use Feather\Models\Gear; +use Feather\Core\Gear; use InvalidArgumentException; use Feather\Components\Foundation\Component; From 19673a8e6dcb4cac798d26e0fbc6766b21d998a6 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:27:33 +0930 Subject: [PATCH 100/136] Validation files can be stored in custom validation directory. Signed-off-by: Jason Lewis --- components/validation/validator.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/components/validation/validator.php b/components/validation/validator.php index ee1738a..af3d869 100644 --- a/components/validation/validator.php +++ b/components/validation/validator.php @@ -71,12 +71,22 @@ public function app($application) */ public function get($validator) { - if(!$this->validating[$validator] = $this->feather['config']->get("feather {$this->application}: validation.{$validator}")) + $segments = explode('.', $validator); + + if(file_exists($path = path('applications') . $this->application . DS . 'validation' . DS . $segments[0] . EXT)) { - throw new InvalidArgumentException("Invalid validator [{$validator}] supplied."); + if($this->validating[$validator] = array_get(require $path, implode('.', array_slice($segments, 1)))) + { + return $this; + } } - return $this; + if($this->validating[$validator] = $this->feather['config']->get("feather {$this->application}: validation.{$validator}")) + { + return $this; + } + + throw new InvalidArgumentException("Invalid validator [{$validator}] supplied."); } /** @@ -101,7 +111,7 @@ public function passes() { // If a response is returned from the closure then we have a custom error messages // to throw with the exception. - if($responses = call_user_func(reset($this->validating), $this)) + if($responses = call_user_func_array(reset($this->validating), array($this))) { if(!is_array(reset($responses))) $responses = array(array_shift($responses) => array()); From 07a13d7920e2aec2b1552844d8fbf5ac25718b24 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:27:51 +0930 Subject: [PATCH 101/136] Fixed some issues with tests. Signed-off-by: Jason Lewis --- tests/auth.test.php | 8 ++++---- tests/breadcrumbs.test.php | 2 +- tests/gear.test.php | 2 +- tests/validation.test.php | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/auth.test.php b/tests/auth.test.php index 6b39da9..b650b9b 100644 --- a/tests/auth.test.php +++ b/tests/auth.test.php @@ -40,11 +40,11 @@ public function testCanCheckActivation() public function user() { - return new Feather\Models\User(array( + return new Feather\Core\User(array( 'roles' => array( - new Feather\Models\Role(array('do_something' => 1)), - new Feather\Models\Role(array('name' => 'Administrator')), - new Feather\Models\Role(array('view_something' => 0)) + new Feather\Core\Role(array('do_something' => 1)), + new Feather\Core\Role(array('name' => 'Administrator')), + new Feather\Core\Role(array('view_something' => 0)) ), 'activated' => 1 )); diff --git a/tests/breadcrumbs.test.php b/tests/breadcrumbs.test.php index 6e6756b..3e23f48 100644 --- a/tests/breadcrumbs.test.php +++ b/tests/breadcrumbs.test.php @@ -18,7 +18,7 @@ public function testCanDropCrumb() public function testCanGetHTML() { - $this->assertEquals('
  • stub
  • ', $this->feather['breadcrumbs']->item('/', 'stub')); + $this->assertEquals('
  • stub
  • ', $this->feather['breadcrumbs']->item('/', 'stub')); } } diff --git a/tests/gear.test.php b/tests/gear.test.php index ec2d2ca..7622dba 100644 --- a/tests/gear.test.php +++ b/tests/gear.test.php @@ -8,7 +8,7 @@ public function setUp() { $this->feather = Feather\Components\Support\Facade::application(); - $this->feather['gear']->register(new Feather\Models\Gear(array( + $this->feather['gear']->register(new Feather\Core\Gear(array( 'identifier' => 'stub', 'location' => 'path: ' . __DIR__ . DS . 'mock', 'auto' => false diff --git a/tests/validation.test.php b/tests/validation.test.php index d7ed7b1..2bfb020 100644 --- a/tests/validation.test.php +++ b/tests/validation.test.php @@ -32,7 +32,7 @@ public function testCanSetCustomData() public function testCanLoadValidatorClosure() { - $this->feather['config']->set('feather: validation.test', function($validator){}); + $this->feather['config']->set('feather core: validation.test', function($validator){}); $validator = $this->feather['validator']->get('test'); @@ -55,7 +55,7 @@ public function testCanAddMessage() public function testValidationDoesPass() { - $this->feather['config']->set('feather: validation.test', function($validator){ + $this->feather['config']->set('feather core: validation.test', function($validator){ $validator->rule('cat', 'required'); }); @@ -64,7 +64,7 @@ public function testValidationDoesPass() public function testValidationExceptionIsThrown() { - $this->feather['config']->set('feather: validation.test', function($validator){ + $this->feather['config']->set('feather core: validation.test', function($validator){ $validator->rule('cat', 'required'); }); From 74dbba8cfbd39dfe888f1493755494ae1c0114a7 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:29:32 +0930 Subject: [PATCH 102/136] Don't need this anymore. Signed-off-by: Jason Lewis --- config/validation.php | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 config/validation.php diff --git a/config/validation.php b/config/validation.php deleted file mode 100644 index 207cc1e..0000000 --- a/config/validation.php +++ /dev/null @@ -1,5 +0,0 @@ - Date: Sun, 23 Sep 2012 20:29:50 +0930 Subject: [PATCH 103/136] Added path to applications directory. Signed-off-by: Jason Lewis --- start.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/start.php b/start.php index eaee54e..4b114c3 100644 --- a/start.php +++ b/start.php @@ -16,9 +16,11 @@ set_path('feather', __DIR__ . DS); -set_path('core', path('feather') . 'applications' . DS . 'core' . DS); +set_path('applications', path('feather') . 'applications' . DS); -set_path('admin', path('feather') . 'applications' . DS . 'admin' . DS); +set_path('core', path('applications') . 'core' . DS); + +set_path('admin', path('applications') . 'admin' . DS); set_path('gears', path('feather') . 'gears' . DS); From d2c6e18994e16dbb4c8a4925588eb1dec56e5182 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:38:27 +0930 Subject: [PATCH 104/136] Fixed bug with validator. Signed-off-by: Jason Lewis --- components/validation/validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/validation/validator.php b/components/validation/validator.php index af3d869..0a4c117 100644 --- a/components/validation/validator.php +++ b/components/validation/validator.php @@ -75,7 +75,7 @@ public function get($validator) if(file_exists($path = path('applications') . $this->application . DS . 'validation' . DS . $segments[0] . EXT)) { - if($this->validating[$validator] = array_get(require $path, implode('.', array_slice($segments, 1)))) + if($this->validating[$validator] = array_get(require $path, implode('.', array_slice($segments, 1)) ?: null)) { return $this; } From 2e1b9c1a1a1f3698e205c5293d83d7220d00a321 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:39:20 +0930 Subject: [PATCH 105/136] Refining the template. Signed-off-by: Jason Lewis --- themes/basic/views/template.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/themes/basic/views/template.blade.php b/themes/basic/views/template.blade.php index 3b093e2..46e9c4f 100644 --- a/themes/basic/views/template.blade.php +++ b/themes/basic/views/template.blade.php @@ -4,7 +4,7 @@ - @event('view: before template.title') {{ $title }} – {{ $app->title }} + @event('view: before template.title') {{ $title }} – {{ $feather->title }} {{ Asset::container('theme')->styles() }} @@ -22,9 +22,9 @@
    @if(Feather\Auth::online()) - @include('feather::menus.user') + @include('core::menu.user') @else - @include('feather::menus.guest') + @include('core::menu.guest') @endif
    From 01e6b8c326810b964d775a926517469873822e9f Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:39:53 +0930 Subject: [PATCH 106/136] Initial commit of index controller. Signed-off-by: Jason Lewis --- applications/core/controllers/index.php | 151 ++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 applications/core/controllers/index.php diff --git a/applications/core/controllers/index.php b/applications/core/controllers/index.php new file mode 100644 index 0000000..cfa4d6d --- /dev/null +++ b/applications/core/controllers/index.php @@ -0,0 +1,151 @@ +feather['config']->get('feather: db.overview.discussions_per_place')); + + $this->layout->nest('content', 'core::index.overview'); + } + + /** + * Allows a user to enter their login details into a form and then become + * authenticated on the forums. + * + * @return void + */ + public function get_login() + { + if($this->feather['auth']->online()) + { + return $this->redirect->to_route('feather'); + } + elseif($this->config->get('feather: db.auth.driver', 'internal') != 'internal') + { + return $this->redirect->before_login(); + } + + $this->breadcrumbs->drop(__('feather core::titles.login')); + + $this->layout->with('title', __('feather core::titles.login')) + ->nest('content', 'feather core::user.login'); + } + + /** + * Handles the posting of the login form and after validation, attempts to + * authenticate the user. + * + * @return void + */ + public function post_login() + { + try + { + $this->validator->get('auth.login')->against(Input::get())->passes(); + } + catch (FeatherValidationException $errors) + { + return $this->redirect->with_query('route: login')->with_input()->with_errors($errors->get()); + } + + if($this->auth->attempt(Input::get())) + { + return $this->redirect->to_previous('route: feather'); + } + + return $this->redirect->to_self()->with_input()->alert('error', 'feather core::login.failure'); + } + + /** + * Logs a user out of the forums and redirects them back to where they were, or depending + * on the authentication driver, a logout landing page. + * + * @return void + */ + public function get_logout() + { + Feather\Auth::logout(); + + // Return to the generated logout URL, this can change depending on the authentication + // driver that is being used. + return Feather\Redirect::after_logout(); + } + + /** + * If registrations are enabled a user can enter the required details into the registration + * form and register an account on the forums. + * + * @return void + */ + public function get_register() + { + if(Feather\Config::get('feather: auth.driver', 'internal') != 'internal') + { + return Feather\Redirect::before_register(); + } + + Feather\Breadcrumbs::drop(__('feather::titles.register')); + + $this->layout->with('title', __('feather::titles.register')) + ->nest('content', 'feather::user.register'); + } + + /** + * Handles the posting of the registration form. Validates and then attempts to + * register a user with the details they supplied. If e-mail activation is enabled + * a user will also be sent an e-mail so they can activate their account. + * + * @return void + */ + public function post_register() + { + try + { + Feather\Validation\Auth\Register::passes(Input::all()); + } + catch (FeatherValidationException $errors) + { + return Feather\Redirect::to_self()->with_input()->with_errors($errors->get()); + } + + try + { + $user = Feather\Models\User::register(Input::all()); + } + catch (FeatherRegistrationException $errors) + { + return Feather\Redirect::to_self()->with_input()->alert('error', 'feather::register.failure'); + } + + // Users must confirm their e-mail address once they have registered. We + // will now send them an e-mail with their activation code. + if(Feather\Config::get('feather: registration.confirm_email')) + { + + } + + // Log the user in and redirect back to the home page. + Feather\Auth::login($user->id); + + return Feather\Redirect::to_previous('route: feather'); + } + + /** + * Simply shows the forums rules. + * + * @return void + */ + public function get_rules() + { + $this->layout->with('title', __('feather::titles.rules')) + ->nest('content', 'feather::misc.rules'); + } + +} \ No newline at end of file From 1dff1532b7b50751f56a645c58bec033553abfb7 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:40:22 +0930 Subject: [PATCH 107/136] Refactoring the base controller. Signed-off-by: Jason Lewis --- applications/core/controllers/base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/core/controllers/base.php b/applications/core/controllers/base.php index c97b380..10141a4 100644 --- a/applications/core/controllers/base.php +++ b/applications/core/controllers/base.php @@ -130,7 +130,7 @@ public function __get($component) return $this->feather[$component]; } - throw new BadMethodCallException('Invalid component [{$component}] called on controller.'); + throw new BadMethodCallException("Invalid component [{$component}] called on controller."); } /** From 7c5200cbb15b6c423ad581faa724a02182b80ad3 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:40:45 +0930 Subject: [PATCH 108/136] Initial commit of auth validation. Signed-off-by: Jason Lewis --- applications/core/validation/auth.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 applications/core/validation/auth.php diff --git a/applications/core/validation/auth.php b/applications/core/validation/auth.php new file mode 100644 index 0000000..2e57bdf --- /dev/null +++ b/applications/core/validation/auth.php @@ -0,0 +1,13 @@ + function($validator) + { + $validator->rule('username', 'required') + ->rule('password', 'required') + ->message('username.required', 'login.messages.username.is_required') + ->message('password.required', 'login.messages.password.is_required'); + } + +); \ No newline at end of file From 22e139070ccf06014e02f7f3fac36aca24025c94 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:41:01 +0930 Subject: [PATCH 109/136] Initial commit of some views. Signed-off-by: Jason Lewis --- .../core/views/index/overview.blade.php | 1 + applications/core/views/menu/guest.blade.php | 4 ++ applications/core/views/user/login.blade.php | 52 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 applications/core/views/index/overview.blade.php create mode 100644 applications/core/views/menu/guest.blade.php create mode 100644 applications/core/views/user/login.blade.php diff --git a/applications/core/views/index/overview.blade.php b/applications/core/views/index/overview.blade.php new file mode 100644 index 0000000..3c002eb --- /dev/null +++ b/applications/core/views/index/overview.blade.php @@ -0,0 +1 @@ +Oh la la \ No newline at end of file diff --git a/applications/core/views/menu/guest.blade.php b/applications/core/views/menu/guest.blade.php new file mode 100644 index 0000000..a61f73a --- /dev/null +++ b/applications/core/views/menu/guest.blade.php @@ -0,0 +1,4 @@ +
      + + +
    \ No newline at end of file diff --git a/applications/core/views/user/login.blade.php b/applications/core/views/user/login.blade.php new file mode 100644 index 0000000..102b2d6 --- /dev/null +++ b/applications/core/views/user/login.blade.php @@ -0,0 +1,52 @@ +

    {{ __('feather core::titles.login') }}

    + +{{ Form::open(urldecode(URI::full()), 'post') }} + +
    + +
    + +
    {{ Form::label('username', __('feather core::common.username')) }}
    +
    + {{ Form::text('username', Input::old('username')) }} + + @error('username') +
    + +
    {{ Form::label('password', __('feather core::common.password')) }}
    +
    + {{ Form::password('password') }} + + @error('password') +
    + +
    {{ Form::label('remember', __('feather core::login.labels.remember.title')) }}
    +
    +
    + + +
    + +
    + {{ __('feather core::login.labels.remember.helper') }} +
    +
    + +
    + +
    + +
    + {{ Form::submit(__('feather core::common.login'), array('class' => 'btn btn-primary btn-big')) }}   + {{ HTML::link_to_route('register', __('feather core::login.labels.register.title'), null, array('class' => 'btn large')) }} +
    + +{{ Form::token() . Form::close() }} \ No newline at end of file From 619f27285bebd90826c77facb4158062274a59ab Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:41:14 +0930 Subject: [PATCH 110/136] Added the main auth routes. Signed-off-by: Jason Lewis --- applications/core/routes.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/applications/core/routes.php b/applications/core/routes.php index e9c1eaf..0aa3276 100644 --- a/applications/core/routes.php +++ b/applications/core/routes.php @@ -1,6 +1,13 @@ 'feather', 'uses' => 'feather core::index@index')); + +/* +|-------------------------------------------------------------------------- +| User Authentication +|-------------------------------------------------------------------------- +*/ + +Route::any('(:bundle)/login', array('as' => 'login', 'uses' => 'feather core::index@login')); +Route::any('(:bundle)/register', array('as' => 'register', 'uses' => 'feather core::login@register')); +Route::get('(:bundle)/logout', array('as' => 'logout', 'uses' => 'feather core::login@logout')); \ No newline at end of file From f6f275d7c5be8c2a8e8cc944c52e98cdedd653c6 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:54:52 +0930 Subject: [PATCH 111/136] Fixed some nasty bugs in the redirector. Signed-off-by: Jason Lewis --- components/support/redirector.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/support/redirector.php b/components/support/redirector.php index a05fd67..23843f1 100644 --- a/components/support/redirector.php +++ b/components/support/redirector.php @@ -3,6 +3,7 @@ use URL; use URI; use Input; +use Bundle; use Request; use Redirect; use Feather\Auth; @@ -68,9 +69,9 @@ public function to_previous($default = null) * * @return object */ - public static function after_logout() + public function after_logout() { - return $this->for_auth(Config::get('feather: auth.logout_url')); + return $this->for_auth(Config::get('feather: db.auth.logout_url')); } /** @@ -78,9 +79,9 @@ public static function after_logout() * * @return object */ - public static function before_register() + public function before_register() { - return $this->for_auth(Config::get('feather: auth.register_url')); + return $this->for_auth(Config::get('feather: db.auth.register_url')); } /** @@ -88,9 +89,9 @@ public static function before_register() * * @return object */ - public static function before_login() + public function before_login() { - return $this->for_auth(Config::get('feather: auth.login_url')); + return $this->for_auth(Config::get('feather: db.auth.login_url')); } /** @@ -107,7 +108,7 @@ protected function for_auth($url) '{token}' => Auth::online() ? Auth::user()->authenticator_token : null ); - if(Config::get('feather: auth.driver') == 'internal') + if(Config::get('feather: db.auth.driver') == 'internal') { $url = URL::to_route('feather'); } From 7328b1132dd379d379cf5cbfed4c3ce106d41908 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:55:11 +0930 Subject: [PATCH 112/136] Fixed a couple issues with the view bootstrapping. Signed-off-by: Jason Lewis --- bootstrap/views.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bootstrap/views.php b/bootstrap/views.php index 0302e41..ba1357e 100644 --- a/bootstrap/views.php +++ b/bootstrap/views.php @@ -1,5 +1,7 @@ cannot('start: discussions', Feather\Models\Place::find(array_pop($matches)))) + if($feather['auth']->cannot('start: discussions', Core\Place::find(array_pop($matches)))) { return null; } From d73be40d09939e751d5c72e5bbb789ab11b4f615 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 20:55:34 +0930 Subject: [PATCH 113/136] Added some getters and setters to the user model. Signed-off-by: Jason Lewis --- applications/core/models/user.php | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/applications/core/models/user.php b/applications/core/models/user.php index 2ed878d..3345e9e 100644 --- a/applications/core/models/user.php +++ b/applications/core/models/user.php @@ -1,5 +1,8 @@ has_many_and_belongs_to('Feather\\Core\\Role', 'user_roles', 'user_id', 'role_id'); } + /** + * When setting a password it must be hashed before stored in the database. + * + * @param string $password + * @return void + */ + public function set_password($password) + { + $this->set_attribute('password', Hash::make($password)); + } + + /** + * Getter for a users slug. + * + * @return string + */ + public function get_slug() + { + return Str::slug($this->get_attribute('username')); + } + + /** + * Getter for a users name. + * + * @return string + */ + public function get_name() + { + return $this->get_attribute('username'); + } + + /** + * Getter for a users avatar URL, provided by Gravatar. + * + * @return string + */ + public function get_avatar() + { + return 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->get_attribute('email')))); + } + } \ No newline at end of file From 253048a7b6866ba45c60a145961f760808f83b27 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 21:07:33 +0930 Subject: [PATCH 114/136] More refactoring of the core application. Signed-off-by: Jason Lewis --- applications/core/controllers/index.php | 14 +- applications/core/language/en/register.php | 81 +++ applications/core/models/ness.php | 477 ++++++++++++++++++ applications/core/models/place.php | 49 ++ applications/core/routes.php | 40 +- applications/core/views/menu/user.blade.php | 33 ++ .../core/views/user/register.blade.php | 82 +++ 7 files changed, 767 insertions(+), 9 deletions(-) create mode 100644 applications/core/language/en/register.php create mode 100644 applications/core/models/ness.php create mode 100644 applications/core/models/place.php create mode 100644 applications/core/views/menu/user.blade.php create mode 100644 applications/core/views/user/register.blade.php diff --git a/applications/core/controllers/index.php b/applications/core/controllers/index.php index cfa4d6d..3c5a602 100644 --- a/applications/core/controllers/index.php +++ b/applications/core/controllers/index.php @@ -71,11 +71,11 @@ public function post_login() */ public function get_logout() { - Feather\Auth::logout(); + $this->auth->logout(); // Return to the generated logout URL, this can change depending on the authentication // driver that is being used. - return Feather\Redirect::after_logout(); + return $this->redirect->after_logout(); } /** @@ -86,15 +86,15 @@ public function get_logout() */ public function get_register() { - if(Feather\Config::get('feather: auth.driver', 'internal') != 'internal') + if($this->config->get('feather: db.auth.driver', 'internal') != 'internal') { - return Feather\Redirect::before_register(); + return $this->redirect->before_register(); } - Feather\Breadcrumbs::drop(__('feather::titles.register')); + $this->breadcrumbs->drop(__('feather core::titles.register')); - $this->layout->with('title', __('feather::titles.register')) - ->nest('content', 'feather::user.register'); + $this->layout->with('title', __('feather core::titles.register')) + ->nest('content', 'feather core::user.register'); } /** diff --git a/applications/core/language/en/register.php b/applications/core/language/en/register.php new file mode 100644 index 0000000..5f53147 --- /dev/null +++ b/applications/core/language/en/register.php @@ -0,0 +1,81 @@ + 'Your registration was unsuccessful. Please try again later or get in touch with an admin.', + + /* + |-------------------------------------------------------------------------- + | Register Validation Messages + |-------------------------------------------------------------------------- + | + | English language messages for individual field errors. + | + */ + + 'messages' => array( + 'username' => array( + 'is_required' => 'You did not enter a username.', + 'too_short' => 'Your username must be at least :length characters.', + 'too_long' => 'Your username must be less than :length characters.', + 'is_invalid' => 'Your username contained invalid characters.', + 'already_exists' => 'An account already exists with that username.' + ), + 'password' => array( + 'is_required' => 'You did not enter a password.', + 'too_short' => 'Your password must be at least :length characters.', + 'too_long' => 'Your password must be less than :length characters.', + 'did_not_match' => 'The passwords you entered did not match.', + ), + 'password_confirmation' => array( + 'is_required' => 'You did not confirm your password.', + ), + 'email' => array( + 'is_required' => 'You did not enter an e-mail address.', + 'invalid' => 'The e-mail you entered is invalid.', + 'already_exists' => 'An account already exists with that e-mail.', + 'does_not_exist' => 'The e-mail you entered does not exist.' + ), + 'rules' => array( + 'not_accepted' => 'You must read and accept the community rules.' + ) + ), + + /* + |-------------------------------------------------------------------------- + | Register Form Labels + |-------------------------------------------------------------------------- + | + | English language messages for register page form labels. + | + */ + + 'labels' => array( + 'password_confirmation' => array( + 'title' => 'Confirm Password' + ), + 'rules' => array( + 'helper' => 'I have read and agree to the :link.' + ) + ) + + +); \ No newline at end of file diff --git a/applications/core/models/ness.php b/applications/core/models/ness.php new file mode 100644 index 0000000..10b92ee --- /dev/null +++ b/applications/core/models/ness.php @@ -0,0 +1,477 @@ + + * + * @author Jason Lewis + * @version 1.0.2 + */ +class Ness extends Base { + + /** + * Stores the Ness related data to avoid cluttering the Eloquent-space. + * + * @var bool + */ + protected $ness = array( + 'deleting' => false, + 'crumbs' => null + ); + + /** + * Place a node after another node in the tree. + * + * + * $node = Ness::find(4); + * + * // Place the node after the node with an ID of 2. + * $node->after(2); + * + * // Place the node after another Ness object. + * $node->after(Ness::find(2)); + * + * + * @param object|int $node + * @return object + */ + public function after($node) + { + return $this->sibling($node, 'after'); + } + + /** + * Place a node before another node in the tree. + * + * + * $node = Ness::find(4); + * + * // Place the node before the node with an ID of 2. + * $node->before(2); + * + * // Place the node before another Ness object. + * $node->before(Ness::find(2)); + * + * + * @param object|int $node + * @return object + */ + public function before($node) + { + return $this->sibling($node, 'before'); + } + + /** + * Makes a node a sibling of another node, the position can either be before or after. + * + * @param object|int $node + * @param string $position + * @return object + */ + protected function sibling($node, $position) + { + $node = $this->check($node); + + // If our node currently exists we need to fake its death, shifting it outside of the + // tree momenterily. + if($this->exists) + { + $this->fake(); + + // Refresh the node we are placing it after so we can get the latest right value. + $node->refresh(); + + // And now revive the bastard to the new left position. + $this->revive(($position == 'after') ? $node->rgt : $node->lft - 1); + } + + // If the node doesn't exist yet in the tree we need to set the left and right values. + // If our position is BEFORE our left value will be the siblings current left value, if + // it is AFTER then it will be the right value plus 1. The right value will always be + // the left value plus 1. + else + { + $this->lft = ($position == 'after') ? $node->rgt + 1 : $node->lft; + + $this->rgt = $this->lft + 1; + + // We need to make an adjustment to the tree so we can fit out brand new node in. + // Once done we'll save our new node. + $this->adjustment($this->lft) + ->save(); + } + + return $this; + } + + /** + * Nest the current node on the supplied node. + * + * + * $node = Ness::find(3); + * + * // Nest the node on node with an ID of 5. + * $node->nest(5); + * + * // Nest the node on another Ness object. + * $node->nest(Ness::find(5)); + * + * + * @param object|int $node + * @return object + */ + public function nest($node) + { + $node = $this->check($node); + + if(!$node->exists) + { + throw new Exception('The node you are nesting on has not been saved yet.'); + } + + // If our node currently exists we need to fake its death, shifting it outside of the + // tree momenterily. + if($this->exists) + { + $this->fake(); + + // Refresh the node we are placing it after so we can get the latest right value. + $node->refresh(); + + // And now revive the bastard to the parent nodes right value. + $this->revive($node->rgt - 1); + + // Refresh our node. + $this->refresh(); + } + + // If the node doesn't exist yet we need set the left and right values. The new node will + // always be placed at the end of any other children. + else + { + $this->lft = $node->rgt; + + $this->rgt = $this->lft + 1; + + // We need to make an adjustment to the tree so we can fit out brand new node in. + // Once done we'll save our new node. + $this->adjustment($this->lft - 1) + ->save(); + } + } + + /** + * Parent node abandons children nodes, they become orphans of the next parent. + * + * + * $parent = Ness::find(4); + * + * // Abandon the children making them orphans to the parents parent. + * $parent->abandon(); + * + * // Abandon the children then move the node to another position. + * $parent->abandon()->before(2); + * + * + * @return object + */ + public function abandon() + { + if(!$this->parent()) + { + throw new Exception('Could not abandon children because the node is not a parent.'); + } + + // Shift all the children of the node forward one position. + static::where('lft', 'BETWEEN', DB::raw(($this->lft + 1) . ' AND ' . ($this->rgt - 1)))->update(array( + 'lft' => DB::raw('lft + 1'), + 'rgt' => DB::raw('rgt + 1') + )); + + $this->rgt = $this->lft + 1; + + $this->save(); + + return $this; + } + + /** + * Deletes a node and any children of that node unless the node has abandoned its children. + * + * + * $node = Ness::find(3); + * + * // Delete the node including it's children if it has any. + * $node->delete(); + * + * // To save the children abandon them before deleting. + * $node->abandon()->delete(); + * + * + * @return object + */ + public function delete() + { + if($this->exists) + { + // If we are deleting then we'll allow Eloquent to take care of the rest. + if($this->ness['deleting']) + { + parent::delete(); + + $this->ness['deleting'] = false; + } + + // If not deleting yet we'll need to check what it is we are actually deleting. + else + { + $this->ness['deleting'] = true; + + // If the node is a parent then we are going to delete all the children along with it. + // To delete a node without its children be sure to abandon the children before deleting. + if($this->parent()) + { + static::where('lft', 'BETWEEN', DB::raw($this->lft . ' AND ' . $this->rgt))->delete(); + } + + // If it's not a parent node then just delete the node, simple really! + else + { + $this->ness['deleting'] = false; + + // Allow Eloquent to delete the actual node. + parent::delete(); + } + + // Now to re-adjust the tree removing the gap we have left. + $this->adjustment($this->lft, ($this->width() + 1) * -1); + } + } + + return $this; + } + + /** + * Return an array of crumbs that led to the current node. + * + * + * $node = Ness::find(3); + * + * $crumbs = $node->crumbs(); + * + * + * @return array + */ + public function crumbs() + { + if(is_null($this->ness['crumbs'])) + { + $this->ness['crumbs'] = static::where('lft', '<', $this->lft)->where('rgt', '>', $this->rgt)->order_by('lft', 'asc')->get(); + } + + return $this->ness['crumbs']; + } + + /** + * Return an array of children for a node. + * + * + * $node = Ness::find(3); + * + * $children = $node->children(); + * + * + * @return array + */ + public function children() + { + if(!$this->parent()) + { + return $children = array(); + } + else + { + $sql = "SELECT node.*, (COUNT( parent.name ) -1) AS depth + FROM places AS node + CROSS JOIN places AS parent + WHERE node.lft > {$this->lft} + AND node.rgt < {$this->rgt} + AND node.lft BETWEEN parent.lft AND parent.rgt + GROUP BY node.id + ORDER BY node.lft"; + + $children = array(); + + foreach(DB::connection(static::$connection)->query($sql) as $object) + { + $children[] = new static((array) $object, true); + } + } + + return $children; + } + + /** + * Refreshes the node by getting the latest left and right values from the database. + * + * @return object + */ + public function refresh() + { + $updated = static::find($this->id); + + $this->fill(array( + 'lft' => $updated->lft, + 'rgt' => $updated->rgt + )); + + return $this; + } + + /** + * Fakes the removal of the node and its children from the database so that a node + * can be shifted around. + * + * @return object + */ + protected function fake() + { + // To fake the removal we'll shift the node outside of the tree by subtracting the + // right value from the node and its children. + static::where('lft', 'BETWEEN', DB::raw("{$this->lft} AND {$this->rgt}"))->update(array( + 'lft' => DB::raw("lft - {$this->rgt}"), + 'rgt' => DB::raw("rgt - {$this->rgt}") + )); + + // Adjust all those from our current left value by reducing a gap. Any nodes that have + // left and right values greater than or equal to this node will have 2 values taken + // from both their left and right values, thus 'removing' this node from the tree. + return $this->adjustment($this->lft, ($this->width() + 1) * -1); + } + + /** + * Revives a fake death (weird) node and all its children. Hallelujah! + * + * @param int $on + * @return object + */ + protected function revive($on) + { + // Make the adjustment to fit the node back into the tree. + $this->adjustment($on); + + // To revive the faked death we need to shift the node back into the tree by adding the + // left value and width of the node to those nodes between the negative width and zero. + $left = $this->width() * -1; + + $adjustment = $on + $this->width() + 1; + + static::where('lft', 'BETWEEN', DB::raw("{$left} AND 0"))->update(array( + 'lft' => DB::raw("lft + {$adjustment}"), + 'rgt' => DB::raw("rgt + {$adjustment}") + )); + + return $this; + } + + /** + * Perform an adjustment of all nodes from a given amount and of a given width. + * + * @param int $from + * @param int $width + * @return object + */ + protected function adjustment($from, $width = null) + { + if(is_null($width)) + { + $width = $this->width() + 1; + } + + static::where('lft', '>', $from)->update(array('lft' => DB::raw("lft + {$width}"))); + + static::where('rgt', '>', $from)->update(array('rgt' => DB::raw("rgt + {$width}"))); + + return $this; + } + + /** + * A node is a parent when its width is greater than one. + * + * + * if($node->parent()) + * { + * // The node is a parent. + * } + * + * + * @return bool + */ + public function parent() + { + return $this->width() > 1; + } + + /** + * Returns the width of the node in the tree. + * + * @return int + */ + public function width() + { + return $this->rgt - $this->lft; + } + + /** + * Checks a node to make sure it's valid, if not attempts to make it valid. + * + * @param object|int $node + * @return object + */ + protected function check($node) + { + if(is_numeric($node)) + { + $node = static::find($node); + } + + return $node; + } + + /** + * Overload the Eloquent save method so we can set the left and right values of a node if they aren't + * set by one of the other methods. + * + * @return bool + */ + public function save() + { + if(!$this->exists and is_null($this->lft)) + { + // If this is the first node in the tree then it gets the left and right values of 1 and 2. + if(!$after = static::select(array('lft', 'rgt'))->order_by('rgt', 'desc')->take(1)->first()) + { + $this->fill(array( + 'lft' => 1, + 'rgt' => 2 + )); + } + else + { + $this->fill(array( + 'lft' => $after->rgt + 1, + 'rgt' => $after->rgt + 2 + )); + } + + } + + return parent::save(); + } + +} \ No newline at end of file diff --git a/applications/core/models/place.php b/applications/core/models/place.php new file mode 100644 index 0000000..850f5e1 --- /dev/null +++ b/applications/core/models/place.php @@ -0,0 +1,49 @@ +has_many('Feather\\Models\\Permission', 'place_id'); + } + + /** + * A place has many moderators. + * + * @return object + */ + public function moderators() + { + return $this->has_many('Feather\\Models\\Place\\Moderator', 'place_id'); + } + + /** + * A place has many discussions. + * + * @return object + */ + public function discussions() + { + return $this->has_many('Feather\\Models\\Discussion', 'place_id'); + } + +} \ No newline at end of file diff --git a/applications/core/routes.php b/applications/core/routes.php index 0aa3276..7acd1d0 100644 --- a/applications/core/routes.php +++ b/applications/core/routes.php @@ -9,5 +9,41 @@ */ Route::any('(:bundle)/login', array('as' => 'login', 'uses' => 'feather core::index@login')); -Route::any('(:bundle)/register', array('as' => 'register', 'uses' => 'feather core::login@register')); -Route::get('(:bundle)/logout', array('as' => 'logout', 'uses' => 'feather core::login@logout')); \ No newline at end of file +Route::any('(:bundle)/register', array('as' => 'register', 'uses' => 'feather core::index@register')); +Route::get('(:bundle)/logout', array('as' => 'logout', 'uses' => 'feather core::index@logout')); + +/* +|-------------------------------------------------------------------------- +| Members and Profiles +|-------------------------------------------------------------------------- +*/ + +Route::get('(:bundle)/members', array('as' => 'members', 'uses' => 'feather::members@index')); +Route::get('(:bundle)/profile/(:any)', array('as' => 'profile', 'uses' => 'feather::profile@index')); + +/* +|-------------------------------------------------------------------------- +| Places +|-------------------------------------------------------------------------- +*/ + +Route::get('(:bundle)/place/(:num)-(:any)/(:page?)', array('as' => 'place', 'uses' => 'feather::place@place')); + +/* +|-------------------------------------------------------------------------- +| Discussions and Starting Discussions +|-------------------------------------------------------------------------- +*/ + +Route::any('(:bundle)/discussion/start', array('as' => 'start.discussion', 'uses' => 'feather::discussion@start')); +Route::any('(:bundle)/place/(:num)-(:any)/start', 'feather::discussion@start'); +Route::any('(:bundle)/discussion/(:num)-(:any)/(:page?)', array('as' => 'discussion', 'uses' => 'feather::discussion@index')); +Route::any('(:bundle)/discussion/(:num)-(:any)/edit', array('as' => 'discussion.edit', 'uses' => 'feather::discussion@edit')); + +/* +|-------------------------------------------------------------------------- +| Rules +|-------------------------------------------------------------------------- +*/ + +Route::get('(:bundle)/rules', array('as' => 'rules', 'uses' => 'feather::home@rules')); \ No newline at end of file diff --git a/applications/core/views/menu/user.blade.php b/applications/core/views/menu/user.blade.php new file mode 100644 index 0000000..39c771c --- /dev/null +++ b/applications/core/views/menu/user.blade.php @@ -0,0 +1,33 @@ +
      + + + @if(Feather\Auth::is('admin')) + + @endif + + + + + +
    \ No newline at end of file diff --git a/applications/core/views/user/register.blade.php b/applications/core/views/user/register.blade.php new file mode 100644 index 0000000..df0bde6 --- /dev/null +++ b/applications/core/views/user/register.blade.php @@ -0,0 +1,82 @@ +

    {{ __('feather core::titles.register') }}

    + +{{ Form::open(URI::full(), 'post') }} + +
    + +
    + +
    + +
    + +
    {{ Form::label('username', __('feather core::common.username')) }}
    +
    + {{ Form::text('username', Input::old('username'), array('autocomplete' => 'off', 'tabindex' => 1)) }} + + @error('username') +
    + +
    {{ Form::label('email', __('feather core::common.email')) }}
    +
    + {{ Form::text('email', Input::old('email'), array('autocomplete' => 'off', 'tabindex' => 1)) }} + + @error('email') +
    + + @event('view: before register.rules') + + @if(Feather\Config::get('feather: db.registration.rules', 0)) + +
    +
    + + + @error('rules') +
    + + @endif + + @event('view: after register.rules') + +
    + +
    + +
    + +
    + +
    + +
    +
    {{ Form::label('password', __('feather core::common.password')) }}
    +
    + {{ Form::password('password', array('autocomplete' => 'off', 'tabindex' => 1)) }} + + @error('password') +
    + +
    {{ Form::label('password_confirmation', __('feather core::register.labels.password_confirmation.title')) }}
    +
    + {{ Form::password('password_confirmation', array('autocomplete' => 'off', 'tabindex' => 1)) }} + + @error('password_confirmation') +
    +
    + +
    + +
    + +
    + +
    + {{ Form::submit(__('feather core::common.register'), array('class' => 'btn btn-primary btn-big', 'tabindex' => 2)) }} +
    + +{{ Form::token() . Form::close() }} \ No newline at end of file From 7194878b01b9fd458f34d9ab959e43c5ab735db5 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 21:41:43 +0930 Subject: [PATCH 115/136] Fixed bug with the publisher, still using plugins. Signed-off-by: Jason Lewis --- tasks/publish.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/publish.php b/tasks/publish.php index 087a80f..99d59ba 100644 --- a/tasks/publish.php +++ b/tasks/publish.php @@ -26,7 +26,7 @@ public function gear($parameters = array()) { $publish = empty($parameters) ? null : array_shift($parameters); - $this->publish($publish, path('gears') . DS, 'plugins'); + $this->publish($publish, path('gears') . DS, 'gears'); return true; } From 97d1320d5fe28b07d4e6f816fe7d6c552c0ae290 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 21:42:16 +0930 Subject: [PATCH 116/136] Bootstrap the gears when constructed. Signed-off-by: Jason Lewis --- components/gear/manager.php | 43 +++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/components/gear/manager.php b/components/gear/manager.php index 0593b51..d3703f0 100644 --- a/components/gear/manager.php +++ b/components/gear/manager.php @@ -2,13 +2,15 @@ use Str; use Event; +use Cache; +use Bundle; use Request; use FilesystemIterator; use Feather\Core\Gear; use InvalidArgumentException; -use Feather\Components\Foundation\Component; +use Feather\Components\Foundation; -class Manager extends Component { +class Manager extends Foundation\Component { /** * Registered Gears. @@ -24,6 +26,41 @@ class Manager extends Component { */ public $started = array(); + /** + * Bootstraps the Gears, loads from database and registers with the manager. + * + * @param Feather\Components\Foundation\Application $feather + * @return void + */ + public function __construct(Foundation\Application $feather) + { + parent::__construct($feather); + + Cache::forget('gears'); + + $manager = $this; + + return Cache::sear('gears', function() use ($manager) + { + foreach(Gear::enabled() as $gear) + { + Bundle::register($gear->identifier, array( + 'location' => 'path: ' . path('gears') . $gear->identifier, + 'handles' => trim(str_replace('(:feather)', Bundle::option('feather', 'handles'), $gear->handles), '/'), + 'auto' => (bool) $gear->auto, + 'autoloads' => (array) json_decode($gear->loads) + )); + + if($gear->auto) + { + Bundle::start($gear->identifier); + } + + $manager->register($gear); + } + }); + } + /** * Register a Gear with the manager. * @@ -115,6 +152,8 @@ public function start($gear) if(class_exists($class)) { $this->started[$gear][$name] = new $class; + + $this->started[$gear][$name]->application($this->feather); } } } From e6a6492df0e7f197d10286e6c6dbb6e31a9d8f0a Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 21:42:42 +0930 Subject: [PATCH 117/136] Register the Feather application with Gears. Signed-off-by: Jason Lewis --- components/gear/foundation.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/gear/foundation.php b/components/gear/foundation.php index 2abb1ac..5ce2ea2 100644 --- a/components/gear/foundation.php +++ b/components/gear/foundation.php @@ -4,6 +4,13 @@ abstract class Foundation { + /** + * The feather instance. + * + * @var Feather\Components\Foundation\Application + */ + protected $feather; + /** * Listen for an event and fire a method or closure handler. * @@ -86,4 +93,15 @@ public function disabled(){} */ public function remove(){} + /** + * Set the Feather application instance. + * + * @param Feather\Components\Foundation\Application $feather + * @return void + */ + public function application(\Feather\Components\Foundation\Application $feather) + { + $this->feather = $feather; + } + } \ No newline at end of file From 9f3e744f59a45a02305efd7e8b2cfbd9dd692619 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 21:45:12 +0930 Subject: [PATCH 118/136] Initial commit of reCAPTCHA gear. Signed-off-by: Jason Lewis --- gears/recaptcha/classes/recaptcha.php | 276 ++++++++++++++++++++++ gears/recaptcha/config/keys.php | 25 ++ gears/recaptcha/gear.json | 12 + gears/recaptcha/language/en/messages.php | 15 ++ gears/recaptcha/public/css/recaptcha.css | 36 +++ gears/recaptcha/public/img/sprite.png | Bin 0 -> 2842 bytes gears/recaptcha/recaptcha.gear.php | 79 +++++++ gears/recaptcha/views/recaptcha.blade.php | 41 ++++ 8 files changed, 484 insertions(+) create mode 100644 gears/recaptcha/classes/recaptcha.php create mode 100644 gears/recaptcha/config/keys.php create mode 100644 gears/recaptcha/gear.json create mode 100644 gears/recaptcha/language/en/messages.php create mode 100644 gears/recaptcha/public/css/recaptcha.css create mode 100644 gears/recaptcha/public/img/sprite.png create mode 100644 gears/recaptcha/recaptcha.gear.php create mode 100644 gears/recaptcha/views/recaptcha.blade.php diff --git a/gears/recaptcha/classes/recaptcha.php b/gears/recaptcha/classes/recaptcha.php new file mode 100644 index 0000000..b588839 --- /dev/null +++ b/gears/recaptcha/classes/recaptcha.php @@ -0,0 +1,276 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; + } + + + + /** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ + protected static function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = static::_recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; + } + + + + /** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + * @return string - The HTML to be embedded in the user's form. + */ + public static function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) + { + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; + } + + /** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ + public static function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) + { + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = static::_recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + + } + + /** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ + public static function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "https://www.google.com/recaptcha/admin/create?" . static::_recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); + } + + protected static function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); + } + + /* Mailhide related code */ + + protected static function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=static::_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + + protected static function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); + } + + /* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ + protected static function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail =static::_recaptcha_aes_encrypt ($email, $ky); + + return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . static::_recaptcha_mailhide_urlbase64 ($cryptmail); + } + + /** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ + protected static function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; + } + + /** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://www.google.com/recaptcha/mailhide/apikey + */ + protected static function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = static::_recaptcha_mailhide_email_parts ($email); + $url = static::recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + + } + +} + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + +?> diff --git a/gears/recaptcha/config/keys.php b/gears/recaptcha/config/keys.php new file mode 100644 index 0000000..cdcd573 --- /dev/null +++ b/gears/recaptcha/config/keys.php @@ -0,0 +1,25 @@ + '6Lc_aM8SAAAAAKTZc0HhemSXaccrURGDSGazUS2t', + + /* + |-------------------------------------------------------------------------- + | reCAPTCHA Private Key + |-------------------------------------------------------------------------- + | + | The private key given to you by the reCAPTCHA API. + | + */ + 'private' => '6Lc_aM8SAAAAAGWaYHsV0een1bcuLKGEx7xD9rcr' + +); \ No newline at end of file diff --git a/gears/recaptcha/gear.json b/gears/recaptcha/gear.json new file mode 100644 index 0000000..1a0c0ac --- /dev/null +++ b/gears/recaptcha/gear.json @@ -0,0 +1,12 @@ +{ + "name":"reCAPTCHA", + "description":"reCAPTCHA is a bot-prevention tool provided by Google.", + "version":"1.0", + "author": + { + "name":"Jason Lewis", + "email":"jason.lewis1991@gmail.com", + "website":"http://jasonlewis.me" + }, + "dependencies":{} +} \ No newline at end of file diff --git a/gears/recaptcha/language/en/messages.php b/gears/recaptcha/language/en/messages.php new file mode 100644 index 0000000..d703abd --- /dev/null +++ b/gears/recaptcha/language/en/messages.php @@ -0,0 +1,15 @@ + 'You did not enter the security confirmation.', + 'is_incorrect' => 'The security confirmation could not be matched.' +); \ No newline at end of file diff --git a/gears/recaptcha/public/css/recaptcha.css b/gears/recaptcha/public/css/recaptcha.css new file mode 100644 index 0000000..426fcb1 --- /dev/null +++ b/gears/recaptcha/public/css/recaptcha.css @@ -0,0 +1,36 @@ +#recaptcha_image { + margin: 4px 0 10px; +} + +#recaptcha_info { + margin: 10px 0; + font-weight: bold; + font-size: 11px; +} + +.join-recaptcha { + clear: left; + list-style-type: none; + padding: 0; + margin: 10px 0 15px; +} + +.join-recaptcha > li { + padding: 0; + margin: 0 8px 0 0; + width: 16px; + height: 16px; + display: inline-block; + background: url('../img/sprite.png') no-repeat 0 0px; +} + +.join-recaptcha > li.refresh { background-position: -32px 0; } +.join-recaptcha > li.audio { background-position: 0 0; } +.join-recaptcha > li.image { background-position: -48px 0; } +.join-recaptcha > li.help { background-position: -16px 0; } + +.join-recaptcha > li > a { + display: block; + width: 16px; + height: 16px; +} \ No newline at end of file diff --git a/gears/recaptcha/public/img/sprite.png b/gears/recaptcha/public/img/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..485031b0b0d539eefb34ed4efaf722a03dcaa79a GIT binary patch literal 2842 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 literal 0 HcmV?d00001 diff --git a/gears/recaptcha/recaptcha.gear.php b/gears/recaptcha/recaptcha.gear.php new file mode 100644 index 0000000..195063f --- /dev/null +++ b/gears/recaptcha/recaptcha.gear.php @@ -0,0 +1,79 @@ +listen('view: before register.rules', 'display'); + + $this->listen('assets: change styles', 'styling'); + + $this->listen('validation: before register', 'validation'); + } + + /** + * Display the reCAPTCHA view within the registration page. + * + * @return string + */ + public function display() + { + return View::make('gear: recaptcha recaptcha'); + } + + /** + * Inject the reCAPTCHA styles into the themes asset container. + * + * @param object $container + * @return void + */ + public function styling($container) + { + if(URI::is('register')) + { + $container->add('gear: recaptcha', '../../gears/recaptcha/css/recaptcha.css'); + } + } + + /** + * Add the reCAPTCHA validation rules and messages to the registration validation. + * + * @param object $validator + * @return void + */ + public function validation($validator) + { + $validator->rule('recaptcha_response_field', array('required', 'recaptcha')) + ->message('recaptcha_response_field.required', 'plugin: recaptcha messages.is_required') + ->message('recaptcha_response_field.recaptcha', 'plugin: recaptcha messages.is_incorrect'); + + // Map to the Recaptcha library so we can register our validation rule with the + // validation. + Autoloader::map(array( + 'Recaptcha\\Recaptcha' => __DIR__ . DS . 'classes' . DS . 'recaptcha.php' + )); + + Validator::register('recaptcha', function($attribute, $value, $parameters) + { + $private = $this->feather['config']->get('plugin: recaptcha keys.private'); + + $recaptcha = Recaptcha\Recaptcha::recaptcha_check_answer($private, Request::ip(), Input::get('recaptcha_challenge_field'), $value); + + return $recaptcha->is_valid; + }); + } + +} \ No newline at end of file diff --git a/gears/recaptcha/views/recaptcha.blade.php b/gears/recaptcha/views/recaptcha.blade.php new file mode 100644 index 0000000..3bee47c --- /dev/null +++ b/gears/recaptcha/views/recaptcha.blade.php @@ -0,0 +1,41 @@ + + +
    {{ Form::label('recaptcha_response_field', 'Security Confirmation') }}
    +
    + + + {{ HTML::script('http://www.google.com/recaptcha/api/challenge?k=' . Feather\Config::get('gear: recaptcha keys.public')) }} +
    \ No newline at end of file From 568446f1c6454fdd2020ee7a33732294ec9129ca Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:08:14 +0930 Subject: [PATCH 119/136] Fixed small bug with validator and custom error messages. Signed-off-by: Jason Lewis --- components/validation/validator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/validation/validator.php b/components/validation/validator.php index 0a4c117..5c170dd 100644 --- a/components/validation/validator.php +++ b/components/validation/validator.php @@ -117,7 +117,7 @@ public function passes() foreach($responses as $response => $replacements) { - $responses[$response] = __("{$this->application}::{$response}", $replacements)->get(); + $responses[$response] = __("feather {$this->application}::{$response}", $replacements)->get(); } throw new FeatherValidationException(new Messages($responses)); @@ -172,7 +172,7 @@ public function message($rule, $message) list($message, $replacements) = $message; } - $this->messages[str_replace('.', '_', $rule)] = __("{$this->application}::{$message}", $replacements)->get(); + $this->messages[str_replace('.', '_', $rule)] = __("feather {$this->application}::{$message}", $replacements)->get(); return $this; } From 53a04c72cd2d177c0fe66f79035433fb7f68a79d Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:29:07 +0930 Subject: [PATCH 120/136] Pass the feather instance to validation closures. Signed-off-by: Jason Lewis --- components/validation/validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/validation/validator.php b/components/validation/validator.php index 5c170dd..b7fe394 100644 --- a/components/validation/validator.php +++ b/components/validation/validator.php @@ -111,7 +111,7 @@ public function passes() { // If a response is returned from the closure then we have a custom error messages // to throw with the exception. - if($responses = call_user_func_array(reset($this->validating), array($this))) + if($responses = call_user_func_array(reset($this->validating), array($this, $this->feather))) { if(!is_array(reset($responses))) $responses = array(array_shift($responses) => array()); From f937ff7376436fc14163212fba057a5a2d8e65ec Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:44:10 +0930 Subject: [PATCH 121/136] Bootstrap things such as language event override and fixed some issues. Signed-off-by: Jason Lewis --- bootstrap/languages.php | 42 ++++++++++++++++++++++++++++++++++++++++ bootstrap/validators.php | 16 +++++++++++++++ bootstrap/views.php | 2 +- start.php | 4 ++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 bootstrap/languages.php create mode 100644 bootstrap/validators.php diff --git a/bootstrap/languages.php b/bootstrap/languages.php new file mode 100644 index 0000000..5f2cf62 --- /dev/null +++ b/bootstrap/languages.php @@ -0,0 +1,42 @@ +has$2 ? view("feather core::error.inline", array("error" => $errors->first$1)) : null; ?>', $value); + return preg_replace($matcher, '$1has$2 ? view("feather core::error.inline", array("error" => $errors->first$2)) : null; ?>', $value); }); /* diff --git a/start.php b/start.php index 4b114c3..62931c4 100644 --- a/start.php +++ b/start.php @@ -89,6 +89,10 @@ require path('feather') . 'bootstrap' . DS . 'ioc' . EXT; +require path('feather') . 'bootstrap' . DS . 'validators' . EXT; + +require path('feather') . 'bootstrap' . DS . 'languages' . EXT; + /* |-------------------------------------------------------------------------- | Load Feather Components From ad52bef2189295f8de758bd6738188bfe8e38315 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:44:26 +0930 Subject: [PATCH 122/136] Fixed a couple plugin references. Signed-off-by: Jason Lewis --- gears/recaptcha/recaptcha.gear.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gears/recaptcha/recaptcha.gear.php b/gears/recaptcha/recaptcha.gear.php index 195063f..8c0ae2c 100644 --- a/gears/recaptcha/recaptcha.gear.php +++ b/gears/recaptcha/recaptcha.gear.php @@ -21,7 +21,7 @@ public function __construct() $this->listen('assets: change styles', 'styling'); - $this->listen('validation: before register', 'validation'); + $this->listen('validation: before auth.register', 'validation'); } /** @@ -57,20 +57,21 @@ public function styling($container) public function validation($validator) { $validator->rule('recaptcha_response_field', array('required', 'recaptcha')) - ->message('recaptcha_response_field.required', 'plugin: recaptcha messages.is_required') - ->message('recaptcha_response_field.recaptcha', 'plugin: recaptcha messages.is_incorrect'); + ->message('recaptcha_response_field.required', 'gear: recaptcha messages.is_required') + ->message('recaptcha_response_field.recaptcha', 'gear: recaptcha messages.is_incorrect'); - // Map to the Recaptcha library so we can register our validation rule with the - // validation. + // Map to the Recaptcha library so we can register our validation rule with the validation. Autoloader::map(array( 'Recaptcha\\Recaptcha' => __DIR__ . DS . 'classes' . DS . 'recaptcha.php' )); - Validator::register('recaptcha', function($attribute, $value, $parameters) + $feather = $this->feather; + + Validator::register('recaptcha', function($attribute, $value, $parameters) use ($feather) { - $private = $this->feather['config']->get('plugin: recaptcha keys.private'); + $private = $feather['config']->get('gear: recaptcha keys.private'); - $recaptcha = Recaptcha\Recaptcha::recaptcha_check_answer($private, Request::ip(), Input::get('recaptcha_challenge_field'), $value); + $recaptcha = \Recaptcha\Recaptcha::recaptcha_check_answer($private, Request::ip(), Input::get('recaptcha_challenge_field'), $value); return $recaptcha->is_valid; }); From 592b8e5f5013ed29fb36e9b2ab524c0ca3c1b919 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:44:44 +0930 Subject: [PATCH 123/136] Polishing off the authentication side of things. Signed-off-by: Jason Lewis --- applications/core/controllers/index.php | 21 +++++----- applications/core/language/en/register.php | 2 +- applications/core/models/gear.php | 10 +++++ applications/core/models/user.php | 42 +++++++++++++++++++ applications/core/routes.php | 2 +- applications/core/validation/auth.php | 28 ++++++++++++- .../core/views/error/inline.blade.php | 2 + applications/core/views/misc/rules.blade.php | 3 ++ .../core/views/user/register.blade.php | 4 +- 9 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 applications/core/views/error/inline.blade.php create mode 100644 applications/core/views/misc/rules.blade.php diff --git a/applications/core/controllers/index.php b/applications/core/controllers/index.php index 3c5a602..99f8794 100644 --- a/applications/core/controllers/index.php +++ b/applications/core/controllers/index.php @@ -108,33 +108,32 @@ public function post_register() { try { - Feather\Validation\Auth\Register::passes(Input::all()); + $this->validator->get('auth.register')->against(Input::get())->passes(); } catch (FeatherValidationException $errors) { - return Feather\Redirect::to_self()->with_input()->with_errors($errors->get()); + return $this->redirect->to_self()->with_input()->with_errors($errors->get()); } try { - $user = Feather\Models\User::register(Input::all()); + $user = Feather\Core\User::register(Input::get()); } - catch (FeatherRegistrationException $errors) + catch (FeatherModelException $errors) { - return Feather\Redirect::to_self()->with_input()->alert('error', 'feather::register.failure'); + return $this->redirect->to_self()->with_input()->alert('error', 'feather core::register.failure'); } // Users must confirm their e-mail address once they have registered. We // will now send them an e-mail with their activation code. - if(Feather\Config::get('feather: registration.confirm_email')) + if($this->config->get('feather: db.registration.confirm_email')) { } - // Log the user in and redirect back to the home page. - Feather\Auth::login($user->id); + $this->auth->login($user); - return Feather\Redirect::to_previous('route: feather'); + return $this->redirect->to_previous('route: feather'); } /** @@ -144,8 +143,8 @@ public function post_register() */ public function get_rules() { - $this->layout->with('title', __('feather::titles.rules')) - ->nest('content', 'feather::misc.rules'); + $this->layout->with('title', __('feather core::titles.rules')) + ->nest('content', 'feather core::misc.rules'); } } \ No newline at end of file diff --git a/applications/core/language/en/register.php b/applications/core/language/en/register.php index 5f53147..d36e3cc 100644 --- a/applications/core/language/en/register.php +++ b/applications/core/language/en/register.php @@ -73,7 +73,7 @@ 'title' => 'Confirm Password' ), 'rules' => array( - 'helper' => 'I have read and agree to the :link.' + 'helper' => 'I\'ve read and agree to the :link.' ) ) diff --git a/applications/core/models/gear.php b/applications/core/models/gear.php index 0c702f0..2be0a72 100644 --- a/applications/core/models/gear.php +++ b/applications/core/models/gear.php @@ -16,4 +16,14 @@ class Gear extends Base { */ public static $timestamps = false; + /** + * Return all gears that have been enabled. + * + * @return array + */ + public static function enabled() + { + return static::where_enabled(1)->get(); + } + } \ No newline at end of file diff --git a/applications/core/models/user.php b/applications/core/models/user.php index 3345e9e..c7333e0 100644 --- a/applications/core/models/user.php +++ b/applications/core/models/user.php @@ -2,6 +2,7 @@ use Str; use Hash; +use Feather\Config; class User extends Base { @@ -70,4 +71,45 @@ public function get_avatar() return 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->get_attribute('email')))); } + /** + * Register a new user and return the new user object. + * + * @param array $input + * @param bool $activated + * @return object + */ + public static function register($input) + { + $user = new static(array( + 'username' => $input['username'], + 'password' => $input['password'], + 'email' => $input['email'] + )); + + if(Config::get('feather: db.registration.confirm_email')) + { + $user->fill(array( + 'activation_key' => Str::random(30), + 'activated' => 0 + )); + } + + if(!$user->save()) + { + throw new FeatherModelException; + } + + // Attach the users role. If the user needs to confirm their e-mail address they are given + // the confirming role status which has an ID of 4. If not then they receive the standard + // member role which has an ID of 2. + $user->roles()->attach(Config::get('feather: db.registration.confirm_email') ? 4 : 2); + + return $user; + } + + public static function edit($user, $input) + { + + } + } \ No newline at end of file diff --git a/applications/core/routes.php b/applications/core/routes.php index 7acd1d0..dceb16b 100644 --- a/applications/core/routes.php +++ b/applications/core/routes.php @@ -46,4 +46,4 @@ |-------------------------------------------------------------------------- */ -Route::get('(:bundle)/rules', array('as' => 'rules', 'uses' => 'feather::home@rules')); \ No newline at end of file +Route::get('(:bundle)/rules', array('as' => 'rules', 'uses' => 'feather core::index@rules')); \ No newline at end of file diff --git a/applications/core/validation/auth.php b/applications/core/validation/auth.php index 2e57bdf..2ac8b84 100644 --- a/applications/core/validation/auth.php +++ b/applications/core/validation/auth.php @@ -8,6 +8,32 @@ ->rule('password', 'required') ->message('username.required', 'login.messages.username.is_required') ->message('password.required', 'login.messages.password.is_required'); - } + }, + + 'register' => function($validator, $feather) + { + $validator->rule('username', array('required', 'min:3', 'max:20', 'alpha_dot', 'unique:users,username')) + ->rule('password', array('required', 'min:7', 'max:15', 'confirmed')) + ->rule('password_confirmation', array('required')) + ->rule('email', array('required', 'email', 'unique:users,email')) + ->message('username.required', 'register.messages.username.is_required') + ->message('username.min', array('register.messages.username.too_short', array('length' => 3))) + ->message('username.max', array('register.messages.username.too_long', array('length' => 20))) + ->message('username.alpha_dot', 'register.messages.username.is_invalid') + ->message('username.unique', 'register.messages.username.already_exists') + ->message('password.required', 'register.messages.password.is_required') + ->message('password.min', array('register.messages.password.too_short', array('length' => 7))) + ->message('password.max', array('register.messages.password.too_long', array('length' => 15))) + ->message('password.confirmed', 'register.messages.password.did_not_match') + ->message('password_confirmation.required', 'register.messages.password_confirmation.is_required') + ->message('email.required', 'register.messages.email.is_required') + ->message('email.email', 'register.messages.email.invalid') + ->message('email.unique', 'register.messages.email.already_exists'); + if($feather['config']->get('feather: db.registration.rules')) + { + $validator->rule('rules', 'accepted') + ->message('rules.accepted', 'register.messages.rules.not_accepted'); + } + } ); \ No newline at end of file diff --git a/applications/core/views/error/inline.blade.php b/applications/core/views/error/inline.blade.php new file mode 100644 index 0000000..e7e6422 --- /dev/null +++ b/applications/core/views/error/inline.blade.php @@ -0,0 +1,2 @@ + +{{ $error }} \ No newline at end of file diff --git a/applications/core/views/misc/rules.blade.php b/applications/core/views/misc/rules.blade.php new file mode 100644 index 0000000..877c2ff --- /dev/null +++ b/applications/core/views/misc/rules.blade.php @@ -0,0 +1,3 @@ +

    Community Rules

    + +{{ Feather\Gear\Markdown\Parse(Feather\Config::get('feather: db.registration.rules_message')) }} \ No newline at end of file diff --git a/applications/core/views/user/register.blade.php b/applications/core/views/user/register.blade.php index df0bde6..c049922 100644 --- a/applications/core/views/user/register.blade.php +++ b/applications/core/views/user/register.blade.php @@ -31,9 +31,9 @@
    @error('rules') From fcda61999078532696936e2281ff8ae61f3c0c4c Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:44:59 +0930 Subject: [PATCH 124/136] Couple style tweaks. Signed-off-by: Jason Lewis --- themes/basic/public/css/theme.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/themes/basic/public/css/theme.css b/themes/basic/public/css/theme.css index 19c0ac2..52ccd81 100644 --- a/themes/basic/public/css/theme.css +++ b/themes/basic/public/css/theme.css @@ -345,7 +345,8 @@ fieldset dd:after, */ .body h1, .body h2, -.body h3 { +.body h3, +.body h4 { margin: 0 0 16px; font-family: "Trebuchet MS", "Ubuntu", serif; font-size: 24px; @@ -355,6 +356,10 @@ fieldset dd:after, .body h2 { font-size: 20px; } .body h3 { font-size: 18px; } +.body h4 { + font-size: 14px; + display: inline; +} .body h1 a, .body h2 a { From e829f0a5ef4b041e5a8910c9cdfed9dc1f008039 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 22:46:31 +0930 Subject: [PATCH 125/136] Fixed an issue with the validation test. Signed-off-by: Jason Lewis --- tests/validation.test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation.test.php b/tests/validation.test.php index 2bfb020..7b9e46c 100644 --- a/tests/validation.test.php +++ b/tests/validation.test.php @@ -50,7 +50,7 @@ public function testCanAddMessage() { $validator = $this->feather['validator']->message('cat', 'dog'); - $this->assertEquals(array('cat' => 'core::dog'), $validator->messages); + $this->assertEquals(array('cat' => 'feather core::dog'), $validator->messages); } public function testValidationDoesPass() From 24388adc069ec90bbaa0887157611f0cf6cd24d1 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 23:25:10 +0930 Subject: [PATCH 126/136] Initial commit of OneConnect gear. Signed-off-by: Jason Lewis --- gears/oneconnect/gear.json | 12 + gears/oneconnect/oneconnect.gear.php | 147 ++++++++++++ gears/oneconnect/wordpress/README.md | 32 +++ .../wordpress/feather-oneconnect.php | 213 ++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 gears/oneconnect/gear.json create mode 100644 gears/oneconnect/oneconnect.gear.php create mode 100644 gears/oneconnect/wordpress/README.md create mode 100644 gears/oneconnect/wordpress/feather-oneconnect.php diff --git a/gears/oneconnect/gear.json b/gears/oneconnect/gear.json new file mode 100644 index 0000000..c61efe5 --- /dev/null +++ b/gears/oneconnect/gear.json @@ -0,0 +1,12 @@ +{ + "name":"OneConnect", + "description":"A Single Sign On gear to connect your application to your Feather installation.", + "version":"1.0.0", + "author": + { + "name":"Jason Lewis", + "email":"jason.lewis1991@gmail.com", + "website":"http://jasonlewis.me" + }, + "dependencies":{} +} \ No newline at end of file diff --git a/gears/oneconnect/oneconnect.gear.php b/gears/oneconnect/oneconnect.gear.php new file mode 100644 index 0000000..e2214f5 --- /dev/null +++ b/gears/oneconnect/oneconnect.gear.php @@ -0,0 +1,147 @@ +listen('auth: bootstrap oneconnect', 'bootstrap'); + } + + /** + * Bootstrap the OneConnect plugin. + * + * @return mixed + */ + public function bootstrap() + { + try + { + $this->compatible(); + } + catch (Exception $error) + { + return; + } + + if($credentials = $this->{$this->handler}()) + { + return $this->feather['sso']->authorize((array) $credentials); + } + } + + /** + * Fetch the authentication data using cURL. + * + * @return mixed + */ + protected function curl() + { + $ch = curl_init($this->feather['config']->get('feather: db.auth.authenticate_url')); + + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_COOKIE, $this->cookies()); + + $credentials = json_decode(curl_exec($ch)); + + curl_close($ch); + + return $credentials; + } + + /** + * Fetch the authentication data using file_get_contents(). + * + * @return mixed + */ + protected function file() + { + $opts = array( + 'http' => array( + 'method' => 'GET', + 'header' => 'Cookie: ' . $this->cookies() + ) + ); + + $context = stream_context_create($opts); + + $credentials = json_decode(file_get_contents($this->feather['config']->get('feather: db.auth.authenticate_url'), false, $context)); + + return $credentials; + } + + /** + * Returns a cookie string to be sent along with headers. + * + * @return string + */ + protected function cookies() + { + $cookies = array(); + + foreach(Request::foundation()->cookies->all() as $key => $value) + { + $cookies[] = "{$key}={$value}"; + } + + return implode('; ', $cookies); + } + + /** + * OneConnect needs either cURL or file_get_contents to fetch the authentication data. + * When activating we make sure the plugin is compatible with the environment. + * + * @return void + */ + protected function compatible() + { + if(!function_exists('curl_init')) + { + if(!function_exists('file_get_contents') or !ini_get('allow_url_fopen')) + { + throw new Exception('OneConnect is not compatible with your environment, cURL or allow_url_fopen is required.'); + } + else + { + $this->handler = 'file'; + } + } + else + { + $this->handler = 'curl'; + } + } + + /** + * Runs when the OneConnect plugin is activated. + * + * @return bool + */ + public function activate() + { + try + { + $this->compatible(); + } + catch (Exception $error) + { + throw $error; + } + } + +} \ No newline at end of file diff --git a/gears/oneconnect/wordpress/README.md b/gears/oneconnect/wordpress/README.md new file mode 100644 index 0000000..41f3f9d --- /dev/null +++ b/gears/oneconnect/wordpress/README.md @@ -0,0 +1,32 @@ +# Feather OneConnect for WordPress + +Feather OneConnect for WordPress enables Single Sign On between your Feather installation and WordPress. Your WordPress user base can sign in and be automatically authenticated when they visit your Feather installation. + +### Installation + +Installing Feather OneConnect for WordPress is dead easy. + +1. Copy and paste the `feather-oneconnect.php` file to your WordPress plugin directory. +2. Enable the plugin in your WordPress administration panel. +3. Define your cookie path and domain in your `wp-config.php` file. Here's an example. + + // The cookie path should be just a slash. + define('COOKIEPATH', '/'); + + // The cookie domain should be the same as the one in your Feather installation. + define('COOKIE_DOMAIN', '.mydomain.com'); + +4. Enable OneConnect on your Feather installation. +5. Copy the Authenticate, Registration, Login, and Logout URLs from the Feather OneConnect for WordPress configuration screen and paste them into the corresponding boxes on the OneConnect configuration. + +When you next login to WordPress you'll become authenticated on your Feather installation. If something isn't working properly try deleting your cookies and starting fresh! + +### How It Works + +Feather OneConnect for WordPress only works when both WordPress and your Feather installation are on the same domain. This is because cookies are not available across domains. + +When a user logs in to WordPress they are given a cookie which is stored on their local machine. When the user visits Feather, OneConnect is able to access all the cookies for the current domain, so this includes the WordPress cookies. OneConnect then makes a request to your WordPress installation, it passes along the cookie information so that WordPress can authenticate the current user. WordPress then returns a couple bits of information about the user to OneConnect. + +Once OneConnect has this information it attempts to authorize the user on Feather. If the user has never visited the forum before, OneConnect will attempt to create a new account for the user with the same username and e-mail they have on WordPress. If, for some crazy reason, either of those is taken then OneConnect will show the user a form. The user then has the chance to create a new account, or link themselves to an existing account. Once done the user will be authenticated and can now access the forums. If the e-mail and username are available then the user will be immediately authenticated. + +This all happens very quickly and unless the user is prompted to create or link an account they won't even realise it's happening. \ No newline at end of file diff --git a/gears/oneconnect/wordpress/feather-oneconnect.php b/gears/oneconnect/wordpress/feather-oneconnect.php new file mode 100644 index 0000000..557e407 --- /dev/null +++ b/gears/oneconnect/wordpress/feather-oneconnect.php @@ -0,0 +1,213 @@ +authenticated(); + } + + // Create the admin menu interface for our plugin. + if(is_admin()) + { + add_action('admin_menu', array($this, 'menu')); + } + + add_action('clear_auth_cookie', array($this, 'logout')); + + register_activation_hook(__FILE__, array($this, 'activate')); + register_deactivation_hook(__FILE__, array($this, 'deactivate')); + } + + /** + * Logs the user out of Feather as well. + * + * @return void + */ + public function logout() + { + setcookie($this->cookie, ' ', time() - 31536000, COOKIEPATH, COOKIE_DOMAIN); + } + + /** + * Adds the plugin to the plugins menu. + * + * @return void + */ + public function menu() + { + add_plugins_page( + 'Feather OneConnect', + 'Feather OneConnect', + 'administrator', + 'oneconnect', + array($this, 'options') + ); + } + + /** + * The options page for the OneConnect plugin. + * + * @return string + */ + public function options() + { + if(isset($_POST['save'])) { + if(function_exists('current_user_can') && !current_user_can('manage_options')) + { + die(__('Permission Denied')); + } + + $authenticate_url = str_replace('{token}', $this->token(), $_POST['feather_oneconnect_authenticate']); + + update_option('feather_oneconnect_authenticate', $authenticate_url); + } + else + { + $authenticate_url = get_option('feather_oneconnect_authenticate'); + } + + echo '
    '; + echo '

    '; + echo '

    '; + echo _e('Feather OneConnect for Wordpress Configuration'); + echo '

    '; + echo '

    The authenticate URL is set by default, however you can change this URL if the default is already in use. Use the {token} placeholder to generate a random token string.

    '; + echo '
    '; + echo '
    '; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
    Authenticate URL
    '; + echo '

    '; + echo ''; + + echo '

    '; + echo _e('Feather OneConnect for Wordpress Information'); + echo '

    '; + echo '

    The following information needs to be copied and pasted over to the OneConnect options on your Feather installation.'; + echo '
    Remember that if you changed the Authenticate URL above you need to save it first to get the updated URL.

    '; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '
    Authenticate URL' . site_url(get_option('feather_oneconnect_authenticate'), 'oneconnect-feather-authenticate') . '
    Registration URL' . site_url('wp-login.php?action=register', 'login') . '
    Login URL' . wp_login_url() . '?redirect_to={current}
    Logout URL' . add_query_arg(array('action' => 'logout', '_wpnonce' => '{token}', 'redirect_to' => '{current}'), site_url('wp-login.php', 'login')) . '
    '; + echo ''; + } + + /** + * Shows the authenticated user details in a JSON encoded array. + * + * @return string + */ + protected function authenticated() + { + global $current_user; + + if(!function_exists('get_currentuserinfo')) + { + require (ABSPATH . WPINC . '/pluggable.php'); + } + + get_currentuserinfo(); + + $credentials = array( + 'id' => null, + 'username' => null, + 'email' => null, + 'token' => null + ); + + if($current_user->ID) + { + $credentials = array( + 'id' => $current_user->ID, + 'username' => $current_user->user_login, + 'email' => $current_user->user_email, + 'token' => wp_create_nonce('log-out') + ); + } + + die(json_encode($credentials)); + } + + /** + * Runs when the plugin is activated, adds options. + * + * @return void + */ + public function activate() + { + add_option('feather_oneconnect_authenticate', '?oneconnect=' . $this->token()); + } + + /** + * Runs when the plugin is deactivated, deletes options. + * + * @return void + */ + public function deactivate() + { + delete_option('feather_oneconnect_authenticate'); + } + + /** + * Generates a random token. + * + * @return string + */ + protected function token() + { + return md5(uniqid()); + } + + } + + // Instantiate the plugin! This is way nicer then a bunch of crummy functions in the global space! + // Code pretty! Code with Laravel! :) + $oneconnect = new Feather_OneConnect_Plugin; +} \ No newline at end of file From 45844f5b4203c7ff626590eb8ab668d211c4e840 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 23:25:44 +0930 Subject: [PATCH 127/136] Initial commit of the authentication components single sign on layer. Signed-off-by: Jason Lewis --- components/auth/sso.php | 105 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 8 deletions(-) diff --git a/components/auth/sso.php b/components/auth/sso.php index 9243fae..e3b81cf 100644 --- a/components/auth/sso.php +++ b/components/auth/sso.php @@ -1,7 +1,10 @@ {$method}($credentials); } - extract($credentials); + $required = array('id', 'email', 'username'); + + foreach($required as $credential) + { + if(!array_key_exists($credential, $credentials) or is_null($credentials[$credential])) + { + return; + } + } + + extract($credentials); // If a user exists with an associated e-mail address that matches then this user has already // been authenticated with a SSO service. @@ -59,17 +72,93 @@ public function authorize(array $credentials) // If there is a user in the database who has the same e-mail or username as our // user then they need to either link their account or create a new one. - elseif($user = User::where('username', '=', $username)->or_where('email', '=', $email)->first()) + elseif($user = User::where_username($username)->or_where('email', '=', $email)->first()) { - Breadcrumbs::drop(__('feather::titles.connect_to_community')); + $this->feather['crumbs']->drop(__('feather core::titles.connect_to_community')); + + return View::of('template')->with('title', __('feather core::titles.connect_to_community')) + ->nest('content', 'feather core::user.associate', compact('user')); + } + + // If there is no user in the database then the user does not need to perform any action to + // connect or create their account. We can simply authorize them to continue using Feather. + // ALl of this is behind the scenes and very quick, the user sees nothing. + $user = User::associate($credentials, $credentials); + + $this->feather['auth']->login($user); + + return $this->feather['redirect']->to_self(); + } - return View::of('layout')->with('title', __('feather::titles.connect_to_community')) - ->nest('content', 'feather::user.associate', compact('user')); + /** + * Attempt to create a user to be associated with the foreign application. + * + * @param array $associate + * @return object + */ + protected function create($associate) + { + try + { + $this->feather['validator']->get('auth.sso.create')->against(Input::get())->passes(); + } + catch (FeatherValidationException $errors) + { + return $this->feather['redirect']->to_self()->with_input()->with_errors($errors->get()); + } + + Input::replace(array( + 'username' => Input::get('create_username'), + 'email' => Input::get('create_email') + )); + + if($user = User::associate($associate, $input)) + { + $this->feather['auth']->login($user); + + return $this->feather['redirect']->to_self(); + } + else + { + return $this->feather['redirect']->to_self()->with_input()->alert('failed', 'feather core::register.failure'); + } + } + + /** + * Attempts to connect a user to an existing Feather account. + * + * @param array $associate + * @return object + */ + protected function connect($associate) + { + try + { + $this->feather['validator']->get('auth.connect')->against(Input::get())->passes(); + } + catch (FeatherValidationException $errors) + { + return $this->feather['redirect']->to_self()->with_input()->with_errors($errors->get()); + } + + $user = User::where_email(Input::get('connect_email'))->first(); + + // If we are able to login with the username retrieved from the database and the password + // provided by the user then we can update the assoicated e-mail and redirect the user. + if($this->attempt(array('username' => $user->username, 'password' => Input::get('connect_password')))) + { + $user->fill(array( + 'authenticator' => $this->feather['config']->get('feather: db.auth.driver'), + 'authenticator_associated_email' => $associate['email'], + 'authenticator_token' => $associate['token'] + )); + + $user->save(); + + return $this->feather['redirect']->to_self(); } - // If there is no user in the database then the associated user is our actual user. Does - // that make sense? Well... we basically just create them a new account. - return static::create($credentials, $credentials); + return $this->feather['redirect']->to_self()->with_input()->alert('error', 'feather core::connect.failure'); } } \ No newline at end of file From 0f8997745db1cc6651c4947f69a93bef0318c765 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Sun, 23 Sep 2012 23:26:03 +0930 Subject: [PATCH 128/136] Refining some things for SSO. Signed-off-by: Jason Lewis --- applications/core/models/user.php | 29 ++++++++++++++++++++++++- applications/core/start.php | 14 +++++++++++- applications/core/validation/auth.php | 31 ++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/applications/core/models/user.php b/applications/core/models/user.php index c7333e0..94ae553 100644 --- a/applications/core/models/user.php +++ b/applications/core/models/user.php @@ -76,7 +76,7 @@ public function get_avatar() * * @param array $input * @param bool $activated - * @return object + * @return Feather\Core\User */ public static function register($input) { @@ -112,4 +112,31 @@ public static function edit($user, $input) } + /** + * Register a new user and associate it with an e-mail from an authenticator. + * + * @param array $associate + * @param array $input + * @return Feather\Core\User + */ + public static function associate($associate, $input) + { + $user = new static(array( + 'username' => $input['username'], + 'authenticator' => Config::get('feather: db.auth.driver'), + 'authenticator_token' => $associate['token'], + 'authenticator_associated_email' => $associate['email'], + 'email' => $input['email'] + )); + + if(!$user->save()) + { + throw new FeatherModelException; + } + + $user->roles()->attach(2); + + return $user; + } + } \ No newline at end of file diff --git a/applications/core/start.php b/applications/core/start.php index 034aa7b..4a1fbea 100644 --- a/applications/core/start.php +++ b/applications/core/start.php @@ -1,5 +1,6 @@ path('core') . 'controllers' . DS . 'base' . EXT -)); \ No newline at end of file +)); + +/* +|-------------------------------------------------------------------------- +| Core Template +|-------------------------------------------------------------------------- +| +| Name the core template. +| +*/ + +View::name('feather core::template', 'template'); \ No newline at end of file diff --git a/applications/core/validation/auth.php b/applications/core/validation/auth.php index 2ac8b84..6adbc70 100644 --- a/applications/core/validation/auth.php +++ b/applications/core/validation/auth.php @@ -35,5 +35,34 @@ $validator->rule('rules', 'accepted') ->message('rules.accepted', 'register.messages.rules.not_accepted'); } - } + }, + + 'sso' => array( + + 'create' => function($validator) + { + $validator->rule('create_username', array('required', 'min:3', 'max:20', 'alpha_dot', 'unique:users,username')) + ->rule('create_email', array('required', 'email', 'unique:users,email')) + ->message('create_username.required', 'authenticate.errors.register.username.is_required') + ->message('create_username.min', array('authenticate.errors.register.username.too_short', array('length' => 3))) + ->message('create_username.max', array('authenticate.errors.register.username.too_long', array('length' => 20))) + ->message('create_username.alpha_dot', 'authenticate.errors.register.username.is_invalid') + ->message('create_username.unique', 'authenticate.errors.register.username.already_exists') + ->message('create_email.required', 'authenticate.errors.register.email.is_required') + ->message('create_email.email', 'authenticate.errors.register.email.invalid') + ->message('create_email.unique', 'authenticate.errors.register.email.already_exists'); + }, + + 'connect' => function($validator) + { + $validator->rule('connect_email', array('required', 'email')) + ->rule('connect_password', array('required', 'min:7', 'max:15')) + ->message('connect_email.required', 'authenticate.errors.register.email.is_required') + ->message('connect_email.unique', 'authenticate.errors.register.email.already_exists') + ->message('connect_password.required', 'authenticate.errors.register.password.is_required') + ->message('connect_password.min', array('authenticate.errors.register.password.too_short', array('length' => 7))) + ->message('connect_password.max', array('authenticate.errors.register.password.too_long', array('length' => 15))); + } + + ) ); \ No newline at end of file From 060ea24eabf8cf88d1ac5e7760f6b57142df05e9 Mon Sep 17 00:00:00 2001 From: Jason Lewis Date: Mon, 24 Sep 2012 01:12:15 +0930 Subject: [PATCH 129/136] Initial commit of Feather documentation gear. Signed-off-by: Jason Lewis --- .../auth/singlesignon/introduction.md | 8 + .../auth/singlesignon/oneconnect.md | 80 + gears/docs/documentation/gears/events.md | 513 ++++++ gears/docs/documentation/home.md | 3 + gears/docs/public/css/bootstrap.css | 722 ++++++++ gears/docs/public/css/docs.css | 155 ++ .../public/img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../docs/public/img/glyphicons-halflings.png | Bin 0 -> 13826 bytes gears/docs/public/js/bootstrap.js | 7 + gears/docs/public/js/jquery.js | 2 + gears/docs/public/js/prettify.js | 1477 +++++++++++++++++ gears/docs/routes.php | 40 + gears/docs/start.php | 34 + gears/docs/views/contents.blade.php | 45 + gears/docs/views/page.blade.php | 60 + 15 files changed, 3146 insertions(+) create mode 100644 gears/docs/documentation/auth/singlesignon/introduction.md create mode 100644 gears/docs/documentation/auth/singlesignon/oneconnect.md create mode 100644 gears/docs/documentation/gears/events.md create mode 100644 gears/docs/documentation/home.md create mode 100644 gears/docs/public/css/bootstrap.css create mode 100644 gears/docs/public/css/docs.css create mode 100644 gears/docs/public/img/glyphicons-halflings-white.png create mode 100644 gears/docs/public/img/glyphicons-halflings.png create mode 100644 gears/docs/public/js/bootstrap.js create mode 100644 gears/docs/public/js/jquery.js create mode 100644 gears/docs/public/js/prettify.js create mode 100644 gears/docs/routes.php create mode 100644 gears/docs/start.php create mode 100644 gears/docs/views/contents.blade.php create mode 100644 gears/docs/views/page.blade.php diff --git a/gears/docs/documentation/auth/singlesignon/introduction.md b/gears/docs/documentation/auth/singlesignon/introduction.md new file mode 100644 index 0000000..567dd0b --- /dev/null +++ b/gears/docs/documentation/auth/singlesignon/introduction.md @@ -0,0 +1,8 @@ +## Single Sign On + +One of Feather's design goals is to easily and effortlessly allow existing applications with an existing user base to integrate and interact with Feather. What this boils down to is a seemless transition from your application to the forums. No longer will your users have to remember two or more passwords for your application. + +Before using a single sign on provider there are a few things you must consider. + +1. If you've been maintaining a separate user base for your forum and your application for any period and have numerous users in both some users may be required to link there accounts on there initial visit to the forum. +2. A [forum migration](/migrations) and a single sign on provider can not be used in conjunction with each other. If you're migrating from an existing forum then it's recommended that you migrate users for a period of time before enabling single sign on. \ No newline at end of file diff --git a/gears/docs/documentation/auth/singlesignon/oneconnect.md b/gears/docs/documentation/auth/singlesignon/oneconnect.md new file mode 100644 index 0000000..d61532b --- /dev/null +++ b/gears/docs/documentation/auth/singlesignon/oneconnect.md @@ -0,0 +1,80 @@ +## OneConnect + +OneConnect is a gear developed for Feather which allows an application on the same domain as Feather to connect and share user information. OneConnect is very easy to get going, with only a few steps required on the developers end. + +To use OneConnect you need to have knowledge of how to fetch the logged in users information for your application and creating a page that can display this information. + +
    +**Hold up, are you using Laravel?** + +If you're developing an application with [Laravel](http://laravel.com) then we suggest you use the **[Harmonize](/auth/single-sign-on/harmonize)** gear instead. +
    + +### WordPress Plugin + +A WordPress plugin is provided with OneConnect. + +1. Copy the `feather-oneconnect.php` file from `gears/oneconnct/wordpress` into your WordPress plugin directory. +2. From your WordPress dashboard enable the plugin from the Installed Plugins screen. +3. Select the Feather OneConnect settings in the navigation and take note of the configuration details. +4. Enable OneConnect in your Feather dashboard, and during configuration paste the details that were listed by the WordPress plugin. + +Your WordPress users should now be able to login to your WordPress website and be automatically authorized on Feather. + +
    +Just a sec! Be sure to read the README.md file on setting the cookie domain and path for your WordPress installation. +
    + +### How OneConnect Works + +When a user logs in to your application a cookie is set for that user. Your application has access to this cookie, so on subsequent page visits the cookie is picked up and your application will know to keep that user logged in. Let's assume that **Jack** has just logged in to your application. When Jack visits the forum Feather will make a request to a page on your application (yes, the page you need to create). Because Jack is logged in this page shows some information about Jack that Feather grabs. Here's an example. + +~~~~ +{ + "id":874, + "username":"Jack", + "email":"jack@beanstalk.com" +} +~~~~ + +The response is JSON, this allows Feather to easily parse the users information. Now that Feather has the Jack's information it can attempt to authorize him on the forums. Feather will see that Jack is yet to visit the forums, so an account is created for Jack and it's linked to his account on your application. His experience is seemless and he knows nothing of what's going on in the background. + +If Jack has already visited the forums before then he'll be authorized for the forums. Again, Jack's experience is seemless. + +The only time Jack will have an interupted experience is when an account already exists with his username or e-mail address. The only time this may occur is if you're forum has been running longer then your application. If Feather is unable to authorize Jack he'll be presented with a screen asking him to create a new account or link to an existing account. If Jack was already signed up on your forums beforehand then he's able to provide his e-mail address on the forum and the corrosponding password and link his account. If Jack does not have an account then he's able to create a new account. + +OneConnect is actually quite a simple process, however for it to work there are a few things that you, as the developer, need to address on your application. + +### Application Cookies + +OneConnect requires that your applications cookies be set on the same domain as Feather. + +~~~~ +setcookie("TestCookie", $value, time() + 3600, "/", ".yourapp.com"); +~~~~ + +You can see that the cookie path and domain have been set to the entire domain, this means that OneConnect has access to your applications cookies. + +
    +Note that the leading **.** is required for older browsers still implementing the deprecated [RFC 2109](http://www.faqs.org/rfcs/rfc2109). +
    + +### User Information + +When setting up OneConnect you'll be required to enter an **Authentication URL**. This URL is where the JSON encoded logged in users information is displayed. The following fields are required for authorizing the user. + +~~~~ +{ + "id":1, + "username":"Username", + "email":"E-mail Address" +} +~~~~ + +
    +When a user is not logged in an empty JSON encoded array or blank page should be returned. +
    + +### Conclusion + +You should now have OneConnect configured and working correctly. When users from your application login they'll be automatically authoried on Feather. If you have any troubles try clearing your cookies first and then trying again. \ No newline at end of file diff --git a/gears/docs/documentation/gears/events.md b/gears/docs/documentation/gears/events.md new file mode 100644 index 0000000..4205870 --- /dev/null +++ b/gears/docs/documentation/gears/events.md @@ -0,0 +1,513 @@ +## Gear Events + +For those of you that use Laravel, Feather makes use of the Events system to power Gear events. Events are fired at points throughout the execution of Feather and your own custom events can be fired. + +### Event Naming Convention + +Feather uses a very simple naming convention for all events that are fired. This is an event in its most basic form. + + {category}: {timing} {event} + +An example of this convention is as follows. + + validation: before auth.register + +The above event is fired before the registrations validation as assessed. The above event is useful when you want your Gear to add custom inputs to the registration form. + +### Asset Events + + + + + + + + + + + + + + + + + +

    assets: change styles

    Receives + + + + + + + + + +
    ParameterDescription
    `object $container`Asset container being used by current theme.
    +
    Expects + `void` +
    + Fired after the controllers method has been run. An asset container for the theme is passed to the event allowing styles within the container to be modified. +
    + + + + + + + + + + + + + + + + + +

    assets: change scripts

    Receives + + + + + + + + + +
    ParameterDescription
    `object $container`Asset container being used by current theme.
    +
    Expects + `void` +
    + Fired after the controllers method has been run. An asset container for the theme is passed to the event allowing scripts within the container to be modified. +
    + +### Controller Events + + + + + + + + + + + + + + + + + +

    controller: before {controller.name}@{verb}.{method.name}

    Receives + + + + + + + + + +
    ParameterDescription
    `object $controller`Instance of the controller object being resolved.
    +
    Expects + `void` +
    + Fired before a given method on a given controller is executed. Listeners receive an instance of the controller object. +
    + + + + + + + + + + + + + + + + + +

    controller: after {controller.name}@{verb}.{method.name}

    Receives + + + + + + + + + +
    ParameterDescription
    `object $controller`Instance of the controller object being resolved.
    +
    Expects + `void` +
    + Fired after a given method on a given controller is executed. Listeners receive an instance of the controller object. +
    + + + + + + + + + + + + + + + + + +

    controller: override {controller.name}@{verb}.{method.name}

    Receives + + + + + + + + + +
    ParameterDescription
    `object $controller`Instance of the controller object being resolved.
    +
    Expects + `mixed` +
    + Overrides a given method on a given controller. No response needs to be returned meaning the event can interact with the `layout` property on the controller instance. +
    + + + + + + + + + + + + + + + + + +

    controller: create {controller.name}@{verb}.{method.name}

    Receives + + + + + + + + + +
    ParameterDescription
    `object $controller`Instance of the controller object being resolved.
    +
    Expects + `mixed` +
    + Create a method for the given HTTP verb on a given controller. As with overriding a method you can interact with the `layout` property on the controller instance. +
    + +
    +**Don't forget!** + +All controller events receive the controller instance meaning you can interact with the all methods and properties on that controller. +
    + +### View Events + + + + + + + + + + + + + +

    view: before category.title

    Expects + `mixed` +
    + Fired before the category title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after category.title

    Expects + `mixed` +
    + Fired after the category title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before category.discussion.counter

    Expects + `mixed` +
    + Fired before the category discussion counter. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after category.discussion.counter

    Expects + `mixed` +
    + Fired after the category discussion counter. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before category.discussion.title

    Expects + `mixed` +
    + Fired before the category discussion title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after category.discussion.title

    Expects + `mixed` +
    + Fired after the category discussion title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before category.discussion.meta

    Expects + `mixed` +
    + Fired before the category discussion meta information. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after category.discussion.meta

    Expects + `mixed` +
    + Fired after the category discussion meta information. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before category.discussion.stats

    Expects + `mixed` +
    + Fired before the category discussion statistics. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after category.discussion.stats

    Expects + `mixed` +
    + Fired after the category discussion statistics. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before template.title

    Expects + `mixed` +
    + Fired before the template title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after template.title

    Expects + `mixed` +
    + Fired after the template title. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before template.meta

    Expects + `mixed` +
    + Fired before the template meta. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after template.meta

    Expects + `mixed` +
    + Fired after the template meta. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: before register.rules

    Expects + `mixed` +
    + Fired before the registration rules. Expects a value to be returned. +
    + + + + + + + + + + + + + +

    view: after register.rules

    Expects + `mixed` +
    + Fired after the registration rules. Expects a value to be returned. +
    \ No newline at end of file diff --git a/gears/docs/documentation/home.md b/gears/docs/documentation/home.md new file mode 100644 index 0000000..a77c732 --- /dev/null +++ b/gears/docs/documentation/home.md @@ -0,0 +1,3 @@ +# Feather Forums + +Feather is a community forum system. \ No newline at end of file diff --git a/gears/docs/public/css/bootstrap.css b/gears/docs/public/css/bootstrap.css new file mode 100644 index 0000000..c1cc864 --- /dev/null +++ b/gears/docs/public/css/bootstrap.css @@ -0,0 +1,722 @@ +/*! + * Bootstrap v2.0.4 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +#map_canvas img{max-width:none;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;} +a{color:#0088cc;text-decoration:none;} +a:hover{color:#005580;text-decoration:underline;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} +.row:after{clear:both;} +[class*="span"]{float:left;margin-left:20px;} +.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574%;*margin-left:2.0744680846382977%;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%;} +.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%;} +.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%;} +.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%;} +.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%;} +.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%;} +.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%;} +.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%;} +.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%;} +.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%;} +.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%;} +.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%;} +.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";} +.container-fluid:after{clear:both;} +p{margin:0 0 9px;}p small{font-size:11px;color:#999999;} +.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;} +h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;} +h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;} +h3{font-size:18px;line-height:27px;}h3 small{font-size:14px;} +h4,h5,h6{line-height:18px;} +h4{font-size:14px;}h4 small{font-size:12px;} +h5{font-size:12px;} +h6{font-size:11px;color:#999999;text-transform:uppercase;} +.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;} +.page-header h1{line-height:1;} +ul,ol{padding:0;margin:0 0 9px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +dl{margin-bottom:18px;} +dt,dd{line-height:18px;} +dt{font-weight:bold;line-height:17px;} +dd{margin-left:9px;} +.dl-horizontal dt{float:left;width:120px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:130px;} +hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;} +strong{font-weight:bold;} +em{font-style:italic;} +.muted{color:#999999;} +abbr[title]{cursor:help;border-bottom:1px dotted #999999;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} +blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:18px;font-style:normal;line-height:18px;} +small{font-size:100%;} +cite{font-style:normal;} +code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} +pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:18px;} +pre code{padding:0;color:inherit;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +.label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;} +.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#b94a48;} +.label-important[href],.badge-important[href]{background-color:#953b39;} +.label-warning,.badge-warning{background-color:#f89406;} +.label-warning[href],.badge-warning[href]{background-color:#c67605;} +.label-success,.badge-success{background-color:#468847;} +.label-success[href],.badge-success[href]{background-color:#356635;} +.label-info,.badge-info{background-color:#3a87ad;} +.label-info[href],.badge-info[href]{background-color:#2d6987;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} +.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;} +.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;} +.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;} +.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;} +table .span1{float:none;width:44px;margin-left:0;} +table .span2{float:none;width:124px;margin-left:0;} +table .span3{float:none;width:204px;margin-left:0;} +table .span4{float:none;width:284px;margin-left:0;} +table .span5{float:none;width:364px;margin-left:0;} +table .span6{float:none;width:444px;margin-left:0;} +table .span7{float:none;width:524px;margin-left:0;} +table .span8{float:none;width:604px;margin-left:0;} +table .span9{float:none;width:684px;margin-left:0;} +table .span10{float:none;width:764px;margin-left:0;} +table .span11{float:none;width:844px;margin-left:0;} +table .span12{float:none;width:924px;margin-left:0;} +table .span13{float:none;width:1004px;margin-left:0;} +table .span14{float:none;width:1084px;margin-left:0;} +table .span15{float:none;width:1164px;margin-left:0;} +table .span16{float:none;width:1244px;margin-left:0;} +table .span17{float:none;width:1324px;margin-left:0;} +table .span18{float:none;width:1404px;margin-left:0;} +table .span19{float:none;width:1484px;margin-left:0;} +table .span20{float:none;width:1564px;margin-left:0;} +table .span21{float:none;width:1644px;margin-left:0;} +table .span22{float:none;width:1724px;margin-left:0;} +table .span23{float:none;width:1804px;margin-left:0;} +table .span24{float:none;width:1884px;margin-left:0;} +form{margin:0 0 18px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:13.5px;color:#999999;} +label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;} +select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;} +input,textarea{width:210px;} +textarea{height:auto;} +textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);} +input[type="radio"],input[type="checkbox"]{margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;} +input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;} +.uneditable-textarea{width:auto;height:auto;} +select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;} +select{width:220px;border:1px solid #bbb;} +select[multiple],select[size]{height:auto;} +select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.radio,.checkbox{min-height:18px;padding-left:18px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;} +input,textarea,.uneditable-input{margin-left:0;} +input.span12, textarea.span12, .uneditable-input.span12{width:930px;} +input.span11, textarea.span11, .uneditable-input.span11{width:850px;} +input.span10, textarea.span10, .uneditable-input.span10{width:770px;} +input.span9, textarea.span9, .uneditable-input.span9{width:690px;} +input.span8, textarea.span8, .uneditable-input.span8{width:610px;} +input.span7, textarea.span7, .uneditable-input.span7{width:530px;} +input.span6, textarea.span6, .uneditable-input.span6{width:450px;} +input.span5, textarea.span5, .uneditable-input.span5{width:370px;} +input.span4, textarea.span4, .uneditable-input.span4{width:290px;} +input.span3, textarea.span3, .uneditable-input.span3{width:210px;} +input.span2, textarea.span2, .uneditable-input.span2{width:130px;} +input.span1, textarea.span1, .uneditable-input.span1{width:50px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;border-color:#ddd;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;} +input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";} +.form-actions:after{clear:both;} +.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);} +:-moz-placeholder{color:#999999;} +:-ms-input-placeholder{color:#999999;} +::-webkit-input-placeholder{color:#999999;} +.help-block,.help-inline{color:#555555;} +.help-block{display:block;margin-bottom:9px;} +.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;} +.input-prepend,.input-append{margin-bottom:5px;}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2;} +.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;} +.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;} +.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee;} +.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:9px;} +legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:160px;} +.form-horizontal .help-block{margin-top:9px;margin-bottom:0;} +.form-horizontal .form-actions{padding-left:160px;} +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;*line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.btn-large [class^="icon-"]{margin-top:1px;} +.btn-small{padding:5px 9px;font-size:11px;line-height:16px;} +.btn-small [class^="icon-"]{margin-top:-1px;} +.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;} +.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn{border-color:#ccc;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top, #0088cc, #0055cc);background-image:-ms-linear-gradient(top, #0088cc, #0055cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));background-image:-webkit-linear-gradient(top, #0088cc, #0055cc);background-image:-o-linear-gradient(top, #0088cc, #0055cc);background-image:linear-gradient(top, #0088cc, #0055cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);border-color:#0055cc #0055cc #003580;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0055cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0055cc;*background-color:#004ab3;} +.btn-primary:active,.btn-primary.active{background-color:#004099 \9;} +.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505;} +.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;} +.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555555, #222222);background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;} +.icon-white{background-image:url("../img/glyphicons-halflings-white.png");} +.icon-glass{background-position:0 0;} +.icon-music{background-position:-24px 0;} +.icon-search{background-position:-48px 0;} +.icon-envelope{background-position:-72px 0;} +.icon-heart{background-position:-96px 0;} +.icon-star{background-position:-120px 0;} +.icon-star-empty{background-position:-144px 0;} +.icon-user{background-position:-168px 0;} +.icon-film{background-position:-192px 0;} +.icon-th-large{background-position:-216px 0;} +.icon-th{background-position:-240px 0;} +.icon-th-list{background-position:-264px 0;} +.icon-ok{background-position:-288px 0;} +.icon-remove{background-position:-312px 0;} +.icon-zoom-in{background-position:-336px 0;} +.icon-zoom-out{background-position:-360px 0;} +.icon-off{background-position:-384px 0;} +.icon-signal{background-position:-408px 0;} +.icon-cog{background-position:-432px 0;} +.icon-trash{background-position:-456px 0;} +.icon-home{background-position:0 -24px;} +.icon-file{background-position:-24px -24px;} +.icon-time{background-position:-48px -24px;} +.icon-road{background-position:-72px -24px;} +.icon-download-alt{background-position:-96px -24px;} +.icon-download{background-position:-120px -24px;} +.icon-upload{background-position:-144px -24px;} +.icon-inbox{background-position:-168px -24px;} +.icon-play-circle{background-position:-192px -24px;} +.icon-repeat{background-position:-216px -24px;} +.icon-refresh{background-position:-240px -24px;} +.icon-list-alt{background-position:-264px -24px;} +.icon-lock{background-position:-287px -24px;} +.icon-flag{background-position:-312px -24px;} +.icon-headphones{background-position:-336px -24px;} +.icon-volume-off{background-position:-360px -24px;} +.icon-volume-down{background-position:-384px -24px;} +.icon-volume-up{background-position:-408px -24px;} +.icon-qrcode{background-position:-432px -24px;} +.icon-barcode{background-position:-456px -24px;} +.icon-tag{background-position:0 -48px;} +.icon-tags{background-position:-25px -48px;} +.icon-book{background-position:-48px -48px;} +.icon-bookmark{background-position:-72px -48px;} +.icon-print{background-position:-96px -48px;} +.icon-camera{background-position:-120px -48px;} +.icon-font{background-position:-144px -48px;} +.icon-bold{background-position:-167px -48px;} +.icon-italic{background-position:-192px -48px;} +.icon-text-height{background-position:-216px -48px;} +.icon-text-width{background-position:-240px -48px;} +.icon-align-left{background-position:-264px -48px;} +.icon-align-center{background-position:-288px -48px;} +.icon-align-right{background-position:-312px -48px;} +.icon-align-justify{background-position:-336px -48px;} +.icon-list{background-position:-360px -48px;} +.icon-indent-left{background-position:-384px -48px;} +.icon-indent-right{background-position:-408px -48px;} +.icon-facetime-video{background-position:-432px -48px;} +.icon-picture{background-position:-456px -48px;} +.icon-pencil{background-position:0 -72px;} +.icon-map-marker{background-position:-24px -72px;} +.icon-adjust{background-position:-48px -72px;} +.icon-tint{background-position:-72px -72px;} +.icon-edit{background-position:-96px -72px;} +.icon-share{background-position:-120px -72px;} +.icon-check{background-position:-144px -72px;} +.icon-move{background-position:-168px -72px;} +.icon-step-backward{background-position:-192px -72px;} +.icon-fast-backward{background-position:-216px -72px;} +.icon-backward{background-position:-240px -72px;} +.icon-play{background-position:-264px -72px;} +.icon-pause{background-position:-288px -72px;} +.icon-stop{background-position:-312px -72px;} +.icon-forward{background-position:-336px -72px;} +.icon-fast-forward{background-position:-360px -72px;} +.icon-step-forward{background-position:-384px -72px;} +.icon-eject{background-position:-408px -72px;} +.icon-chevron-left{background-position:-432px -72px;} +.icon-chevron-right{background-position:-456px -72px;} +.icon-plus-sign{background-position:0 -96px;} +.icon-minus-sign{background-position:-24px -96px;} +.icon-remove-sign{background-position:-48px -96px;} +.icon-ok-sign{background-position:-72px -96px;} +.icon-question-sign{background-position:-96px -96px;} +.icon-info-sign{background-position:-120px -96px;} +.icon-screenshot{background-position:-144px -96px;} +.icon-remove-circle{background-position:-168px -96px;} +.icon-ok-circle{background-position:-192px -96px;} +.icon-ban-circle{background-position:-216px -96px;} +.icon-arrow-left{background-position:-240px -96px;} +.icon-arrow-right{background-position:-264px -96px;} +.icon-arrow-up{background-position:-289px -96px;} +.icon-arrow-down{background-position:-312px -96px;} +.icon-share-alt{background-position:-336px -96px;} +.icon-resize-full{background-position:-360px -96px;} +.icon-resize-small{background-position:-384px -96px;} +.icon-plus{background-position:-408px -96px;} +.icon-minus{background-position:-433px -96px;} +.icon-asterisk{background-position:-456px -96px;} +.icon-exclamation-sign{background-position:0 -120px;} +.icon-gift{background-position:-24px -120px;} +.icon-leaf{background-position:-48px -120px;} +.icon-fire{background-position:-72px -120px;} +.icon-eye-open{background-position:-96px -120px;} +.icon-eye-close{background-position:-120px -120px;} +.icon-warning-sign{background-position:-144px -120px;} +.icon-plane{background-position:-168px -120px;} +.icon-calendar{background-position:-192px -120px;} +.icon-random{background-position:-216px -120px;} +.icon-comment{background-position:-240px -120px;} +.icon-magnet{background-position:-264px -120px;} +.icon-chevron-up{background-position:-288px -120px;} +.icon-chevron-down{background-position:-313px -119px;} +.icon-retweet{background-position:-336px -120px;} +.icon-shopping-cart{background-position:-360px -120px;} +.icon-folder-close{background-position:-384px -120px;} +.icon-folder-open{background-position:-408px -120px;} +.icon-resize-vertical{background-position:-432px -119px;} +.icon-resize-horizontal{background-position:-456px -118px;} +.icon-hdd{background-position:0 -144px;} +.icon-bullhorn{background-position:-24px -144px;} +.icon-bell{background-position:-48px -144px;} +.icon-certificate{background-position:-72px -144px;} +.icon-thumbs-up{background-position:-96px -144px;} +.icon-thumbs-down{background-position:-120px -144px;} +.icon-hand-right{background-position:-144px -144px;} +.icon-hand-left{background-position:-168px -144px;} +.icon-hand-up{background-position:-192px -144px;} +.icon-hand-down{background-position:-216px -144px;} +.icon-circle-arrow-right{background-position:-240px -144px;} +.icon-circle-arrow-left{background-position:-264px -144px;} +.icon-circle-arrow-up{background-position:-288px -144px;} +.icon-circle-arrow-down{background-position:-312px -144px;} +.icon-globe{background-position:-336px -144px;} +.icon-wrench{background-position:-360px -144px;} +.icon-tasks{background-position:-384px -144px;} +.icon-filter{background-position:-408px -144px;} +.icon-briefcase{background-position:-432px -144px;} +.icon-fullscreen{background-position:-456px -144px;} +.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";} +.btn-group:after{clear:both;} +.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} +.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:4px;*padding-bottom:4px;} +.btn-group>.btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;} +.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;} +.btn-group>.btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#0055cc;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:7px;margin-left:0;} +.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);} +.btn-mini .caret{margin-top:5px;} +.btn-small .caret{margin-top:6px;} +.btn-large .caret{margin-top:6px;border-left-width:5px;border-right-width:5px;border-top-width:5px;} +.dropup .btn-large .caret{border-bottom:5px solid #000000;border-top:0;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);} +.nav{margin-left:0;margin-bottom:18px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;} +.nav>.pull-right{float:right;} +.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;} +.nav-list [class^="icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;} +.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;} +.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;} +.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;} +.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;} +.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;} +.navbar{color:#999999;}.navbar .brand:hover{text-decoration:none;} +.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#999999;} +.navbar .navbar-text{margin-bottom:0;line-height:40px;} +.navbar .navbar-link{color:#999999;}.navbar .navbar-link:hover{color:#ffffff;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn{margin:0;} +.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} +.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;} +.navbar-search .search-query:-ms-input-placeholder{color:#cccccc;} +.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} +.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-bottom{bottom:0;} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;} +.navbar .nav>li{display:block;float:left;} +.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px;} +.navbar .btn-group{margin:0;padding:5px 5px 6px;} +.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;} +.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;} +.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;} +.navbar .nav.pull-right{margin-left:10px;margin-right:0;} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#222222;*background-color:#151515;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#080808 \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} +.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} +.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100);} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent;} +.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#ffffff;} +.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{left:auto;right:12px;} +.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{left:auto;right:13px;} +.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#999999;} +.breadcrumb .active a{color:#333333;} +.pagination{height:36px;margin:18px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;} +.pagination a:hover,.pagination .active a{background-color:#f5f5f5;} +.pagination .active a{color:#999999;cursor:default;} +.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;} +.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager a:hover{text-decoration:none;background-color:#f5f5f5;} +.pager .next a{float:right;} +.pager .previous a{float:left;} +.pager .disabled a,.pager .disabled a:hover{color:#999999;background-color:#fff;cursor:default;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px;} +.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);} +a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;} +.thumbnail .caption{padding:9px;} +.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;} +.alert-heading{color:inherit;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;} +.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;} +.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;} +.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);} +.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);} +.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);} +.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);} +.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;} +.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-2px;} +.tooltip.right{margin-left:2px;} +.tooltip.bottom{margin-top:2px;} +.tooltip.left{margin-left:-2px;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;} +.popover.right{margin-left:5px;} +.popover.bottom{margin-top:5px;} +.popover.left{margin-left:-5px;} +.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;} +.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;} +.modal-open .dropdown-menu{z-index:2050;} +.modal-open .dropdown.open{*z-index:2050;} +.modal-open .popover{z-index:2060;} +.modal-open .tooltip{z-index:2070;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;} +.modal-body{overflow-y:auto;max-height:400px;padding:15px;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";opacity:0.3;filter:alpha(opacity=30);} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100);} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;} +.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;} +.open{*z-index:1000;}.open >.dropdown-menu{display:block;} +.pull-right>.dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion{margin-bottom:18px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:18px;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} +.carousel .item>img{display:block;line-height:1;} +.carousel .active,.carousel .next,.carousel .prev{display:block;} +.carousel .active{left:0;} +.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} +.carousel .next{left:100%;} +.carousel .prev{left:-100%;} +.carousel .next.left,.carousel .prev.right{left:0;} +.carousel .active.left{left:-100%;} +.carousel .active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;} diff --git a/gears/docs/public/css/docs.css b/gears/docs/public/css/docs.css new file mode 100644 index 0000000..c066126 --- /dev/null +++ b/gears/docs/public/css/docs.css @@ -0,0 +1,155 @@ +body { + padding-top: 60px; + line-height: 1.6em; + padding-bottom: 20px; +} + +li { + line-height: 1.6em; +} + +p:last-child { + margin-bottom: 0; +} + +.sidebar-nav { + padding: 8px; +} + +pre { + color: #333333; + display: block; + font-family: Menlo,Monaco,"Courier New",monospace; + font-size: 12px; + line-height: 18px; +} + +.prettyprint { + margin-bottom: 24px; +} + +.com { + color: #5A525F; +} + +.lit { + color: #195F91; +} + +.pun, .opn, .clo { + color: #93A1A1; +} + +.fun { + color: #DC322F; +} + +.str, .atv { + color: #D01D33; +} + +.kwd, .linenums .tag { + color: #142459; +} + +.typ, .atn, .dec, .var { + color: #947897; +} + +.pln { + color: #666666; +} + +.prettyprint { + background-color: #F9F9F9; + border: 1px solid #F2F2F2; + border-radius: 2px 2px 2px 2px; + max-height: 300px; + overflow: auto; + padding: 20px; +} + +.prettyprint.linenums { + box-shadow: 70px 0 0 #FFFFFF inset, 71px 0 0 #F1F1F1 inset; +} + +ol.linenums { + margin: 0 0 0 50px; +} + +ol.linenums li { + color: #BEBEC5; + line-height: 21px; + margin: 0; + padding-left: 30px; + text-shadow: 0 1px 0 #FFFFFF; +} + +ol.linenums li:hover { + background-color: #F3F3F3; +} + +.prettyprint.linenums { + box-shadow: none !important; + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; +} + +.prettyprint.linenums li { + color: #F9F9F9 !important; + padding-left: 0 !important; + font-size: 12px !important; +} + +ol.linenums { + margin: 0 !important; +} + +.api { + width: 100%; + background-color: #F9F9F9; + border: 1px solid #e8e8e8; + margin: 18px 0 18px; +} + +.api:last-child { + margin: 0; +} + +.api th { + padding: 10px; + border-bottom: 1px solid #e8e8e8; + text-align: left; +} + +.api .api-label { + background-color: #f5f5f5; + vertical-align: top; + font-weight: bold; + border-right: 1px solid #e8e8e8; +} + +.api td { + padding: 10px; +} + +.api .parameters th { + text-transform: uppercase; + color: #777; + border-bottom: 0; + padding: 0; +} + +.api .parameters td { + padding: 0 48px 0 0; +} + +.api .parameter { + display: pre; + color: #333; + font-family: Menlo,Monaco,Consolas,"Courier New",monospace; +} + +.api .description { + border-top: 1px solid #e8e8e8; +} \ No newline at end of file diff --git a/gears/docs/public/img/glyphicons-halflings-white.png b/gears/docs/public/img/glyphicons-halflings-white.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 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 B7G?kd literal 0 HcmV?d00001 diff --git a/gears/docs/public/img/glyphicons-halflings.png b/gears/docs/public/img/glyphicons-halflings.png new file mode 100644 index 0000000000000000000000000000000000000000..79bc568c21395d5a5ceab62fadb04457094b2ac7 GIT binary patch literal 13826 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 literal 0 HcmV?d00001 diff --git a/gears/docs/public/js/bootstrap.js b/gears/docs/public/js/bootstrap.js new file mode 100644 index 0000000..2c00b6f --- /dev/null +++ b/gears/docs/public/js/bootstrap.js @@ -0,0 +1,7 @@ +/** +* Bootstrap.js by @fat & @mdo +* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('