From 1c3d68a73e59bdb5d2793d2f5c252cf26612e404 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Thu, 10 Dec 2015 07:30:45 -0600 Subject: [PATCH 01/15] WIP --- provision/main.sh | 7 ++++--- provision/vagrant.sh | 5 +++++ src/Template/Layout/default.ctp | 14 +++++++------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/provision/main.sh b/provision/main.sh index 46085ec..d31cd6c 100755 --- a/provision/main.sh +++ b/provision/main.sh @@ -159,7 +159,8 @@ sudo service apache2 restart # Set up DB backups on reboot/shutdown. -SHUTDOWN_DB_BACKUP_SCRIPT="/etc/init.d/k99_shutdown_db_backup" +SHUTDOWN_DB_BACKUP_SCRIPT_NAME="K99_shutdown_db_backup" +SHUTDOWN_DB_BACKUP_SCRIPT="/etc/init.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" sudo tee "${SHUTDOWN_DB_BACKUP_SCRIPT}" <<-EOSHL > /dev/null #!/usr/bin/env bash . /etc/app_env @@ -170,9 +171,9 @@ EOSHL sudo chmod a+x "${SHUTDOWN_DB_BACKUP_SCRIPT}" -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc0.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` +sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" "/etc/rc0.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc6.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` +sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" "/etc/rc6.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" # Call the environment-specific provisioning script, if it exists. diff --git a/provision/vagrant.sh b/provision/vagrant.sh index a4798b7..7daa177 100755 --- a/provision/vagrant.sh +++ b/provision/vagrant.sh @@ -40,5 +40,10 @@ sudo service apache2 reload "${PROVISION_DIR}/mailcatcher.sh" +# Set a user account password for the vagrant user to allow Sequel Pro to connect easily. +echo "## Setting vagrant user's password for easy MySQL access." +echo "vagrant:vagrant" | sudo chpasswd + + # Finish up. echo "## Done: `basename "$0"`" diff --git a/src/Template/Layout/default.ctp b/src/Template/Layout/default.ctp index 629b217..e449f1a 100755 --- a/src/Template/Layout/default.ctp +++ b/src/Template/Layout/default.ctp @@ -31,13 +31,13 @@ use Cake\Core\Configure; Html->meta('icon') ?> Html->css([ - 'normalize.css', - 'foundation.min.css', - 'app.css' + 'normalize', + 'foundation.min', + 'app', ]) ?> Html->script([ - 'vendor/modernizr.js', + 'vendor/modernizr', ]) ?> fetch('social_meta') ?> @@ -70,9 +70,9 @@ use Cake\Core\Configure; element('Layout/footer'); ?> Html->script([ - 'vendor/jquery.js', - 'foundation.min.js', - 'app.js', + 'vendor/jquery', + 'foundation.min', + 'app', ]) ?> From dac258c7fd34e0305ea64f2e19b4ebd9a5c1f2fc Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 25 Apr 2016 09:34:50 -0500 Subject: [PATCH 02/15] Add UuidShell to Composer. --- composer.json.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json.template b/composer.json.template index e3dc642..85045f8 100644 --- a/composer.json.template +++ b/composer.json.template @@ -22,7 +22,8 @@ "cakephp/bake": "~1.0", "phpunit/phpunit": "~4.8", "johnkary/phpunit-speedtrap": "~1.0@dev", - "loadsys/loadsys_codesniffer": "~3.0" + "loadsys/loadsys_codesniffer": "~3.0", + "loadsys/cakephp-uuid-shell": "~1.0" }, "autoload": { "psr-4": { From 93af24213c629b1e648716d768f35e9131631bb9 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 13 Jun 2016 07:39:49 -0500 Subject: [PATCH 03/15] Import improvements from AP project. --- README.md.template | 28 +++++++++---------- provision/main.sh | 7 ++--- .../TestCase/Controller/AppControllerTest.php | 17 +++++------ .../Controller/PagesControllerTest.php | 3 +- tests/TestCase/Lib/ConfigClosuresTest.php | 4 +-- tests/TestCase/Model/Table/TableTest.php | 6 ++-- tests/TestCase/View/AppViewTest.php | 3 +- tests/bootstrap.php | 2 +- webroot/css/app.css | 8 ++++++ 9 files changed, 41 insertions(+), 37 deletions(-) diff --git a/README.md.template b/README.md.template index 3b2ceda..34d3827 100755 --- a/README.md.template +++ b/README.md.template @@ -1,4 +1,4 @@ -_This template includes more information than a typical project requires, both to provide hints on possible things to include, as well as to make the process of filling it largely a matter of deleting information that is not applicable. Specifically; **be sure to remove or replace any notes and comments in italics,** like this one. By convention, pseudo-variables you should replace are typically in ALLCAPS._ + # [{{PROJECT_TITLE:@TODO}}]({{PROJECT_REPO_URL:https://github.com/loadsys/app}}) @@ -9,7 +9,7 @@ _This template includes more information than a typical project requires, both t {{PROJECT_DESCRIPTION_NO_QUOTES:@TODO}} -_Brief app description. Why does it exist? Who uses it?_ + * Production URL: {{PROJECT_PRODUCTION_URL:@TODO}} * Staging URL: {{PROJECT_STAGING_URL:@TODO}} @@ -19,7 +19,7 @@ _Brief app description. Why does it exist? Who uses it?_ ## Environment -_"Environment" refers to external technologies required for the app to run. Anything that the app "assumes" will be available. Memcache is part of the environment, jQuery is a library. **Always** include the minimum PHP version, PHP extensions (and versions) utilized, database software version, and any other **external** programs used. Think in particular about the production environment, even if a tool (like memcached) is not used locally in development._ + ### Hosting @@ -75,7 +75,7 @@ The following tools can easily be _made_ available by uncommenting the appropria ### Included Libaries and Submodules -_"Libraries" refer to packages that are directly executed or used by the app. Items that the app is able to obtain or install for itself are libraries. List any packages that are pulled in via composer, included as git submodules or directly bundled in the repo. Include links to the package's homepage or repo and the version number in use (if applicable). The list below is pre-populated with the submodules included in this CakePHP-Skeleton repo, and also lists some common add-ons._ + Libraries should be included with Composer whenever possible. Git submodules should be used as a fallback, and directly bundling the code into the project repo as a last resort. The Skeleton includes the following defaults: @@ -106,7 +106,7 @@ Bundled packages: ### cron Tasks -_Document anything that is expected to run outside of a normal web browser interface here. Include when it is supposed to run and any details about permissions, logging, etc._ + ``` 0 0,12 * * * /var/www/bin/cake COMMAND > /var/www/logs/COMMAND.log 2>&1 @@ -116,7 +116,7 @@ _Document anything that is expected to run outside of a normal web browser inter ## Installation -_In general, document the series of steps necessary to set up the project on a new system (development or production). If there is a setup shell script, don't document its internal steps (the script itself does that), just how to run it. If setup is manual, list each step in order._ + ### Development (vagrant) @@ -124,7 +124,7 @@ _In general, document the series of steps necessary to set up the project on a n Developers are expected to use the vagrant environment for all local work. Using a \_AMP stack on your machine directly is no longer advised or supported. You should have everything from the [Developer Specific](#Developer-specific) section above already installed on your Mac. ```bash -git clone {{PROJECT_REPO_CLONE_URL:@TODO}} ./ +git clone {{PROJECT_REPO_CLONE_SSH:@TODO}} ./ ./bootstrap.sh vagrant ``` @@ -139,7 +139,7 @@ The bootstrap script takes care of installing dependencies. After this process, * 1080 TCP from Anywhere (Mailcatcher) * SSH from `66.228.59.201/32` (Loadsysdev) 1. ssh into the box. - 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_HTTPS_URL:@TODO}} staging staging` + 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} staging staging` ### Production @@ -155,12 +155,12 @@ The bootstrap script takes care of installing dependencies. After this process, * (Locally) Update the `config/app.php` with the new credentials and commit/push them to GitHub. 1. Create or provision a new web instance server (EC2), matching the Vagrant `box` in use (or vice-versa). This currently means **Ubuntu Server 14.04 LTS 64bit**. 1. ssh into the box. - 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_HTTPS_URL:@TODO}} master master` + 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} master master` ## Contributing -_Information a developer would need to work on the project in the "correct" way. (Tests, etc.)_ + ### After Pulling @@ -174,7 +174,7 @@ On your host: * `bin/cache-clear` (Make sure temp files are reset between host/vm use.) * `bin/db-backup` (Store the previous database contents before running schema/data updates.) * `bin/migrations` (Set up the DB with the latest schema.) - * `bin/cake BasicSeed.basic_seed` (Populate the latest set of development data from the seeds, if the plugin is available.) + * `bin/cake basic_seed -v` (Populate the latest set of development data from the seeds, if the plugin is available.) ### Developer Workflow @@ -228,7 +228,7 @@ Create a new "SSH" connection with the following settings: * Port: 3306 * SSH Host: 127.0.0.1 * SSH User: vagrant -* SSH Password: vagrant (Or [someone online](https://coderwall.com/p/yzwqvg) say you can point to your local `~/.vagrant.d/insecureprivatekey`.) +* SSH Password: vagrant (Or [someone online](https://coderwall.com/p/yzwqvg) says you can point to your local `~/.vagrant.d/insecureprivatekey`.) * SSH Port: 2222 (per `config/provision.yaml`.) This setup is handy for backing up your data if you're about to destroy the box, or for making Schema or Seed changes before running the Shell commands in the VM. @@ -246,7 +246,7 @@ This setup is handy for backing up your data if you're about to destroy the box, #### Sample Data -* Both test data and started production data is maintained by the Loadsys Basic Seeds plugin. +* Both test data and starting production data is maintained by the Loadsys Basic Seeds plugin. * The default seed files are environment aware, making it possible to run the same command regardless of environment. * To populate (or reset) your local dev data back to the contents of the seed: * `bin/cake BasicSeed.basic_seed` @@ -328,4 +328,4 @@ New devs should all run through these steps to get familiar with the app and the ## License -Copyright (c) 2015 {{PROJECT_CLIENT_NAME:@TODO}} +Copyright (c) 2016 {{PROJECT_CLIENT_NAME:@TODO}} diff --git a/provision/main.sh b/provision/main.sh index d31cd6c..46085ec 100755 --- a/provision/main.sh +++ b/provision/main.sh @@ -159,8 +159,7 @@ sudo service apache2 restart # Set up DB backups on reboot/shutdown. -SHUTDOWN_DB_BACKUP_SCRIPT_NAME="K99_shutdown_db_backup" -SHUTDOWN_DB_BACKUP_SCRIPT="/etc/init.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" +SHUTDOWN_DB_BACKUP_SCRIPT="/etc/init.d/k99_shutdown_db_backup" sudo tee "${SHUTDOWN_DB_BACKUP_SCRIPT}" <<-EOSHL > /dev/null #!/usr/bin/env bash . /etc/app_env @@ -171,9 +170,9 @@ EOSHL sudo chmod a+x "${SHUTDOWN_DB_BACKUP_SCRIPT}" -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" "/etc/rc0.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" +sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc0.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" "/etc/rc6.d/${SHUTDOWN_DB_BACKUP_SCRIPT_NAME}" +sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc6.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` # Call the environment-specific provisioning script, if it exists. diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php index 310b669..0b7277f 100644 --- a/tests/TestCase/Controller/AppControllerTest.php +++ b/tests/TestCase/Controller/AppControllerTest.php @@ -1,24 +1,25 @@ [ [ - 'className' => 'RandomClass' + 'className' => 'RandomClass', ], [ 'className' => 'RandomClass', @@ -79,7 +79,7 @@ public function providerCacheMerge() { 'Prefix and ClassName Overrides' => [ [ 'className' => 'RandomClass', - 'prefix' => 'something_' + 'prefix' => 'something_', ], [ 'className' => 'RandomClass', diff --git a/tests/TestCase/Model/Table/TableTest.php b/tests/TestCase/Model/Table/TableTest.php index a53412d..1eea471 100644 --- a/tests/TestCase/Model/Table/TableTest.php +++ b/tests/TestCase/Model/Table/TableTest.php @@ -10,17 +10,15 @@ use Cake\TestSuite\TestCase; /** - * App\Model\Table\Table Test Case + * App\Test\TestCase\Model\Table\TableTest */ class TableTest extends TestCase { - /** * Fixtures * * @var array */ - public $fixtures = [ - ]; + public $fixtures = []; /** * setUp method diff --git a/tests/TestCase/View/AppViewTest.php b/tests/TestCase/View/AppViewTest.php index 8d98c18..9f612e9 100644 --- a/tests/TestCase/View/AppViewTest.php +++ b/tests/TestCase/View/AppViewTest.php @@ -16,8 +16,7 @@ class AppViewTest extends TestCase { * * @var array */ - public $fixtures = [ - ]; + public $fixtures = []; /** * setUp method diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c57ea15..d99f1fe 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -30,7 +30,7 @@ 'plugins' => [TEST_APP . 'Plugin' . DS], 'templates' => [APP . 'Template' . DS], 'locales' => [APP . 'Locale' . DS], - ] + ], ]); diff --git a/webroot/css/app.css b/webroot/css/app.css index 08ae114..e39e48c 100644 --- a/webroot/css/app.css +++ b/webroot/css/app.css @@ -28,3 +28,11 @@ footer ul.inline-list li { font-size: 0.75em; } +.top-bar-section li.text { + background: #8d1c1c; + color: #eee; + font-size: 0.8125rem; + font-weight: normal; + padding: 12px 0.9375rem 12px 0.9375rem; + text-transform: none; +} From d3e9823529c8fc27fb6f3f59a9473af65c333093 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 13 Jun 2016 08:20:11 -0500 Subject: [PATCH 04/15] Import improvements from WMS2 project. --- composer.json.template | 3 ++- config/app-vagrant.php | 8 ++++---- config/app.php.template | 7 +++++++ src/Controller/AppController.php | 3 +++ src/Lib/ConfigClosures.php | 14 ++++++-------- src/Model/Table/Table.php | 8 +++----- src/Template/Element/Flash/warning.ctp | 11 +++++++++++ src/Template/Layout/default.ctp | 4 ++-- src/View/AppView.php | 3 ++- tests/TestCase/Controller/AppControllerTest.php | 2 +- tests/TestCase/Lib/ConfigClosuresTest.php | 5 ----- tests/TestCase/Model/Table/TableTest.php | 2 +- tests/TestCase/View/AppViewTest.php | 9 ++++++++- webroot/css/app.css | 2 +- 14 files changed, 51 insertions(+), 30 deletions(-) create mode 100644 src/Template/Element/Flash/warning.ctp diff --git a/composer.json.template b/composer.json.template index 85045f8..d715352 100644 --- a/composer.json.template +++ b/composer.json.template @@ -15,7 +15,8 @@ "loadsys/cakephp-creatormodifier": "~1.0", "loadsys/cakephp-libregistry": "~1.0", "loadsys/cakephp-loadsys-theme": "dev-master", - "loadsys/cakephp-shell-scripts": "~3.0" + "loadsys/cakephp-shell-scripts": "~3.0", + "loadsys/cakephp-auth-userentity": "~1.0" }, "require-dev": { "cakephp/debug_kit": "~3.2", diff --git a/config/app-vagrant.php b/config/app-vagrant.php index ab05867..d0a717f 100755 --- a/config/app-vagrant.php +++ b/config/app-vagrant.php @@ -24,26 +24,26 @@ 'Cache' => [ 'default' => [ 'compress' => false, - 'duration' => 120, + 'duration' => 0, 'servers' => '127.0.0.1', 'username' => null, 'password' => null, ], '_cake_core_' => [ - 'duration' => 120, + 'duration' => 0, 'servers' => '127.0.0.1', 'username' => null, 'password' => null, ], '_cake_model_' => [ - 'duration' => 120, + 'duration' => 0, 'servers' => '127.0.0.1', 'username' => null, 'password' => null, ], 'sessions' => [ 'compress' => false, - 'duration' => 120, + 'duration' => '+1 day', 'servers' => '127.0.0.1', 'username' => null, 'password' => null, diff --git a/config/app.php.template b/config/app.php.template index 4ab2e95..71763f2 100755 --- a/config/app.php.template +++ b/config/app.php.template @@ -278,6 +278,13 @@ return [ 'file' => 'error', 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], ], + 'performance' => [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'file' => 'performance', + //'levels' => [], + 'scopes' => ['performance'], + ], ], /** diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index ba87e72..7354b73 100755 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -15,6 +15,7 @@ namespace App\Controller; use Cake\Controller\Controller; +use LibRegistry\LibRegistryTrait; /** * Application Controller @@ -25,6 +26,8 @@ * @link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller */ class AppController extends Controller { + // Allow access to shared/injected libraries, works like TableRegistry. + use LibRegistryTrait; /** * Initialization hook method. diff --git a/src/Lib/ConfigClosures.php b/src/Lib/ConfigClosures.php index 3ca4826..40ac400 100644 --- a/src/Lib/ConfigClosures.php +++ b/src/Lib/ConfigClosures.php @@ -7,6 +7,7 @@ namespace App\Lib; use Cake\Core\Configure; +use Cake\ORM\Entity as User; use Cake\Utility\Hash; /** @@ -73,14 +74,11 @@ public static function creditCardYear() { * properties set. */ public static function userEntity($email, $firstname = '', $lastname = '') { - $entity = new \App\Model\Entity\User(); - - $entity->email = $email; - $entity->firstname = $firstname; - $entity->lastname = $lastname; - $entity->ignore_invalid_email = true; - - return $entity; + return new User([ + 'email' => $email, + 'firstname' => $firstname, + 'lastname' => $lastname, + ]); } /** diff --git a/src/Model/Table/Table.php b/src/Model/Table/Table.php index 7eed8c7..c7502bb 100644 --- a/src/Model/Table/Table.php +++ b/src/Model/Table/Table.php @@ -13,11 +13,9 @@ use Cake\Validation\Validator; /** - * App Model - * + * \App\Model\Table\Table */ class Table extends CakeORMTable { - /** * Initialize method * @@ -34,11 +32,11 @@ public function initialize(array $config) { $this->belongsTo('Creators', [ 'className' => 'Users', - 'foreignKey' => 'creator_id' + 'foreignKey' => 'creator_id', ]); $this->belongsTo('Modifiers', [ 'className' => 'Users', - 'foreignKey' => 'modifier_id' + 'foreignKey' => 'modifier_id', ]); } diff --git a/src/Template/Element/Flash/warning.ctp b/src/Template/Element/Flash/warning.ctp new file mode 100644 index 0000000..d49c1d9 --- /dev/null +++ b/src/Template/Element/Flash/warning.ctp @@ -0,0 +1,11 @@ + + +
+ + × +
diff --git a/src/Template/Layout/default.ctp b/src/Template/Layout/default.ctp index e449f1a..69ed825 100755 --- a/src/Template/Layout/default.ctp +++ b/src/Template/Layout/default.ctp @@ -65,9 +65,9 @@ use Cake\Core\Configure; fetch('content') ?> - - element('Layout/footer'); ?> + element('Layout/footer'); ?> + Html->script([ 'vendor/jquery', diff --git a/src/View/AppView.php b/src/View/AppView.php index 73ba6ce..9d1d7ed 100755 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -7,7 +7,7 @@ use Cake\View\View; /** - * App View class + * \App\View\AppView */ class AppView extends View { /** @@ -25,6 +25,7 @@ public function initialize() { 'errorClass' => 'error', 'templates' => [ 'error' => '{{content}}', + 'radioContainer' => '
{{content}}
', ], ]); $this->loadHelper('Flash'); diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php index 0b7277f..48d4187 100644 --- a/tests/TestCase/Controller/AppControllerTest.php +++ b/tests/TestCase/Controller/AppControllerTest.php @@ -8,7 +8,7 @@ use Cake\TestSuite\IntegrationTestCase; /** - * App\Test\TestCase\Controller\AppControllerTest + * \App\Test\TestCase\Controller\AppControllerTest */ class AppControllerTest extends IntegrationTestCase { /** diff --git a/tests/TestCase/Lib/ConfigClosuresTest.php b/tests/TestCase/Lib/ConfigClosuresTest.php index a7cfdae..c501e59 100644 --- a/tests/TestCase/Lib/ConfigClosuresTest.php +++ b/tests/TestCase/Lib/ConfigClosuresTest.php @@ -158,11 +158,6 @@ public function testUserEntity($email, $firstname, $lastname) { $output->lastname, 'The generated User->lastname Entity should match the output' ); - $this->assertEquals( - $entity->ignore_invalid_email, - $output->ignore_invalid_email, - 'The generated User->ignore_invalid_email Entity should match the output' - ); } /** diff --git a/tests/TestCase/Model/Table/TableTest.php b/tests/TestCase/Model/Table/TableTest.php index 1eea471..7125507 100644 --- a/tests/TestCase/Model/Table/TableTest.php +++ b/tests/TestCase/Model/Table/TableTest.php @@ -10,7 +10,7 @@ use Cake\TestSuite\TestCase; /** - * App\Test\TestCase\Model\Table\TableTest + * \App\Test\TestCase\Model\Table\TableTest */ class TableTest extends TestCase { /** diff --git a/tests/TestCase/View/AppViewTest.php b/tests/TestCase/View/AppViewTest.php index 9f612e9..abb09a9 100644 --- a/tests/TestCase/View/AppViewTest.php +++ b/tests/TestCase/View/AppViewTest.php @@ -52,7 +52,14 @@ public function testInitialize() { ->with('Html'); $AppView->expects($this->at(1)) ->method('loadHelper') - ->with('Form'); + ->with( + 'Form', + $this->callback(function ($options) { + $this->assertArrayHasKey('errorClass', $options); + $this->assertArrayHasKey('templates', $options); + return true; + }) + ); $AppView->expects($this->at(2)) ->method('loadHelper') ->with('Flash'); diff --git a/webroot/css/app.css b/webroot/css/app.css index e39e48c..a69c259 100644 --- a/webroot/css/app.css +++ b/webroot/css/app.css @@ -1,5 +1,5 @@ div#container { - margin-top: 5px; + margin-top: 0.3125rem; } div.contain-to-grid { From 291176ca8717eb0e64b4270693a3d7809c7630cc Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Mon, 13 Jun 2016 11:45:37 -0500 Subject: [PATCH 05/15] Import of improvements from EU2 project. --- .editorconfig | 2 +- .gitattributes | 2 + .gitignore | 9 +- .sass-lint.yml | 270 +++++++++++++ .travis.yml.template | 16 +- Gruntfile.js | 146 +++++++ README.md.template | 43 +-- bootstrap.sh | 22 +- bower.json.template | 7 + composer.json | 4 +- composer.json.template | 13 +- config/app-local.sample.php | 6 +- config/app-staging.php | 15 +- config/app-travis.php | 5 +- config/app-vagrant.php | 1 + config/app.php.template | 41 +- config/bootstrap.php | 49 ++- config/routes.php | 26 -- config/seed.php | 2 +- package.json.template | 27 ++ phpcs.xml.template | 7 + provision/010-cake.conf | 9 +- provision/apache-vagrant.conf | 10 + provision/baremetal.sh | 3 + provision/dot-files/.gemrc | 4 + provision/mailcatcher.sh | 4 +- provision/main.sh | 35 +- provision/mysql-shutdown-backup.conf | 23 ++ provision/production.sh | 2 + provision/vagrant.sh | 34 +- skel/test-project.sh | 6 +- src/Controller/AppController.php | 145 +++++++ src/Controller/PagesController.php | 10 + src/Database/Type/JsonType.php | 79 ++++ src/Lib/Log/LogTrait.php | 50 +++ src/Model/Table/Table.php | 18 +- src/Template/Element/Flash/default.ctp | 2 +- src/Template/Element/Flash/error.ctp | 2 +- src/Template/Element/Flash/success.ctp | 4 +- src/Template/Element/Flash/warning.ctp | 4 +- src/Template/Element/Layout/breadcrumbs.ctp | 17 +- src/Template/Layout/default.ctp | 11 +- src/View/AppView.php | 7 + src/View/Helper/ListsHelper.php | 58 +++ .../TestCase/Controller/AppControllerTest.php | 356 +++++++++++++++++- tests/TestCase/Database/Type/JsonTypeTest.php | 182 +++++++++ tests/TestCase/Lib/ConfigClosuresTest.php | 19 +- tests/TestCase/Lib/Log/LogTraitTest.php | 101 +++++ tests/TestCase/Migrations/MigrationsTest.php | 115 ++++-- tests/TestCase/Model/Table/TableTest.php | 8 +- .../TestCase/View/Helper/ListsHelperTest.php | 141 +++++++ tests/bootstrap.php | 10 +- webroot/.htaccess | 5 + webroot/css/build/.gitkeep | 0 webroot/css/{app.css => src/app.scss} | 15 + webroot/js/app.js | 1 - webroot/js/build/.gitkeep | 0 webroot/js/src/app.js | 8 + webroot/js/src/init.js | 17 + webroot/js/test/.gitkeep | 0 webroot/test.html | 22 ++ 61 files changed, 2065 insertions(+), 185 deletions(-) create mode 100644 .sass-lint.yml create mode 100644 Gruntfile.js create mode 100644 bower.json.template create mode 100644 package.json.template create mode 100644 phpcs.xml.template create mode 100644 provision/apache-vagrant.conf create mode 100644 provision/dot-files/.gemrc create mode 100644 provision/mysql-shutdown-backup.conf create mode 100644 src/Database/Type/JsonType.php create mode 100644 src/Lib/Log/LogTrait.php create mode 100644 src/View/Helper/ListsHelper.php create mode 100644 tests/TestCase/Database/Type/JsonTypeTest.php create mode 100644 tests/TestCase/Lib/Log/LogTraitTest.php create mode 100644 tests/TestCase/View/Helper/ListsHelperTest.php create mode 100644 webroot/css/build/.gitkeep rename webroot/css/{app.css => src/app.scss} (64%) delete mode 100644 webroot/js/app.js create mode 100644 webroot/js/build/.gitkeep create mode 100644 webroot/js/src/app.js create mode 100644 webroot/js/src/init.js create mode 100644 webroot/js/test/.gitkeep create mode 100644 webroot/test.html diff --git a/.editorconfig b/.editorconfig index e8c541a..4d3c2d8 100755 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,6 @@ insert_final_newline = true trim_trailing_whitespace = true ; Matches the exact files package.json and .travis.yml -[{package.json,.travis.yml,composer.json,*.yaml,*.yml}] +[{package.json,.travis.yml,composer.json,*.yaml,*.yml,*.scss}] indent_style = space indent_size = 2 diff --git a/.gitattributes b/.gitattributes index 926a808..89e26de 100755 --- a/.gitattributes +++ b/.gitattributes @@ -34,3 +34,5 @@ *.mo binary *.pdf binary *.phar binary + +# Paths, files and extensions managed by Large File Support. diff --git a/.gitignore b/.gitignore index cb33a5b..f0846d8 100755 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,13 @@ /.vagrant/ /phpunit.xml -# Composer Dependencies +# Composer Dependencies & Build Tooling /vendor/* /node_modules +/bower_components +/.sass-cache +/webroot/css/qunit.css + # OS X .DS_Store @@ -24,3 +28,6 @@ Icon? Network Trash Folder Temporary Items .apdisk + +# IDEs +/.idea/ diff --git a/.sass-lint.yml b/.sass-lint.yml new file mode 100644 index 0000000..1fc0dfc --- /dev/null +++ b/.sass-lint.yml @@ -0,0 +1,270 @@ +######################### +## Sample Sass Lint File +######################### +# Ref: https://raw.githubusercontent.com/sasstools/sass-lint/master/docs/sass-lint.yml +# Linter Options +options: + # Don't merge default rules + # merge-default-rules: false + # Set the formatter to 'html' + #formatter: html + # Output file instead of logging results + #output-file: 'linters/sass-lint.html' +# File Options +#files: +# include: 'sass/**/*.s+(a|c)ss' +# ignore: +# - 'sass/vendor/**/*.*' +# Rule Configuration +rules: + extends-before-mixins: 2 + extends-before-declarations: 2 + placeholder-in-extend: 2 + mixins-before-declarations: + - 2 + - + exclude: + - breakpoint + - mq + + no-warn: 1 + no-debug: 1 + no-ids: 2 + no-important: 2 + hex-notation: + - 2 + - + style: uppercase + indentation: + - 2 + - + size: 2 + property-sort-order: + - 1 + - + order: + - display + - margin + ignore-custom-properties: true + variable-for-property: + - 2 + - + properties: + - margin + - content + + + + + + +#options: +# formatter: stylish +# +# +#files: +# include: 'css/**/*.scss' +# +# +#rules: +# border-zero: +# - 1 +# - convention: none +# +# brace-style: +# - 1 +# - style: 1tbs +# allow-single-line: false +# +# clean-import-paths: +# - 1 +# - leading-underscore: false +# filename-extension: false +# +# empty-args: +# - 1 +# - include: false +# +# empty-line-between-blocks: +# - 1 +# - include: true +# allow-single-line-rulesets: false +# +# extends-before-declarations: +# - 1 +# +# extends-before-mixins: +# - 1 +# +# final-newline: +# - 1 +# - include: true +# +# force-attribute-nesting: +# - 0 +# +# force-element-nesting: +# - 0 +# +# foce-pseudo-nesting: +# - 0 +# +# function-name-format: +# - 1 +# - allow-leading-underscore: true +# convention: hyphenatedlowercase +# +# hex-length: +# - 1 +# - style: short +# +# hex-notation: +# - 1 +# - style: uppercase +# +# indentation: +# - 1 +# - size: 1 +# +# leading-zero: +# - 1 +# - include: false +# +# mixin-name-format: +# - 1 +# - allow-leading-underscore: true +# convention: hyphenatedlowercase +# +# mixins-before-declarations: +# - 1 +# +# nesting-depth: +# - 2 +# - max-depth: 4 +# +# no-color-keywords: +# - 0 +# +# no-color-literals: +# - 2 +# - allow-rgba: true +# +# no-css-comments: +# - 0 +# +# no-debug: +# - 0 +# +# no-duplicate-properties: +# - 2 +# +# no-empty-rulesets: +# - 1 +# +# no-extends: +# - 0 +# +# no-ids: +# - 0 +# +# no-important: +# - 2 +# +# no-invalid-hex: +# - 2 +# +# no-mergable-selectors: +# - 0 +# +# no-misspelled-properties: +# - 2 +# +# no-qualifying-elements: +# - 0 +# +# no-trailing-zero: +# - 1 +# +# no-transition-all: +# - 1 +# +# no-url-protocols: +# - 1 +# +# no-vendor-prefixes: +# - 2 +# +# no-warn: +# - 0 +# +# one-declaration-per-line: +# - 1 +# +# placeholder-in-extends: +# - 0 +# +# placeholder-name-format: +# - 1 +# - allow-leading-underscore: true +# convention: hyphenatedlowercase +# +# property-sort-order: +# - 1 +# - order: concentric +# +# quotes: +# - 1 +# - style: single +# +# shorthand-values: +# - 0 +# +# single-line-per-selector: +# - 1 +# +# space-after-bang: +# - 1 +# - include: false +# +# space-after-colon: +# - 1 +# - include: true +# +# space-after-comma: +# - 1 +# - include: true +# +# space-before-bang: +# - 1 +# - include: true +# +# space-before-brace: +# - 1 +# - include: true +# +# space-before-colon: +# - 1 +# - include: false +# +# space-between-parens: +# - 1 +# - include: false +# +# trailing-semicolon: +# - 1 +# - include: true +# +# url-quotes: +# - 1 +# +# variable-for-property: +# - 0 +# +# variable-name-format: +# - 1 +# - allow-leading-underscore: true +# convention: hyphenatedlowercase +# +# zero-unit: +# - 1 +# - include: false diff --git a/.travis.yml.template b/.travis.yml.template index 91dfaf7..f19535d 100644 --- a/.travis.yml.template +++ b/.travis.yml.template @@ -12,6 +12,8 @@ php: cache: directories: - $HOME/.composer/cache + - bower_components + - node_modules # Environment Variables to set env: @@ -34,22 +36,32 @@ branches: - gh-pages before_install: - - composer self-update + #- composer self-update - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini install: - composer config -g github-oauth.github.com $GITHUB_TOKEN - composer install --no-interaction + - npm install --save-dev before_script: - phpenv rehash - mysql -e 'DROP DATABASE IF EXISTS `travis_app`; CREATE DATABASE `travis_app`;' script: - # The -n option can be used to suppress warnings + # Test the main app - bin/codesniffer-run - bin/phpunit -v - bin/coverage-ensure 90 + # Test the Javascript + #- grunt qunit --verbose --force + # Run Behat behavior tests + #- mysql -e 'DROP DATABASE IF EXISTS `travis_app`; CREATE DATABASE `travis_app`;' + #- bin/cake server -p 8765 & + #- sleep 3 + #- bin/migrations + #- bin/cake basic_seed -v + #- bin/behat -v -p travis notifications: email: false diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..90caffc --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,146 @@ +module.exports = function(grunt) { + 'use strict'; + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + // Install Tasks + bowercopy: { + css: { + options: { + srcPrefix: 'bower_components', + destPrefix: 'webroot/css' + }, + files: { + 'qunit.css': 'bower_components/qunit/qunit/qunit.css' + } + } + }, + + // CSS Tasks + sass: { + dist: { + options: { + loadPath: ['bower_components/foundation-sites/scss'], + }, + files: { + 'webroot/css/build/app.css': 'webroot/css/src/app.scss' + } + } + }, + postcss: { + options: { + map: true, + sourcesContent: true, + processors: [ + require('autoprefixer')({ + browsers: ['last 2 versions', 'ie >= 9', 'and_chr >= 2.3'] + }), + require('cssnano')() + ] + }, + dist: { src: 'webroot/css/build/*.css' } + }, + sasslint: { + options: { + configFile: '.sass-lint.yml', + //formatter: 'unix', // Ref: https://github.com/eslint/eslint/tree/master/lib/formatters + //outputFile: 'tmp/sasslint-report.txt' + }, + dist: [ + 'webroot/css/src/*.scss' + ] + }, + + // JS Tasks + concat: { + options: { + separator: ';', + }, + main: { + src: [ + 'webroot/js/vendor/jquery.js', + 'bower_components/foundation-sites/js/foundation.core.js', + 'bower_components/foundation-sites/js/foundation.util.mediaQuery.js', + 'bower_components/foundation-sites/js/foundation.drilldown.js', + 'bower_components/foundation-sites/js/foundation.dropdown.js', + 'bower_components/foundation-sites/js/foundation.dropdownMenu.js', + 'bower_components/foundation-sites/js/foundation.equalizer.js', + 'bower_components/foundation-sites/js/foundation.orbit.js', + 'bower_components/foundation-sites/js/foundation.responsiveMenu.js', + 'bower_components/foundation-sites/js/foundation.responsiveToggle.js', + 'bower_components/foundation-sites/js/foundation.tabs.js', + 'bower_components/foundation-sites/js/foundation.toggler.js', + 'bower_components/foundation-sites/js/foundation.util.box.js', + 'bower_components/foundation-sites/js/foundation.util.keyboard.js', + 'bower_components/foundation-sites/js/foundation.util.motion.js', + 'bower_components/foundation-sites/js/foundation.util.nest.js', + 'bower_components/foundation-sites/js/foundation.util.timerAndImageLoader.js', + 'bower_components/foundation-sites/js/foundation.util.touch.js', + 'bower_components/foundation-sites/js/foundation.util.triggers.js', + 'webroot/js/src/init.js', + 'webroot/js/src/app.js', + ], + dest: 'webroot/js/build/scripts.js' + }, + tests: { + src: [ + 'webroot/js/vendor/modernizr.js', + 'webroot/js/vendor/jquery.js', + 'webroot/js/init.js', + 'webroot/js/src/order-balance.js', + 'bower_components/qunit/qunit/qunit.js', + 'webroot/js/test/*.js', + ], + dest: 'webroot/js/build/tests.js' + } + }, + uglify: { + options: { + mangle: false + }, + my_target: { + files: { + 'webroot/js/build/scripts.min.js': ['webroot/js/build/scripts.js'], + } + } + }, + qunit: { + all: ['webroot/test.html'] + }, + + // Developer Tasks + watch: { + css: { + files: ['webroot/css/src/*.scss'], + tasks: ['sass'] + }, + jss: { + files: ['webroot/js/test/*.js', 'webroot/js/src/*.js'], + tasks: ['concat', 'qunit'] //@TODO: The `uglify` task is still technically required here to get `webroot/js/src/*.js` into `webroot/js/build/`. Could be replaced with a simple "copy" command during file watch though. + } + } + }); + + grunt.loadNpmTasks('grunt-bowercopy'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-sass'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-postcss'); + grunt.loadNpmTasks('grunt-sass-lint'); + + grunt.registerTask('css', [ + 'sass:dist', 'postcss:dist' // build css assets (in order) + ]); + grunt.registerTask('sasstest', [ + 'sasslint:dist' // lint source scss files + ]); + grunt.registerTask('testjs', [ + 'concat', 'uglify', 'qunit' // build js assets and run tests (in order) + ]); + grunt.registerTask('default', [ + 'css', 'testjs' + ]); +}; diff --git a/README.md.template b/README.md.template index 34d3827..7e5ac23 100755 --- a/README.md.template +++ b/README.md.template @@ -25,7 +25,7 @@ This section documents the minimum required tools for hosting this application. -* [CakePHP](https://github.com/cakephp/cakephp) v3.1+ +* [CakePHP](https://github.com/cakephp/cakephp) v3.2+ * PHP v5.6+ * intl * pdo + mysql @@ -47,6 +47,7 @@ The following tools **should be installed on your development machine** in order * PHP v5.4+ (Mac system default should work fine.) * [composer](http://getcomposer.org/) for dependency management. * [git](https://git-scm.com/) + * [git-lfs](https://git-lfs.github.com/) :exclamation: **Recommended** in order to store photos and assets "in" the repo using Large File Support. * Either of the following: * [VirtualBox](https://www.virtualbox.org/) v4.3+ (free) * [VMware Fusion](http://www.vmware.com/products/fusion) v6+ plus the [vagrant VMware plugin](https://www.vagrantup.com/vmware) (not free, but **fast**) @@ -54,23 +55,17 @@ The following tools **should be installed on your development machine** in order * [vagrant-bindfs](https://github.com/gael-ian/vagrant-bindfs) * [vagrant-cachier](https://github.com/fgrehm/vagrant-cachier) * [vagrant-vbguest](https://github.com/dotless-de/vagrant-vbguest) -* For automatically running tests: - * [node.js](http://nodejs.org/download/) - * [npm](https://npmjs.org/) - * [grunt-cli](http://gruntjs.com/getting-started) Vagrant + VirtualBox/VMware provide the following additional tools inside the VM. There are no "optional" installs. Developers must be able to run tests, generate phpDocs and run the code sniffer locally before committing. Thankfully, **the vagrant VM provides the following tools**, including: * PHP's [xdebug extension](http://xdebug.org/) v2+ * PHP's sqlite extension (for DebugKit). * [composer](https://getcomposer.org/) - -The following tools can easily be _made_ available by uncommenting the appropriate section of the `provision/main.sh` script: - * [nodejs](http://nodejs.org/) + [npm](https://www.npmjs.org/) * [`json`](http://trentm.com/json/) command line tool. * ember-cli * grunt-cli + * bower ### Included Libaries and Submodules @@ -81,14 +76,14 @@ Libraries should be included with Composer whenever possible. Git submodules sho Composer-provided: -* [CakePHP](https://github.com/cakephp/cakephp) v3.0+ +* [CakePHP](https://github.com/cakephp/cakephp) v3.2+ * [Bake](https://github.com/cakephp/bake) * [DebugKit](https://github.com/cakephp/debug_kit) +* [Migrations](https://github.com/cakephp/migrations) * [Loadsys Cake Basic Seeds](https://github.com/loadsys/CakePHP-Basic-Seed) * [Loadsys Cake ConfigReadShell](https://github.com/loadsys/CakePHP-ConfigReadShell) * [Loadsys Cake LibRegistry](https://github.com/loadsys/CakePHP-LibRegistry) * [Loadsys Cake Shell Scripts](https://github.com/loadsys/CakePHP-Shell-Scripts) -* [Migrations](https://github.com/cakephp/migrations) * [phpunit](http://phpunit.de/) v4.1+ * [phpDocumentor](http://phpdoc.org/) v2 * [PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) v2 @@ -139,7 +134,7 @@ The bootstrap script takes care of installing dependencies. After this process, * 1080 TCP from Anywhere (Mailcatcher) * SSH from `66.228.59.201/32` (Loadsysdev) 1. ssh into the box. - 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} staging staging` + 1. Run `curl https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} staging staging` ### Production @@ -155,7 +150,7 @@ The bootstrap script takes care of installing dependencies. After this process, * (Locally) Update the `config/app.php` with the new credentials and commit/push them to GitHub. 1. Create or provision a new web instance server (EC2), matching the Vagrant `box` in use (or vice-versa). This currently means **Ubuntu Server 14.04 LTS 64bit**. 1. ssh into the box. - 1. Run `https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} master master` + 1. Run `curl https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/baremetal-bootstrap | bash -s -- {{PROJECT_REPO_CLONE_HTTPS:@TODO}} master master` ## Contributing @@ -169,12 +164,15 @@ Things to do after pulling updates from the remote repo. On your host: -* `composer install` (Install any changed/updated dependencies.) -* `vagrant ssh` - * `bin/cache-clear` (Make sure temp files are reset between host/vm use.) +* `bin/deps-install` (Install any changes/updated dependencies from git submodules, composer, pear, npm, etc.) +* `vagrant provision` (Make any changes to the VM's config that may be necessary, and runs associated Cake provisioning steps:) + * `bin/clear-cache` (Make sure temp files are reset between host/vm use.) * `bin/db-backup` (Store the previous database contents before running schema/data updates.) * `bin/migrations` (Set up the DB with the latest schema.) - * `bin/cake basic_seed -v` (Populate the latest set of development data from the seeds, if the plugin is available.) + * `bin/cake basic_seed -v` (Populate the latest set of development data from the seeds.) + * `npm install --save-dev` (Installs grunt modules.) + * `bower install` (Installs front end dependencies.) + * `grunt bowercopy` (Moves css files from bower_components to webroot/css.) ### Developer Workflow @@ -283,11 +281,13 @@ Unit tests should be created for all new code written in the following categorie * Components * Helper methods * Shells and Tasks -* Libraries in `Lib/' +* Libraries in `src/Lib/' * Javascript in `webroot/js/` * **Bundled** plugins +Command line automated "test-on-save" is also possible with Grunt via: `grunt watch`. This will block the terminal while it waits for file changes. New files should get picked up as well. + The full testsuite is run using `bin/phpunit` from the root of the project. There are three testsuites setup as part of the phpunit.xml.dist file. @@ -299,12 +299,13 @@ There are three testsuites setup as part of the phpunit.xml.dist file. ### Javascript Unit Testing -**@TODO: Review and update this section.** - -* Tests can also be written for the browser JavaScript code. +* Tests should also be written for the browser JavaScript code. * Javascript should be written in individual "class" files (they will be merged by asset compilation) in `webroot/js/src/`. -* Anything you would normally put in a `document.ready(...)` call should be placed in @TODO. +* Anything you would normally put in a `document.ready(...)` call should be placed in `webroot/js/init.js`. * Matching test files should be created in `webroot/js/test/`. +* Tests are run with phantomjs via grunt: + * Run `grunt watch` to run the test suite on js test or source file change. + * Run `grunt qunit` to run the qunit test suite. ### CSS Changes diff --git a/bootstrap.sh b/bootstrap.sh index f35963e..80e6b91 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -66,13 +66,22 @@ VAGRANT_CONFIG_FILE="$DIR/Vagrantfile" VAGRANT="$( which vagrant )" -# Checking specifically for `vagrant` here is a magic string, but what can we do? +# Checking for `vagrant`. if [ $? -eq 0 ] && [ -e "$VAGRANT_CONFIG_FILE" ] && [ "$NEW_APP_ENV" = "vagrant" ]; then echo "## Found Vagrant: ${VAGRANT}" echo "## Found Vagrant config file: $VAGRANT_CONFIG_FILE" echo "## Provisioning for development." + # Verify required apps are installed. + APP_LIST=('composer' 'node' 'npm' 'grunt' 'bower') + for APP in "${APP_LIST[@]}"; do + if ! command -v "$APP" >/dev/null 2>&1; then + echo "!! Required command missing: \`$APP\`. Please install before continuing." + exit 1 + fi + done + vagrant up # Otherwise provision "bare metal" for the APP_ENV provided. @@ -90,7 +99,7 @@ fi # Install composer packages using versions specified in config/lock file. -echo "## Running \`composer install\`." +echo "## Installing project dependencies." composer install --no-interaction --ignore-platform-reqs --optimize-autoloader @@ -112,6 +121,13 @@ read -p ">> Enter a GitHub read-only, public-only auth token: " COMPOSER_TOKEN bin/vagrant-exec "composer config --global github-oauth.github.com $COMPOSER_TOKEN" +npm install --save-dev + +bower install + +# Build CSS and JS assets to start things off. +grunt -v + # Prime the database with schema and data. echo "## Loading database schema and environment specific seed data." @@ -120,7 +136,7 @@ bin/vagrant-exec 'bin/cake Migrations migrate -v' # Always call the "production" seed file, because it's env-aware and will # load the correct seed file based on APP_ENV's value. -bin/vagrant-exec 'bin/cake BasicSeed.basic_seed -v' +bin/vagrant-exec 'bin/cake basic_seed -v' # Finish up. diff --git a/bower.json.template b/bower.json.template new file mode 100644 index 0000000..aaa4977 --- /dev/null +++ b/bower.json.template @@ -0,0 +1,7 @@ +{ + "name": "{{COMPOSER_PROJECT_NAME:loadsys/@TODO}}", + "dependencies" : { + "qunit" : "1.*", + "foundation-sites" : "6.1.2" + } +} diff --git a/composer.json b/composer.json index 5dbfe68..10c6551 100755 --- a/composer.json +++ b/composer.json @@ -6,12 +6,12 @@ "license": "MIT", "require": { "php": ">=5.6.0", - "cakephp/cakephp": "~3.1", + "cakephp/cakephp": "~3.2", "cakephp/plugin-installer": "*" }, "require-dev": { "composer/composer": "*", - "phpunit/phpunit": "4.8", + "phpunit/phpunit": "~4.8", "loadsys/loadsys_codesniffer": "~3.0" }, "autoload": { diff --git a/composer.json.template b/composer.json.template index d715352..f24b027 100644 --- a/composer.json.template +++ b/composer.json.template @@ -5,7 +5,7 @@ "license": "proprietary", "require": { "php": ">=5.6.0", - "cakephp/cakephp": "~3.1", + "cakephp/cakephp": "~3.2", "cakephp/migrations": "~1.0", "cakephp/plugin-installer": "*", "mobiledetect/mobiledetectlib": "2.*", @@ -14,7 +14,7 @@ "loadsys/cakephp-config-read": "~3.0", "loadsys/cakephp-creatormodifier": "~1.0", "loadsys/cakephp-libregistry": "~1.0", - "loadsys/cakephp-loadsys-theme": "dev-master", + "loadsys/cakephp-loadsys-theme": "~1.0", "loadsys/cakephp-shell-scripts": "~3.0", "loadsys/cakephp-auth-userentity": "~1.0" }, @@ -24,7 +24,14 @@ "phpunit/phpunit": "~4.8", "johnkary/phpunit-speedtrap": "~1.0@dev", "loadsys/loadsys_codesniffer": "~3.0", - "loadsys/cakephp-uuid-shell": "~1.0" + "phpmd/phpmd": "^2.3", + "loadsys/cakephp-uuid-shell": "~1.0", + "loadsys/cakephp-loadsys-theme": "^1.0", + "behat/behat": "~3.0", + "behat/mink": "^1.7", + "behat/mink-extension": "^2.2", + "behat/mink-goutte-driver": "^1.2", + "behat/mink-selenium2-driver": "^1.3" }, "autoload": { "psr-4": { diff --git a/config/app-local.sample.php b/config/app-local.sample.php index 7d49501..d9273d0 100755 --- a/config/app-local.sample.php +++ b/config/app-local.sample.php @@ -1,5 +1,9 @@ [ 'compress' => false, - 'duration' => 120, + 'duration' => '+1 day', 'servers' => '127.0.0.1', 'username' => null, 'password' => null, @@ -112,6 +115,16 @@ * a file, call this Configure var instead. */ 'Defaults' => [ + /** + * We don't have to **force** ssl in staging because it should already + * enabled via the load balancer. Since config/bootstrap.php defines + * an updated Request::is('ssl') detector that recognizes AWS load + * balancer scenarios, asset URLs will still be created with https:// + * even though Cake will think the request was over http:// from + * the ELB. + */ + 'ssl_force' => false, + 'Env' => [ 'Token' => 'staging', 'Hint' => [ diff --git a/config/app-travis.php b/config/app-travis.php index 6b38c98..0379855 100644 --- a/config/app-travis.php +++ b/config/app-travis.php @@ -1,5 +1,7 @@ [ + 'ssl_force' => false, 'Env' => [ 'Token' => 'travis', 'Hint' => [ diff --git a/config/app-vagrant.php b/config/app-vagrant.php index d0a717f..82f1469 100755 --- a/config/app-vagrant.php +++ b/config/app-vagrant.php @@ -116,6 +116,7 @@ * a file, call this Configure var instead. */ 'Defaults' => [ + 'ssl_force' => false, 'Env' => [ 'Token' => 'vagrant', 'Hint' => [ diff --git a/config/app.php.template b/config/app.php.template index 71763f2..40ee923 100755 --- a/config/app.php.template +++ b/config/app.php.template @@ -1,5 +1,7 @@ [ - 'default' => ConfigClosures::cacheMerge( + 'default' => ConfigClosures::cacheMerge([ // Returns the default array, merged with the array passed in. - ), + 'prefix' => 'default_', + ]), /** * Configure the cache used for general framework caching. Path information, @@ -239,17 +242,20 @@ return [ ], /** - * The test connection is used during the test suite. + * The test connection is used during the test suite. (Not used in + * production, but provides defaults for all other envs.) */ 'test' => [ 'className' => 'Cake\Database\Connection', 'driver' => 'Cake\Database\Driver\Mysql', 'persistent' => false, + /* 'host' => 'localhost', - //'port' => 'nonstandard_port_number', + 'port' => 3306, 'username' => 'my_app', 'password' => 'secret', 'database' => 'test_myapp', + */ 'encoding' => 'utf8', 'timezone' => 'UTC', 'cacheMetadata' => true, @@ -278,11 +284,25 @@ return [ 'file' => 'error', 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], ], + 'transactions' => [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'file' => 'transactions', + 'levels' => [], + 'scopes' => ['transactions'], + ], + 'security' => [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'file' => 'security', + 'levels' => [], + 'scopes' => ['security'], + ], 'performance' => [ 'className' => 'Cake\Log\Engine\FileLog', 'path' => LOGS, 'file' => 'performance', - //'levels' => [], + 'levels' => [], 'scopes' => ['performance'], ], ], @@ -313,6 +333,15 @@ return [ * a file, call this Configure var instead. */ 'Defaults' => [ + /** + * When enabled, all generated URLs will use https and any non-ssl + * requests will be redirected to their ssl counterpart. + * + * @see \App\Controller\AppController::ssl() + * @see config/bootstrap.php + */ + 'ssl_force' => true, + 'short_name' => '{{APP_DISPLAY_SHORT_NAME:@TODO Set app short name.}}', 'long_name' => '{{PROJECT_TITLE}}', 'domain' => '{{APP_DOMAIN_ONLY:acme.com}}', diff --git a/config/bootstrap.php b/config/bootstrap.php index 68ea1b2..24fb813 100755 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -51,6 +51,7 @@ use Cake\Mailer\Email; use Cake\Network\Request; use Cake\Routing\DispatcherFactory; +use Cake\Routing\Router; use Cake\Utility\Inflector; use Cake\Utility\Security; @@ -182,23 +183,25 @@ */ /** - * Plugins need to be loaded manually, you can either load them one by one or all of them in a single call - * Uncomment one of the lines below, as you need. make sure you read the documentation on Plugin to use more - * advanced ways of loading plugins + * Plugins need to be loaded manually, you can either load them one by one + * or all of them in a single call. Uncomment one of the lines below as + * needed. Make sure you read the documentation on Plugin to use more + * advanced ways of loading plugins. * * Plugin::loadAll(); // Loads all plugins at once * Plugin::load('Migrations'); //Loads a single plugin named Migrations - * */ - Plugin::load('BasicSeed', ['bootstrap' => false, 'routes' => false]); Plugin::load('CreatorModifier', ['bootstrap' => false, 'routes' => false]); Plugin::load('ConfigRead', ['bootstrap' => false, 'routes' => false]); Plugin::load('LoadsysTheme', ['bootstrap' => false, 'routes' => false]); Plugin::load('Migrations'); +Plugin::load('Uuid', ['bootstrap' => false, 'routes' => false]); -// Only try to load DebugKit in development mode -// Debug Kit should not be installed on a production system +/** + * Only try to load DebugKit in development mode + * Debug Kit should not be installed on a production system + */ if (Configure::read('debug')) { Plugin::load('DebugKit', ['bootstrap' => true]); } @@ -215,3 +218,35 @@ * This is needed for matching the auto-localized string output of Time() class when parsing dates. */ Type::build('datetime')->useLocaleParser(); + +/** + * Override the `ssl` detector to also work behind a load balancer. + */ +Request::addDetector('sslorig', ['env' => 'HTTPS', 'options' => [1, 'on']]); + +Request::addDetector('ssl', function ($request) { + return $request->is('sslorig') || $request->header('X_FORWARDED_PROTO') === 'https'; +}); + +/** + * If we're running in an SSL environment, make sure all generated links + * include SSL even if the request was decrypted before it reached Apache. + * + * This fixes issues with links to media and assets being written to the + * page using http:// URLs because the request into Apache was vanilla + * http:// even though the browser connected to the load balancer over + * https://, which prevents "mixed content" warnings in the browser. + */ +if (Request::createFromGlobals()->is('ssl') || Configure::read('Defaults.ssl_force')) { + $secureUrl = str_replace('http://', 'https://', Router::fullBaseUrl()); + Router::fullBaseUrl($secureUrl); +} + +/** + * Custom field type to automatically encode/decode data into json format + * + * In Coupon Table, we've defined a custom type field + * This field will store json formatted properties/values to identify data + * for a particular coupon type: basic, location, multi-site + */ +Type::map('json', 'App\Database\Type\JsonType'); diff --git a/config/routes.php b/config/routes.php index 99b2d7e..8644da7 100755 --- a/config/routes.php +++ b/config/routes.php @@ -5,17 +5,6 @@ * In this file, you set up routes to your controllers and their actions. * Routes are very important mechanism that allows you to freely connect * different URLs to chosen controllers and their actions (functions). - * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://cakephp.org CakePHP(tm) Project - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ use Cake\Core\Plugin; @@ -23,21 +12,6 @@ /** * The default class to use for all routes - * - * The following route classes are supplied with CakePHP and are appropriate - * to set as the default: - * - * - Route - * - InflectedRoute - * - DashedRoute - * - * If no call is made to `Router::defaultRouteClass`, the class used is - * `Route` (`Cake\Routing\Route\Route`) - * - * Note that `Route` does not do any inflections on URLs which will result in - * inconsistently cased URLs when used with `:plugin`, `:controller` and - * `:action` markers. - * */ Router::defaultRouteClass('DashedRoute'); diff --git a/config/seed.php b/config/seed.php index 871fa97..7acc922 100644 --- a/config/seed.php +++ b/config/seed.php @@ -5,7 +5,7 @@ namespace App\Config\BasicSeed; -// Define records. +// Define records to be imported into the database. $data = [ //@TODO: Add PRODUCTION seed data for each table as migrations are created. ]; diff --git a/package.json.template b/package.json.template new file mode 100644 index 0000000..8813227 --- /dev/null +++ b/package.json.template @@ -0,0 +1,27 @@ +{ + "name": "{{COMPOSER_PROJECT_NAME:loadsys/@TODO}}", + "version": "0.1.0", + "license": "UNLICENSED", + "private": true, + "repository": { + "type": "git", + "url": "{{PROJECT_REPO_URL:https://github.com/loadsys/app}}" + }, + "dependencies": {}, + "devDependencies": { + "autoprefixer": "^6.3.6", + "cssnano": "^3.5.2", + "graceful-fs": "^4.1.3", + "grunt": "*", + "grunt-bowercopy": "^1.2.4", + "grunt-contrib-concat": "~1.0.0", + "grunt-contrib-qunit": ">=1.0.1", + "grunt-contrib-sass": "^1.0.0", + "grunt-contrib-uglify": "~1.0.0", + "grunt-contrib-watch": "~1.0.0", + "grunt-postcss": "~0.8.0", + "grunt-sass-lint": "^0.1.1", + "lodash": "^4.6.1", + "phantomjs-prebuilt": "*" + } +} diff --git a/phpcs.xml.template b/phpcs.xml.template new file mode 100644 index 0000000..5d51458 --- /dev/null +++ b/phpcs.xml.template @@ -0,0 +1,7 @@ + + + {{PROJECT_TITLE}} coding standard. + + + + diff --git a/provision/010-cake.conf b/provision/010-cake.conf index 0445f08..4c815ec 100644 --- a/provision/010-cake.conf +++ b/provision/010-cake.conf @@ -11,6 +11,13 @@ Options -Indexes AllowOverride All - Require all granted + + + IncludeOptional "/var/www/provision/apache_auth_${APP_ENV}*.conf" + + + Require all granted + + diff --git a/provision/apache-vagrant.conf b/provision/apache-vagrant.conf new file mode 100644 index 0000000..d52b59b --- /dev/null +++ b/provision/apache-vagrant.conf @@ -0,0 +1,10 @@ +# Launch Apache after vagrant shared folder mount. +description "Start Apache after vagrant mounts shared folders." +author "Brian Porter " +version "1.0.0" + +# Start this script when vagrant finishes mounting shared folders. +start on vagrant-mounted + +# Start Apache. +exec service apache2 start diff --git a/provision/baremetal.sh b/provision/baremetal.sh index 91fdde7..f568ea2 100755 --- a/provision/baremetal.sh +++ b/provision/baremetal.sh @@ -98,6 +98,9 @@ sudo chown -R ${TARGET_USER}:www-data /var/www sudo chmod -R a+w /var/www/tmp /var/www/logs +# Initialize git LFS support in the repo: +cd /var/www +git lfs install # Finish up. diff --git a/provision/dot-files/.gemrc b/provision/dot-files/.gemrc new file mode 100644 index 0000000..62080f1 --- /dev/null +++ b/provision/dot-files/.gemrc @@ -0,0 +1,4 @@ +# Skip installation of all documentation +# Ref: https://gauravsohoni.wordpress.com/2014/02/19/install-gems-without-documentation-ri-rdoc/ +--- +gem: --no-ri --no-rdoc diff --git a/provision/mailcatcher.sh b/provision/mailcatcher.sh index 7e5c845..bf0a961 100755 --- a/provision/mailcatcher.sh +++ b/provision/mailcatcher.sh @@ -12,7 +12,9 @@ sudo apt-get update -y sudo apt-get install -y libsqlite3-dev ruby1.9.3 -sudo gem install mailcatcher --no-ri --no-rdoc +sudo gem install mime-types --version "< 3" + +sudo gem install mailcatcher --conservative --no-ri --no-rdoc sudo tee "/etc/init/mailcatcher.conf" <<-'EOINIT' > /dev/null description "Mailcatcher" diff --git a/provision/main.sh b/provision/main.sh index 46085ec..7d4cfb9 100755 --- a/provision/main.sh +++ b/provision/main.sh @@ -66,6 +66,10 @@ echo "UTC" | sudo tee /etc/timezone > /dev/null sudo dpkg-reconfigure --frontend noninteractive tzdata +sudo update-rc.d -f ntp remove + +sudo apt-get remove -y ntp + sudo apt-get update -y sudo apt-get upgrade -y @@ -85,13 +89,15 @@ sudo add-apt-repository -y ppa:ondrej/php5-5.6 sudo apt-add-repository -y ppa:git-core/ppa +curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash + # Install direct requirements. echo "## Installing LAMP stack components." sudo apt-get update -y -sudo apt-get install -y git-core apache2 php5 php5-curl php5-intl php5-mcrypt php5-memcached php5-mysql +sudo apt-get install -y git-core git-lfs apache2 php5 php5-curl php5-intl php5-mcrypt php5-memcached php5-mysql # Install composer. @@ -100,17 +106,6 @@ echo "## Installing composer." curl -sS https://raw.githubusercontent.com/loadsys/CakePHP-Shell-Scripts/master/composer > composer && sudo bash composer -# Install Node.js, Grunt and Ember. -# Ref: https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories -# echo "## Installing node.js, Grunt and Ember." -# -# curl -sL https://deb.nodesource.com/setup_0.12 | sudo bash - -# -# sudo apt-get install -y nodejs -# -# sudo npm install -g json grunt-cli ember-cli - - # Set up the machine's APP_ENV value. echo "## Setting app environment." @@ -159,20 +154,8 @@ sudo service apache2 restart # Set up DB backups on reboot/shutdown. -SHUTDOWN_DB_BACKUP_SCRIPT="/etc/init.d/k99_shutdown_db_backup" -sudo tee "${SHUTDOWN_DB_BACKUP_SCRIPT}" <<-EOSHL > /dev/null - #!/usr/bin/env bash - . /etc/app_env - cd "/var/www" - bin/db-backup - -EOSHL - -sudo chmod a+x "${SHUTDOWN_DB_BACKUP_SCRIPT}" - -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc0.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` - -sudo ln -s "${SHUTDOWN_DB_BACKUP_SCRIPT}" /etc/rc6.d/`basename "${SHUTDOWN_DB_BACKUP_SCRIPT}"` +echo "## Installing a mysqld shutdown script to dump a backup first." +sudo cp -v "${PROVISION_DIR}/mysql-shutdown-backup.conf" /etc/init/ # Call the environment-specific provisioning script, if it exists. diff --git a/provision/mysql-shutdown-backup.conf b/provision/mysql-shutdown-backup.conf new file mode 100644 index 0000000..052fc46 --- /dev/null +++ b/provision/mysql-shutdown-backup.conf @@ -0,0 +1,23 @@ +# MySQL Shutdown-time Backup +description "MySQL Shutdown-time Backup" +author "Brian Porter " +version "1.0.0" + +# Start this script when mysql is about to stop. +start on stopping mysql + +# Block the stopping of mysql until this script finishes. +task + +# Give the script time to write the backup file and compress it. +kill timeout 300 + +# Define the script to execute. +script + # priority can be overriden and "-s" adds output to stderr + ERR_LOGGER="logger -p daemon.err -t /etc/init/$(basename $0).conf -i" + + cd /var/www + . /etc/app_env + $ERR_LOGGER "APP_ENV=$APP_ENV, $(bin/db-backup | tr '\r\n' ' ')" +end script diff --git a/provision/production.sh b/provision/production.sh index 674a1d4..dc34467 100755 --- a/provision/production.sh +++ b/provision/production.sh @@ -25,6 +25,8 @@ SMTP_RELAY_HOST_AND_PORT="@TODO:ses.hostname.here:587" SMTP_RELAY_USERNAME="@TODO:ses.username-here" SMTP_RELAY_PASSWORD="@TODO:ses.password-here" + + echo "## Starting: `basename "$0"`." diff --git a/provision/vagrant.sh b/provision/vagrant.sh index 7daa177..788b111 100755 --- a/provision/vagrant.sh +++ b/provision/vagrant.sh @@ -17,11 +17,24 @@ # Set up working vars. # PROVISION_DIR must be inherited from main.sh # APP_ENV must be inherited from main.sh +# TARGET_USER must be inherited from main.sh + +LOGIN_PASS='vagrant' echo "## Starting: `basename "$0"`." +# Set a user account password for the vagrant user to allow Sequel Pro to connect easily. +echo "## Setting vagrant user's password for easy MySQL access." + +echo "${TARGET_USER}:${LOGIN_PASS}" | sudo chpasswd + + +# Add the user to the admin group to make reading logs easier. +sudo usermod -a -G adm $TARGET_USER + + # Farm out local MySQL server install to the common "mysql_server" script. "${PROVISION_DIR}/mysql_server.sh" @@ -36,13 +49,28 @@ sudo php5enmod memcached sqlite3 pdo_sqlite xdebug sudo service apache2 reload +# Make sure Apache starts (again) after vagrant shared folders are mounted. +echo "## Ensuring Apache starts after vagrant shared folders are mounted." + +sudo cp -v "${PROVISION_DIR}/apache-vagrant.conf" /etc/init/ + + # Install Mailcatcher. "${PROVISION_DIR}/mailcatcher.sh" -# Set a user account password for the vagrant user to allow Sequel Pro to connect easily. -echo "## Setting vagrant user's password for easy MySQL access." -echo "vagrant:vagrant" | sudo chpasswd +# Install Node.js, Grunt. (Compiled "production-ready" files are currently +# committed to the repo, so these tools are only required in development.) +# Ref: https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories +echo "## Installing node.js and Grunt." + +curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - + +sudo apt-get install -y libfontconfig nodejs + +sudo npm install -g json grunt-cli bower + +sudo gem install sass # Finish up. diff --git a/skel/test-project.sh b/skel/test-project.sh index 444d24e..e091095 100755 --- a/skel/test-project.sh +++ b/skel/test-project.sh @@ -68,7 +68,11 @@ cat < packages.json EOD # Execute the proper command. -composer create-project -v --ignore-platform-reqs --repository-url=./packages.json ${PACKAGE_NAME} "${DEST_DIR}" dev-${BRANCH_NAME} +composer create-project -v --ignore-platform-reqs \ + --repository-url=./packages.json \ + ${PACKAGE_NAME} \ + "${DEST_DIR}" \ + dev-${BRANCH_NAME} # Clean up after ourselves. rm -f packages.json diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 7354b73..0335ee5 100755 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -14,9 +14,16 @@ */ namespace App\Controller; +use App\Lib\Log\LogTrait; use Cake\Controller\Controller; +use Cake\Controller\Exception\SecurityException; +use Cake\Core\Configure; +use Cake\Event\Event; +use Cake\Network\Exception\BadRequestException; +use Cake\Routing\Router; use LibRegistry\LibRegistryTrait; + /** * Application Controller * @@ -29,6 +36,9 @@ class AppController extends Controller { // Allow access to shared/injected libraries, works like TableRegistry. use LibRegistryTrait; + // Enable logging as a class method + use LogTrait; + /** * Initialization hook method. * @@ -38,6 +48,141 @@ class AppController extends Controller { */ public function initialize() { parent::initialize(); + + $this->loadComponent('RequestHandler'); $this->loadComponent('Flash'); + $this->loadComponent('Auth', [ + 'className' => 'AuthUserEntity.UserEntityAuth', + 'entityClass' => '\Cake\ORM\Entity', + 'entityOptions' => [ + 'associated' => [], + ], + ]); + $this->loadComponent('Security', [ + 'blackHoleCallback' => 'blackHole', + ]); + $this->loadComponent('Csrf'); + } + + /** + * Allows controllers to change auth access without having to override + * the entire beforeFilter callback. + * + * @return void + */ + protected function auth() { + } + + /** + * If SSL is enforced, use the Security Component to require SSL for all + * actions. + * + * @return void + */ + protected function ssl() { + if (Configure::read('Defaults.ssl_force')) { + $this->Security->requireSecure(); + } + } + + /** + * Process a Security Component BlackHoled request. + * + * @param string $type Error type, this is either `secure` or `auth`. + * @param \Cake\Controller\Exception\SecurityException $exception Additional debug info describing the cause. + * @return void + * @throws \Cake\Controller\Exception\SecurityException in the advent of debug being true. + * @link http://book.cakephp.org/3.0/en/controllers/components/security.html#handling-blackhole-callbacks + */ + public function blackHole($type, SecurityException $exception) { + $this->log( + sprintf( + 'Security Component black-holed this request: Request URL: %s Exception Type: %s Exception Message: %s Exception Reason: %s', + $this->request->here(), + $exception->getType(), + $exception->getMessage(), + $exception->getReason() + ), + 'error', + ['scope' => ['security']] + ); + + if (Configure::read('debug')) { + throw $exception; + } + + switch ($exception->getType()) { + case 'secure': + $this->forceSsl(); + break; + case 'auth': + default: + $this->authError(); + break; + } + } + + /** + * Handle the case of an `auth` type error for the Security Component. Which + * indicates a form validation error, or a controller/action mismatch error. + * + * @return void + * @throws \Cake\Network\Exception\BadRequestException Throws an BadRequestException + * on this method being called. + */ + protected function authError() { + throw new BadRequestException('There was an error with your request. Please, try again.'); + } + + /** + * Handles the case of the Security Component, having a secure type of blackhole. + * Indicates the current rquest should have been using SSL and was not. + * Redirect the current request to one using SSL. + * + * @return \Cake\Network\Response Redirects the current request to an SSL request. + */ + protected function forceSsl() { + $secureUrl = Router::url($this->request->here(), true); + $secureUrl = str_replace('http://', 'https://', $secureUrl); + return $this->redirect($secureUrl, (Configure::read('debug') ? 302 : 301)); + } + + /** + * validate that a User's role matches the prefixed route + * + * @param array $user The array of User properties + * @return bool Returns true if the User's role matches the prefix + */ + public function isAuthorized($user = null) { + // Default deny + return false; + } + + /** + * Called before the controller action. You can use this method to configure and + * customize components or perform logic that needs to happen before each + * controller action. + * + * @param Event $event An Event instance + * @return void + * @link http://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks + */ + public function beforeFilter(Event $event) { + $this->ssl(); + $this->auth(); + } + + /** + * Called after the controller action is run, but before the view is rendered. + * You can use this method to perform logic or set view variables that are + * required on every request. + * + * @param \Cake\Event\Event $event An Event instance + * @return void + * @link http://book.cakephp.org/3.0/en/controllers.html#request-life-cycle-callbacks + */ + public function beforeRender(Event $event) { + $u = $this->Auth->userEntity(); // Will be null when not logged in. + $this->set(compact('u')); } } diff --git a/src/Controller/PagesController.php b/src/Controller/PagesController.php index 1db7fec..22b6f0a 100755 --- a/src/Controller/PagesController.php +++ b/src/Controller/PagesController.php @@ -26,6 +26,16 @@ * @link http://book.cakephp.org/3.0/en/controllers/pages-controller.html */ class PagesController extends AppController { + /** + * Set whether authentication is required to access actions in this controller. + * + * @return void + */ + protected function auth() { + // Pages are public by default. + $this->Auth->allow(); + } + /** * Displays a view * diff --git a/src/Database/Type/JsonType.php b/src/Database/Type/JsonType.php new file mode 100644 index 0000000..a61ed7a --- /dev/null +++ b/src/Database/Type/JsonType.php @@ -0,0 +1,79 @@ +columnType('packed', 'json'); + * return $table; + * } + */ +namespace App\Database\Type; + +use Cake\Database\Driver; +use Cake\Database\Type; +use PDO; + +/** + * \App\Database\Type\JsonType + */ +class JsonType extends Type { + /** + * Decodes Json data if present + * + * @param mixed $value data to be decoded + * @param Driver $driver PDO driver trait + * @return mixed|null Assume the value is a json-encoded string and unpack it. + */ + public function toPHP($value, Driver $driver) { + if ($value === null) { + return null; + } + + return json_decode($value, true); + } + + /** + * Assembles data for use in Entity + * + * @param mixed $value data to be decoded + * @return mixed If the value is already a PHP array (or null) return it, + * otherwise assume its a json-encoded string and unpack it. + */ + public function marshal($value) { + if (is_array($value) || $value === null) { + return $value; + } + + return json_decode($value, true); + } + + /** + * Json encodes data to store as a scalar in the database + * + * @param mixed $value data to be encoded + * @param Driver $driver PDO driver trait + * @return string The json-encoded version of the provided PHP array. + */ + public function toDatabase($value, Driver $driver) { + return json_encode($value); + } + + /** + * Prepares data for SQL handling + * + * @param string $value encoded data for storing as a string + * @param Driver $driver PDO driver trait + * @return int A \PDO constant indicating the parameter's type. + */ + public function toStatement($value, Driver $driver) { + if ($value === null) { + return PDO::PARAM_NULL; + } + + return PDO::PARAM_STR; + } +} diff --git a/src/Lib/Log/LogTrait.php b/src/Lib/Log/LogTrait.php new file mode 100644 index 0000000..e2da8a0 --- /dev/null +++ b/src/Lib/Log/LogTrait.php @@ -0,0 +1,50 @@ + 10, 'format' => 'log']) . "\n"; + $this->log($msg, $level, $context); + } +} diff --git a/src/Model/Table/Table.php b/src/Model/Table/Table.php index c7502bb..4356130 100644 --- a/src/Model/Table/Table.php +++ b/src/Model/Table/Table.php @@ -84,8 +84,22 @@ public function validationDefault(Validator $validator) { * @return \Cake\ORM\RulesChecker */ public function buildRules(RulesChecker $rules) { - $rules->add($rules->existsIn(['creator_id'], 'Creators')); - $rules->add($rules->existsIn(['modifier_id'], 'Modifiers')); + $rules->add( + $rules->existsIn(['creator_id'], 'Creators'), + 'creator_id_exists_in', + [ + 'message' => 'Please select a valid Creator.', + 'errorField' => 'creator_id', + ] + ); + $rules->add( + $rules->existsIn(['modifier_id'], 'Modifiers'), + 'modifier_id_exists_in', + [ + 'message' => 'Please select a valid Modifier.', + 'errorField' => 'modifier_id', + ] + ); return $rules; } diff --git a/src/Template/Element/Flash/default.ctp b/src/Template/Element/Flash/default.ctp index 158deab..b12f347 100755 --- a/src/Template/Element/Flash/default.ctp +++ b/src/Template/Element/Flash/default.ctp @@ -1,5 +1,5 @@ -
+
×
diff --git a/src/Template/Element/Flash/warning.ctp b/src/Template/Element/Flash/warning.ctp index d49c1d9..c72de71 100644 --- a/src/Template/Element/Flash/warning.ctp +++ b/src/Template/Element/Flash/warning.ctp @@ -1,11 +1,11 @@ -
+
×
diff --git a/src/Template/Element/Layout/breadcrumbs.ctp b/src/Template/Element/Layout/breadcrumbs.ctp index 3d12702..b881b99 100644 --- a/src/Template/Element/Layout/breadcrumbs.ctp +++ b/src/Template/Element/Layout/breadcrumbs.ctp @@ -20,13 +20,12 @@ foreach($breadcrumbs as $breadcrumbTitle => $breadcrumbUrl) { ?>
-Html->getCrumbList( - [ - 'firstClass' => false, - 'lastClass' => 'current', - 'class' => 'breadcrumbs', - 'role' => 'menubar' - ] -); -?> +
+ Html->getCrumbList([ + 'firstClass' => false, + 'lastClass' => 'current', + 'class' => 'breadcrumbs', + 'role' => 'menubar' + ]) ?> +
diff --git a/src/Template/Layout/default.ctp b/src/Template/Layout/default.ctp index 69ed825..4af42e3 100755 --- a/src/Template/Layout/default.ctp +++ b/src/Template/Layout/default.ctp @@ -31,9 +31,7 @@ use Cake\Core\Configure; Html->meta('icon') ?> Html->css([ - 'normalize', - 'foundation.min', - 'app', + 'build/app', ]) ?> Html->script([ @@ -43,7 +41,6 @@ use Cake\Core\Configure; fetch('social_meta') ?> fetch('meta') ?> fetch('css') ?> - fetch('script') ?> @@ -70,9 +67,9 @@ use Cake\Core\Configure;
Html->script([ - 'vendor/jquery', - 'foundation.min', - 'app', + 'build/scripts.min', ]) ?> + + fetch('script') ?> diff --git a/src/View/AppView.php b/src/View/AppView.php index 9d1d7ed..a10d2a5 100755 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -26,6 +26,13 @@ public function initialize() { 'templates' => [ 'error' => '{{content}}', 'radioContainer' => '
{{content}}
', + 'dateWidget' => ' +
+
{{month}}
+
{{day}}
+
{{year}}
+
+ ', ], ]); $this->loadHelper('Flash'); diff --git a/src/View/Helper/ListsHelper.php b/src/View/Helper/ListsHelper.php new file mode 100644 index 0000000..a42b6da --- /dev/null +++ b/src/View/Helper/ListsHelper.php @@ -0,0 +1,58 @@ + Display] pairs is to only pass the + * first argument: `$this->Lists->get('Students.school_grade')`. + * + * When called without any arguments, the method acts as a View-level + * shortcut to `\Cake\Core\Configure::read('Lists')` and returns the + * entire sub-array of available lists. + * + * @param string $path Configure path to the array to search **excluding** + * the leading `Lists.` + * @param string $key The final component in a path, typically stored as + * a slug in a DB field. + * @param null|string $default The value to use if the path or key can + * not be found. The default $default is to pass the $key value + * through, if present, otherwise the empty string is used. + * @return string The found Configure value, or $default if path was not + * found/empty. + */ + public function get($path = '', $key = null, $default = null) { + $path = rtrim("Lists.{$path}.{$key}", '.'); + $default = (!is_null($default) ? $default : $key); + $val = Configure::read($path); + return (!is_null($val) ? $val : $default); + } +} diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php index 48d4187..22d5108 100644 --- a/tests/TestCase/Controller/AppControllerTest.php +++ b/tests/TestCase/Controller/AppControllerTest.php @@ -7,6 +7,23 @@ use App\Controller\AppController; use Cake\TestSuite\IntegrationTestCase; +/** + * \App\Test\TestCase\Controller\TestAppController + */ +class TestAppController extends AppController { + public function ssl() { + return parent::ssl(); + } + + public function authError() { + return parent::authError(); + } + + public function forceSsl() { + return parent::forceSsl(); + } +} + /** * \App\Test\TestCase\Controller\AppControllerTest */ @@ -18,6 +35,17 @@ class AppControllerTest extends IntegrationTestCase { */ public $fixtures = []; + /** + * tearDown method + * + * @return void + */ + public function tearDown() { + Configure::write('Defaults.ssl_force', false); + + parent::tearDown(); + } + /** * Test initialize() method. * @@ -30,24 +58,58 @@ public function testInitialize() { // to inspect the controller instance. $this->get('/'); $componentRegistry = $this->_controller->components(); + $this->assertTrue( + $componentRegistry->has('RequestHandler'), + 'AppController must load the RequestHandler component.' + ); $this->assertTrue( $componentRegistry->has('Flash'), 'AppController must load the Flash component.' ); - //$this->assertTrue( - // $componentRegistry->has('Auth'), - // 'AppController must load the Auth component.' - //); + $this->assertTrue( + $componentRegistry->has('Auth'), + 'AppController must load the Auth component.' + ); + $this->assertTrue( + $componentRegistry->has('Security'), + 'AppController must load the Security component.' + ); + $this->assertTrue( + $componentRegistry->has('Csrf'), + 'AppController must load the Csrf component.' + ); } /** - * Test isAuthorized() method. + * Test isAuthorized method * + * @param bool $expected Whether the isAuthorized() method is expected to pass or fail. + * @param string $msg Optional PHPUnit assertion failure message. * @return void * @covers \App\Controller\AppController::isAuthorized + * @dataProvider provideIsAuthorizedArgs */ - public function testIsAuthorized() { - $this->markTestSkipped('No method to test.'); + public function testIsAuthorized($expected, $msg = '') { + $request = $this->getMock('Cake\Network\Request'); + $request->params['prefix'] = $prefix; + $controller = new AppController($request); + + $this->assertEquals( + $expected, + $controller->isAuthorized([]), + $msg + ); + } + + /** + * Provide arguments for the isAuthorized method + * + * @return array + */ + public function provideIsAuthorizedArgs() { + return [ + [false, 'All authorization is denied by default.'], + ]; } /** @@ -58,7 +120,178 @@ public function testIsAuthorized() { * @covers \App\Controller\AppController::auth */ public function testBeforeFilter() { - $this->markTestSkipped('No method to test.'); + $request = $this->getMock('Cake\Network\Request'); + $request->params['prefix'] = 'admin'; + + // Set up auth data, AuthComponent and Controller. + $user = [ + 'id' => 1, + 'role' => 'admin', + 'email' => 'admin@localhost', + ]; + $userEntity = new User($user); + $this->session($user); + + $authComponent = $this->getMock('AuthComponent', ['userEntity']); + $authComponent->expects($this->once()) + ->method('userEntity') + ->with() + ->willReturn($userEntity); + + $controller = $this->getMock( + 'App\Controller\AppController', + ['viewBuilder', 'layout'], + [$request] + ); + $controller->expects($this->once()) + ->method('viewBuilder') + ->will($this->returnSelf()); + $controller->expects($this->once()) + ->method('layout') + ->with('admin'); + $controller->Auth = $authComponent; + + $controller->beforeFilter(new Event([])); + + // Assert view vars are present. + $this->assertEquals( + $userEntity, + $controller->viewVars['u'], + 'The `u` global view var should be set.' + ); + $this->assertEquals( + $user['role'], + $controller->viewVars['uRole'], + 'The `uRole` global view var should be set.' + ); + } + + /** + * Test blackHole method with the various types of errors that can be passed, + * when debug is equal to true. + * + * @param string $errorType The string for the type of error to handle. + * @return void + * @covers \App\Controller\AppController::blackHole + * @dataProvider providerBlackHole + */ + public function testBlackHoleDebugTrue($errorType, $exception) { + Configure::write('debug', true); + $exceptionMessage = 'Sample Message'; + $exceptionClass = new $exception($exceptionMessage); + + $requestUrl = 'http://localhost.com/pages/view/12345'; + $request = $this->getMock( + 'Cake\Network\Request', + ['here'], + [$requestUrl] + ); + $request->expects($this->once()) + ->method('here') + ->with() + ->will($this->returnValue($requestUrl)); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['log', 'authError', 'forceSsl'], + [$request] + ); + $controller->expects($this->once()) + ->method('log') + ->with( + "Security Component black-holed this request: Request URL: {$requestUrl} Exception Type: {$errorType} Exception Message: {$exceptionMessage} Exception Reason: ", + 'error', + ['scope' => ['security']] + ) + ->will($this->returnValue(true)); + + $controller->expects($this->never()) + ->method('authError'); + $controller->expects($this->never()) + ->method('forceSsl'); + + $this->setExpectedException( + $exception, + $exceptionMessage + ); + + $controller->blackHole($errorType, $exceptionClass); + } + + /** + * Test blackHole method with the various types of errors that can be passed, + * when debug is equal to false. + * + * @param string $errorType The string for the type of error to handle. + * @return void + * @covers \App\Controller\AppController::blackHole + * @dataProvider providerBlackHole + */ + public function testBlackHoleDebugFalse($errorType, $exception) { + Configure::write('debug', false); + $exceptionMessage = 'Sample Message'; + $exceptionClass = new $exception($exceptionMessage); + + $requestUrl = 'http://localhost.com/pages/view/12345'; + $request = $this->getMock( + 'Cake\Network\Request', + ['here'], + [$requestUrl] + ); + $request->expects($this->once()) + ->method('here') + ->with() + ->will($this->returnValue($requestUrl)); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['log', 'authError', 'forceSsl'], + [$request] + ); + $controller->expects($this->once()) + ->method('log') + ->with( + "Security Component black-holed this request: Request URL: {$requestUrl} Exception Type: {$errorType} Exception Message: {$exceptionMessage} Exception Reason: ", + 'error', + ['scope' => ['security']] + ) + ->will($this->returnValue(true)); + + if ($errorType === 'secure') { + $controller->expects($this->never()) + ->method('authError'); + $controller->expects($this->once()) + ->method('forceSsl') + ->with() + ->will($this->returnValue(true)); + } else { + $controller->expects($this->once()) + ->method('authError') + ->with() + ->will($this->returnValue(true)); + $controller->expects($this->never()) + ->method('forceSsl'); + } + + $controller->blackHole($errorType, $exceptionClass); + } + + /** + * Test blackHole method with secure passed. + * + * @return array Data inputs for testBlackHole + */ + public function providerBlackHole() { + return [ + [ + 'auth', + '\Cake\Controller\Exception\AuthSecurityException', + ], + [ + 'secure', + '\Cake\Controller\Exception\SecurityException', + ], + ]; } /** @@ -70,4 +303,111 @@ public function testBeforeFilter() { public function testBeforeRender() { $this->markTestSkipped('No method to test.'); } + + /** + * Test ssl enforcement, when the Defaults.ssl_force is set to true. + * + * @return void + * @covers \App\Controller\AppController::ssl + */ + public function testSslEnforcementTrue() { + Configure::write('Defaults.ssl_force', true); + $request = $this->getMock('Cake\Network\Request'); + + $securityComponent = $this->getMock( + 'SecurityComponent', + ['requireSecure'] + ); + $securityComponent->expects($this->once()) + ->method('requireSecure') + ->will($this->returnValue(true)); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['redirect'], + [$request] + ); + $controller->Security = $securityComponent; + + $controller->ssl(); + } + + /** + * Test ssl enforcement, when the Defaults.ssl_force is set to false. + * + * @return void + * @covers \App\Controller\AppController::ssl + */ + public function testSslEnforcementFalse() { + Configure::write('Defaults.ssl_force', false); + $request = $this->getMock('Cake\Network\Request'); + + $securityComponent = $this->getMock( + 'SecurityComponent', + ['requireSecure'] + ); + $securityComponent->expects($this->never()) + ->method('requireSecure') + ->will($this->returnValue(true)); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['redirect'], + [$request] + ); + $controller->Security = $securityComponent; + + $controller->ssl(); + } + + /** + * Test ssl redirects + * + * @return void + * @covers \App\Controller\AppController::forceSsl + */ + public function testForceSslEnforcement() { + $request = $this->getMock( + 'Cake\Network\Request', + [], + ['http://localhost.com'] + ); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['redirect'], + [$request] + ); + $controller->expects($this->once()) + ->method('redirect') + ->will($this->returnValue(true)); + + $controller->forceSsl(); + } + + /** + * Test authError should just throw an exception. + * + * @return void + * @covers \App\Controller\AppController::authError + */ + public function testAuthError() { + $request = $this->getMock( + 'Cake\Network\Request', + [], + ['http://localhost.com'] + ); + + $controller = $this->getMock( + '\App\Test\TestCase\Controller\TestAppController', + ['redirect'], + [$request] + ); + + $this->setExpectedException( + '\Cake\Network\Exception\BadRequestException', + 'There was an error with your request. Please, try again.' + ); + $controller->authError(); + } } diff --git a/tests/TestCase/Database/Type/JsonTypeTest.php b/tests/TestCase/Database/Type/JsonTypeTest.php new file mode 100644 index 0000000..c4905c3 --- /dev/null +++ b/tests/TestCase/Database/Type/JsonTypeTest.php @@ -0,0 +1,182 @@ + '870b3210-e38f-4222-881c-46c0baaf2b0a', + ]; + + /** + * A json-encoded version of ::$dataArray. Assigned during ::setUp(). + * + * @var string + */ + protected $dataJson = ''; + + /** + * setUp method. + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $this->Type = new JsonType(); + $this->driver = $this->getMock('Cake\Database\Driver'); + $this->dataJson = json_encode($this->dataArray); + } + + /** + * tearDown method. + * + * @return void + */ + public function tearDown() { + unset($this->Type); + unset($this->driver); + unset($this->dataArray); + unset($this->dataJson); + + parent::tearDown(); + } + + /** + * Test toPHP() with null values. + * + * @return void + */ + public function testToPHPWithEmpty() { + $this->assertNull( + $this->Type->toPHP(null, $this->driver), + 'Converting a null value should produce null output.' + ); + $this->assertNull( + $this->Type->toPHP('', $this->driver), + 'Converting an empty string should produce null output.' + ); + } + + /** + * Test toPHP() with json values. + * + * @return void + */ + public function testToPHPWithJson() { + $this->assertEquals( + $this->dataArray, + $this->Type->toPHP($this->dataJson, $this->driver), + 'Returned value should be a json_encode()d string.' + ); + } + + /** + * Test converting to database format. + * + * @return void + */ + public function testToDatabase() { + $this->assertEquals( + $this->dataJson, + $this->Type->toDatabase($this->dataArray, $this->driver), + 'The packed field value should be stored as json encoded string' + ); + } + + /** + * Test marshalling data. + * + * @param mixed $input The value to provide to marshal(). + * @param mixed $expected The expected output value. + * @param string $msg Optional PHPUnit assertion failure message. + * @return void + * @dataProvider provideMarshalArgs + */ + public function testMarshal($input, $expected, $msg = '') { + $this->assertSame( + $expected, + $this->Type->marshal($input), + $msg + ); + } + + /** + * Data provider for marshal(). + * + * @return array + */ + public function provideMarshalArgs() { + return [ + [ + '{"foo":"bar"}', // input + ['foo' => 'bar'], // expected + 'A json string should be marshalled to an array.', // msg + ], + [ + ['foo' => 'bar'], + ['foo' => 'bar'], + 'An array should be marshalled to an array.', + ], + [ + null, + null, + 'A null value should be marshalled to null.', + ], + ]; + } + + /** + * Simple sanity check of toStatement(). + * + * @param mixed $input The value to provide to toStatement(). + * @param mixed $expected The expected output value. + * @param string $msg Optional PHPUnit assertion failure message. + * @return void + * @dataProvider provideToStatementArgs + */ + public function testToStatement($value, $expected, $msg = '') { + $this->assertEquals( + $expected, + $this->Type->toStatement($value, $this->driver), + $msg + ); + } + + /** + * Provide input/output args to testToStatement(). + * + * @return array Sets of [input, output, msg]. + */ + public function provideToStatementArgs() { + return [ + [ + null, // input + PDO::PARAM_NULL, // expected + 'Null input should produce PDO `NULL` param type.', // msg + ], + [ + 'just a string', + PDO::PARAM_STR, + 'Any non-null input should produce PDO `STR` param type.', + ], + ]; + } +} diff --git a/tests/TestCase/Lib/ConfigClosuresTest.php b/tests/TestCase/Lib/ConfigClosuresTest.php index c501e59..965525d 100644 --- a/tests/TestCase/Lib/ConfigClosuresTest.php +++ b/tests/TestCase/Lib/ConfigClosuresTest.php @@ -6,6 +6,7 @@ use App\Lib\ConfigClosures; use Cake\Core\Configure; +use Cake\ORM\Entity as User; use Cake\TestSuite\TestCase; /** @@ -40,9 +41,9 @@ public function tearDown() { */ public function testCacheMerge(array $overrides, array $expected) { $output = ConfigClosures::cacheMerge($overrides); - $this->assertEquals( - $output, + $this->assertArraySubset( $expected, + $output, 'The output does not match what we expected to have generated.' ); } @@ -56,9 +57,6 @@ public function providerCacheMerge() { 'compress' => true, 'duration' => '+1 years', 'prefix' => '@TODO_', - 'servers' => '@TODO: Default (prod) Memcached server address', - 'username' => '@TODO: Default (prod) Memcached server username', - 'password' => '@TODO: Default (prod) Memcached server password', ], ], 'className Override' => [ @@ -70,9 +68,6 @@ public function providerCacheMerge() { 'compress' => true, 'duration' => '+1 years', 'prefix' => '@TODO_', - 'servers' => '@TODO: Default (prod) Memcached server address', - 'username' => '@TODO: Default (prod) Memcached server username', - 'password' => '@TODO: Default (prod) Memcached server password', ], ], @@ -86,9 +81,6 @@ public function providerCacheMerge() { 'compress' => true, 'duration' => '+1 years', 'prefix' => '@TODO_something_', - 'servers' => '@TODO: Default (prod) Memcached server address', - 'username' => '@TODO: Default (prod) Memcached server username', - 'password' => '@TODO: Default (prod) Memcached server password', ], ], ]; @@ -131,15 +123,14 @@ public function testCreditCardYear() { * @dataProvider providerUserEntity */ public function testUserEntity($email, $firstname, $lastname) { - if (!class_exists('App\Model\Entity\User')) { + if (!class_exists('User')) { $this->markTestSkipped('No User entity available to test with.'); } - $entity = new \App\Model\Entity\User(); + $entity = new User(); $entity->email = $email; $entity->firstname = $firstname; $entity->lastname = $lastname; - $entity->ignore_invalid_email = true; $output = ConfigClosures::userEntity($email, $firstname, $lastname); diff --git a/tests/TestCase/Lib/Log/LogTraitTest.php b/tests/TestCase/Lib/Log/LogTraitTest.php new file mode 100644 index 0000000..3293e45 --- /dev/null +++ b/tests/TestCase/Lib/Log/LogTraitTest.php @@ -0,0 +1,101 @@ +TestLogTrait = new TestLogTrait(); + + $config = TableRegistry::exists('Users') ? [] : ['className' => '\App\Model\Table\UsersTable']; + $this->Users = TableRegistry::get('Users', $config); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->TestLogTrait); + unset($this->Users); + Log::drop('trait_test'); + + parent::tearDown(); + } + /** + * Test log method. + * + * @return void + */ + public function testLog() { + $mock = $this->getMock('Psr\Log\LoggerInterface'); + $mock->expects($this->at(0)) + ->method('log') + ->with('error', 'Testing'); + $mock->expects($this->at(1)) + ->method('log') + ->with('debug', [1, 2]); + Log::config('trait_test', ['engine' => $mock]); + $subject = $this->getObjectForTrait('App\Lib\Log\LogTrait'); + $subject->log('Testing'); + $subject->log([1, 2], 'debug'); + } + + /** + * test the ::logPerformance() method + * + * @return void + */ + public function testLogPerformance() { + $message = 'foo'; + $Trait = $this->getMock( + '\App\Test\TestCase\Lib\Log\TestLogTrait', + ['log'] + ); + $Trait->expects($this->once()) + ->method('log') + ->with($this->anything(), 'info', ['scope' => ['performance']]) + ->will($this->returnValue(null)); + + $this->assertNull($Trait->logPerformance($message)); + } +} diff --git a/tests/TestCase/Migrations/MigrationsTest.php b/tests/TestCase/Migrations/MigrationsTest.php index 5bc4d6e..d28aa72 100644 --- a/tests/TestCase/Migrations/MigrationsTest.php +++ b/tests/TestCase/Migrations/MigrationsTest.php @@ -13,7 +13,8 @@ use Cake\Utility\Inflector; use Phinx\Db\Table; use Phinx\Migration\AbstractMigration; -use \FilesystemIterator; +use \AppendIterator; +use \FilesystemIterator as FSI; use \GlobIterator; /** @@ -25,11 +26,18 @@ class MigrationFolderIterator extends GlobIterator { public function current() { $fileInfo = parent::current(); + $realpath = $fileInfo->getRealPath(); $filename = $fileInfo->getBasename(); list($version, $underscoredName) = explode('_', $filename, 2); $classname = Inflector::camelize(basename($underscoredName, '.php')); - //@DEBUG: return compact('filename', 'classname', 'version'); - return [$fileInfo->getRealPath(), $classname, $version]; + + // Determine the plugin name the Migration file originates from. + $segments = explode(DIRECTORY_SEPARATOR, $realpath); + $isPlugin = array_search('plugins', $segments); + $plugin = ($isPlugin ? $segments[$isPlugin + 1] : null); + + //@DEBUG: return compact('filename', 'realpath', 'classname', 'version', 'plugin'); + return [$realpath, $classname, $version, $plugin]; } } @@ -86,17 +94,20 @@ public function tearDown() { * * @param string $filename The full filesystem path to the Migration file to load. * @param string $classname The inferred (inflected) name of the Migration class. - * @param string $version The inferred version number of the Migration, taken from the timestamp in the filename. + * @param string $version The inferred version number of the Migration, taken + * from the timestamp in the filename. + * @param string $plugin The inferred plugin name from which the Migration + * file originates, if any. * @return void * @dataProvider provideMigrationFiles */ - public function testMigrationFile($filename, $classname, $version) { - include $filename; + public function testMigrationFile($filename, $classname, $version, $plugin = null) { + require_once $filename; $this->mockMigration($classname, $version); // Trigger the up() and/or change() methods. (The mocks provide the assertions.) $this->migration->up(); - if (is_callable([$this->migration, "change"])) { + if (is_callable([$this->migration, 'change'])) { $this->migration->change(); } } @@ -107,11 +118,31 @@ public function testMigrationFile($filename, $classname, $version) { * @return void */ public function provideMigrationFiles() { - $globPath = dirname(dirname(dirname(dirname(__FILE__)))) . '/config/Migrations/*.php'; - $it = new MigrationFolderIterator($globPath, FilesystemIterator::KEY_AS_FILENAME); + $basePaths = [ + ROOT . '/config/Migrations/*.php', // main app + ROOT . '/plugins/*/config/Migrations/*.php', // plugins + ]; + + $it = new AppendIterator(); + foreach ($basePaths as $path) { + $it->append(new MigrationFolderIterator($path, FSI::KEY_AS_FILENAME)); + } + return $it; } + /** + * Return an array of method names available in $class, except for those in $excluded. + * + * Used when mocking the individual migration classes to mock "everything + * except up(), down() and change()". + * + * @return array + */ + protected function getClassMethodsExcept($class, array $excluded) { + return array_diff(get_class_methods($class), $excluded); + } + /** * Mock the migration class to spy on schema change methods. * @@ -128,7 +159,8 @@ protected function mockMigration($class, $version) { ->will($this->returnCallback([$this, 'assertOptions'])); // The Migration just needs to return an instance of the mocked Table. - $this->migration = $this->getMock($class, ['table'], [$version]); + $methods = $this->getClassMethodsExcept($class, ['up', 'down', 'change']); + $this->migration = $this->getMock($class, $methods, [$version]); $this->migration->expects($this->any()) ->method('table') ->will($this->returnCallback([$this, 'collectTableProperties'])); @@ -203,6 +235,7 @@ public function assertOptions() { break; default: + //$this->markTestSkipped('Nothing found to test.'); // Useful for debugging the spies. break; } @@ -213,10 +246,18 @@ public function assertOptions() { * Custom assertion to ensure the given array has a [key] element. * * @param string $key The key to assert exists. - * @param array $array An array, typically options for the migration method, obtained from the mocked call to table() or addColumn(), etc. in the Migration file. - * @param string $table The name of the "current" table the Migration file is operating on. This is stateful given the fluent nature of phinx migrations. Set by ::assertOptions(). Used to provide more accurate assertion failure messages. - * @param string $method The name of the current method being called in the migration file. Used to provide more accurate assertion failure messages. - * @param string $field The name of the field being modified by the migration. Context-sensitive. Used to provide more accurate assertion failure messages. + * @param array $array An array, typically options for the migration method, + * obtained from the mocked call to table() or addColumn(), etc. in the + * Migration file. + * @param string $table The name of the "current" table the Migration file + * is operating on. This is stateful given the fluent nature of phinx + * migrations. Set by ::assertOptions(). Used to provide more accurate + * assertion failure messages. + * @param string $method The name of the current method being called in the + * migration file. Used to provide more accurate assertion failure messages. + * @param string $field The name of the field being modified by the migration. + * Context-sensitive. Used to provide more accurate assertion failure + * messages. * @return void */ public function assertHasKey($key, $array, $table, $method, $field) { @@ -232,10 +273,18 @@ public function assertHasKey($key, $array, $table, $method, $field) { * Custom assertion to ensure the given array has a non-empty [key] element. * * @param string $key The key to assert exists and is not empty. - * @param array $array An array, typically options for the migration method, obtained from the mocked call to table() or addColumn(), etc. in the Migration file. - * @param string $table The name of the "current" table the Migration file is operating on. This is stateful given the fluent nature of phinx migrations. Set by ::assertOptions(). Used to provide more accurate assertion failure messages. - * @param string $method The name of the current method being called in the migration file. Used to provide more accurate assertion failure messages. - * @param string $field The name of the field being modified by the migration. Context-sensitive. Used to provide more accurate assertion failure messages. + * @param array $array An array, typically options for the migration method, + * obtained from the mocked call to table() or addColumn(), etc. in the + * Migration file. + * @param string $table The name of the "current" table the Migration file + * is operating on. This is stateful given the fluent nature of phinx + * migrations. Set by ::assertOptions(). Used to provide more accurate + * assertion failure messages. + * @param string $method The name of the current method being called in the + * migration file. Used to provide more accurate assertion failure messages. + * @param string $field The name of the field being modified by the migration. + * Context-sensitive. Used to provide more accurate assertion failure + * messages. * @return void */ public function assertKeyNotEmpty($key, $array, $table, $method, $field) { @@ -250,10 +299,17 @@ public function assertKeyNotEmpty($key, $array, $table, $method, $field) { /** * Custom assertion to ensure that DECIMAL field types defined both a [precision] and a [scale]. * - * @param array $options An array of options obtained from the mocked call to table() or addColumn(), etc. in the Migration file. - * @param string $table The name of the "current" table the Migration file is operating on. This is stateful given the fluent nature of phinx migrations. Set by ::assertOptions(). Used to provide more accurate assertion failure messages. - * @param string $method The name of the current method being called in the migration file. Used to provide more accurate assertion failure messages. - * @param string $field The name of the field being modified by the migration. Context-sensitive. Used to provide more accurate assertion failure messages. + * @param array $options An array of options obtained from the mocked call + * to table() or addColumn(), etc. in the Migration file. + * @param string $table The name of the "current" table the Migration file + * is operating on. This is stateful given the fluent nature of phinx + * migrations. Set by ::assertOptions(). Used to provide more accurate + * assertion failure messages. + * @param string $method The name of the current method being called in the + * migration file. Used to provide more accurate assertion failure messages. + * @param string $field The name of the field being modified by the migration. + * Context-sensitive. Used to provide more accurate assertion failure + * messages. * @return void */ public function assertDecimal($options, $table, $method, $field) { @@ -279,10 +335,17 @@ public function assertDecimal($options, $table, $method, $field) { /** * Custom assertion to ensure that TINYINT(1) (boolean) field types define an [unsigned => true] option. * - * @param array $options An array of options obtained from the mocked call to table() or addColumn(), etc. in the Migration file. - * @param string $table The name of the "current" table the Migration file is operating on. This is stateful given the fluent nature of phinx migrations. Set by ::assertOptions(). Used to provide more accurate assertion failure messages. - * @param string $method The name of the current method being called in the migration file. Used to provide more accurate assertion failure messages. - * @param string $field The name of the field being modified by the migration. Context-sensitive. Used to provide more accurate assertion failure messages. + * @param array $options An array of options obtained from the mocked call + * to table() or addColumn(), etc. in the Migration file. + * @param string $table The name of the "current" table the Migration file + * is operating on. This is stateful given the fluent nature of phinx + * migrations. Set by ::assertOptions(). Used to provide more accurate + * assertion failure messages. + * @param string $method The name of the current method being called in the + * migration file. Used to provide more accurate assertion failure messages. + * @param string $field The name of the field being modified by the migration. + * Context-sensitive. Used to provide more accurate assertion failure + * messages. * @return void */ public function assertBoolean($options, $table, $method, $field) { diff --git a/tests/TestCase/Model/Table/TableTest.php b/tests/TestCase/Model/Table/TableTest.php index 7125507..d076072 100644 --- a/tests/TestCase/Model/Table/TableTest.php +++ b/tests/TestCase/Model/Table/TableTest.php @@ -6,11 +6,15 @@ namespace App\Test\TestCase\Model\Table; use App\Model\Table\Table; +use Cake\ORM\RulesChecker; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; +use Cake\Validation\Validator; /** * \App\Test\TestCase\Model\Table\TableTest + * + * @coversDefaultClass App\Model\Table\Table */ class TableTest extends TestCase { /** @@ -86,7 +90,7 @@ public function testInitialize() { * @return void */ public function testValidationDefault() { - $validator = new \Cake\Validation\Validator(); + $validator = new Validator(); $validator = $this->AppTable->validationDefault($validator); $this->assertTrue($validator->hasField('id')); @@ -104,7 +108,7 @@ public function testValidationDefault() { public function testBuildRules() { $this->assertInstanceOf( '\Cake\ORM\RulesChecker', - $this->AppTable->buildRules(new \Cake\ORM\RulesChecker()), + $this->AppTable->buildRules(new RulesChecker()), 'Cursory sanity check. buildRules() should return a ruleChecker.' ); } diff --git a/tests/TestCase/View/Helper/ListsHelperTest.php b/tests/TestCase/View/Helper/ListsHelperTest.php new file mode 100644 index 0000000..cd132cd --- /dev/null +++ b/tests/TestCase/View/Helper/ListsHelperTest.php @@ -0,0 +1,141 @@ + 'bar', + 'sub' => [ + 'one' => 1, + 'two' => 2, + 'three' => 3, + ], + ]; + + /** + * Temporarily stores the existing Configure(Lists) value during testing. + * (Restored in tearDown().) + * + * @var array + */ + protected $ListsBackup = null; + + /** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $view = new View(); + $this->Helper = new ListsHelper($view); + + $this->ListsBackup = Configure::read('Lists'); + Configure::write('Lists', $this->Lists); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->Helper); + + Configure::write('Lists', $this->ListsBackup); + + parent::tearDown(); + } + + /** + * Test get(). + * + * @param string $path The Configure path to start in. + * @param string $key The key to fetch from $path. + * @param null|string $default Value to return when $path/$key not found or empty. + * @param mixed $expected The expected return value. + * @param string $msg Optional PHPUnit assertion failure message. + * @return void + * @dataProvider provideGetArgs + */ + public function testGet($path, $key, $default, $expected, $msg = '') { + $this->assertEquals( + $expected, + $this->Helper->get($path, $key, $default), + $msg + ); + } + + /** + * Provide I/O pairs for testGet(). + * + * @return array + */ + public function provideGetArgs() { + return [ + [ + 'doesnotexist', + null, + null, + null, + 'Invalid path should return null output.', + ], + + [ + 'foo', + null, + null, + 'bar', + 'Valid path should return string value at that index.', + ], + + [ + 'sub.one', + null, + null, + 1, + 'Valid dotted path should return scalar value at that index.', + ], + + [ + 'sub', + null, + null, + $this->Lists['sub'], + 'Valid partial path should return entire sub-array at that index.', + ], + + [ + 'sub', + 'one', + null, + $this->Lists['sub']['one'], + 'Valid path + key should return scalar value at that index.', + ], + + [ + 'sub', + 'doesnotexist', + 'default text', + 'default text', + 'Invalid path should return the provided default value when present.', + ], + ]; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d99f1fe..1d04009 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -6,6 +6,7 @@ * unit tests in this file. */ +use Cake\Cache\Cache; use Cake\Core\Configure; require dirname(__DIR__) . DS . 'config' . DS . 'bootstrap.php'; @@ -35,8 +36,13 @@ // Wipe out any accumulated caches before running tests. -foreach (\Cake\Cache\Cache::configured() as $key) { - \Cake\Cache\Cache::clear(false, $key); +foreach (Cache::configured() as $key) { + if (in_array($key, ['sessions'])) { + echo "Skipped cache: $key\n"; + continue; + } + + Cache::clear(false, $key); echo "Cleared cache: $key\n"; } diff --git a/webroot/.htaccess b/webroot/.htaccess index f5f2d63..1acdcc3 100755 --- a/webroot/.htaccess +++ b/webroot/.htaccess @@ -3,3 +3,8 @@ RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] + + + Order Allow,Deny + Deny from all + diff --git a/webroot/css/build/.gitkeep b/webroot/css/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/webroot/css/app.css b/webroot/css/src/app.scss similarity index 64% rename from webroot/css/app.css rename to webroot/css/src/app.scss index a69c259..0fc6e56 100644 --- a/webroot/css/app.css +++ b/webroot/css/src/app.scss @@ -1,3 +1,18 @@ +/** + * Main application styles should be defined here. + */ + +@import "../cake.css"; // @TODO: Remove this when no longer required. + +//@import 'settings'; +@import 'foundation'; +@include foundation-everything; +//@import 'variables'; +//@import 'tables'; +//@import 'forms'; +//@import 'footer'; + + div#container { margin-top: 0.3125rem; } diff --git a/webroot/js/app.js b/webroot/js/app.js deleted file mode 100644 index 947ba5a..0000000 --- a/webroot/js/app.js +++ /dev/null @@ -1 +0,0 @@ -$(document).foundation(); diff --git a/webroot/js/build/.gitkeep b/webroot/js/build/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/webroot/js/src/app.js b/webroot/js/src/app.js new file mode 100644 index 0000000..788dc6d --- /dev/null +++ b/webroot/js/src/app.js @@ -0,0 +1,8 @@ +/** + * This file contains any directive that should be executed **on every page**. + * This is the last script to be loaded before (and before any + * page-specific scripts. Calls to global functions should happen here. + * + * Anything that needs to happen during window.onload() should be placed in + * init.js. + */ diff --git a/webroot/js/src/init.js b/webroot/js/src/init.js new file mode 100644 index 0000000..2ac1035 --- /dev/null +++ b/webroot/js/src/init.js @@ -0,0 +1,17 @@ +/** + * This file contains any directive that should be loaded **for every page** + * on document.ready(). It should also contain global function definitions + * used by `app.js` later on. Any global actions that need to occur "inline" + * as the last element of the page before should be made from app.js. + */ + + +// Initialize Foundation +$(document).foundation(); + + +// Define actions that should fire on `window.onload()`. +function init() { +} + +window.onload = init(); diff --git a/webroot/js/test/.gitkeep b/webroot/js/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/webroot/test.html b/webroot/test.html new file mode 100644 index 0000000..6d5ec85 --- /dev/null +++ b/webroot/test.html @@ -0,0 +1,22 @@ + + + + + + Javascript Tests + + + + + +
+
+
+
+
+
+
+ + + + From 9330662e5e048ebba6bac7d815476567f302334b Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 14 Jun 2016 13:44:59 -0500 Subject: [PATCH 06/15] Relocate CSS/JS vendor assets. --- Gruntfile.js | 63 +++++++++++-------- webroot/css/foundation.min.css | 1 - webroot/css/src/_variables.scss | 3 + webroot/css/src/app.scss | 15 +++-- webroot/css/{ => vendor}/cake.css | 0 webroot/css/{ => vendor}/foundation.css | 0 webroot/css/{ => vendor}/normalize.css | 0 webroot/js/{ => vendor}/foundation.min.js | 0 .../foundation/foundation.abide.js | 0 .../foundation/foundation.accordion.js | 0 .../foundation/foundation.alert.js | 0 .../foundation/foundation.clearing.js | 0 .../foundation/foundation.dropdown.js | 0 .../foundation/foundation.equalizer.js | 0 .../foundation/foundation.interchange.js | 0 .../foundation/foundation.joyride.js | 0 .../js/{ => vendor}/foundation/foundation.js | 0 .../foundation/foundation.magellan.js | 0 .../foundation/foundation.offcanvas.js | 0 .../foundation/foundation.orbit.js | 0 .../foundation/foundation.reveal.js | 0 .../foundation/foundation.slider.js | 0 .../{ => vendor}/foundation/foundation.tab.js | 0 .../foundation/foundation.tooltip.js | 0 .../foundation/foundation.topbar.js | 0 25 files changed, 52 insertions(+), 30 deletions(-) delete mode 100644 webroot/css/foundation.min.css create mode 100644 webroot/css/src/_variables.scss rename webroot/css/{ => vendor}/cake.css (100%) rename webroot/css/{ => vendor}/foundation.css (100%) rename webroot/css/{ => vendor}/normalize.css (100%) rename webroot/js/{ => vendor}/foundation.min.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.abide.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.accordion.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.alert.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.clearing.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.dropdown.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.equalizer.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.interchange.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.joyride.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.magellan.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.offcanvas.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.orbit.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.reveal.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.slider.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.tab.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.tooltip.js (100%) rename webroot/js/{ => vendor}/foundation/foundation.topbar.js (100%) diff --git a/Gruntfile.js b/Gruntfile.js index 90caffc..2377ea4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -21,7 +21,7 @@ module.exports = function(grunt) { sass: { dist: { options: { - loadPath: ['bower_components/foundation-sites/scss'], + // loadPath: ['bower_components/foundation-sites/scss'], }, files: { 'webroot/css/build/app.css': 'webroot/css/src/app.scss' @@ -59,25 +59,31 @@ module.exports = function(grunt) { }, main: { src: [ + 'webroot/js/vendor/modernizr.js', 'webroot/js/vendor/jquery.js', - 'bower_components/foundation-sites/js/foundation.core.js', - 'bower_components/foundation-sites/js/foundation.util.mediaQuery.js', - 'bower_components/foundation-sites/js/foundation.drilldown.js', - 'bower_components/foundation-sites/js/foundation.dropdown.js', - 'bower_components/foundation-sites/js/foundation.dropdownMenu.js', - 'bower_components/foundation-sites/js/foundation.equalizer.js', - 'bower_components/foundation-sites/js/foundation.orbit.js', - 'bower_components/foundation-sites/js/foundation.responsiveMenu.js', - 'bower_components/foundation-sites/js/foundation.responsiveToggle.js', - 'bower_components/foundation-sites/js/foundation.tabs.js', - 'bower_components/foundation-sites/js/foundation.toggler.js', - 'bower_components/foundation-sites/js/foundation.util.box.js', - 'bower_components/foundation-sites/js/foundation.util.keyboard.js', - 'bower_components/foundation-sites/js/foundation.util.motion.js', - 'bower_components/foundation-sites/js/foundation.util.nest.js', - 'bower_components/foundation-sites/js/foundation.util.timerAndImageLoader.js', - 'bower_components/foundation-sites/js/foundation.util.touch.js', - 'bower_components/foundation-sites/js/foundation.util.triggers.js', + + // Foundation 5 (bundled): + 'webroot/js/vendor/foundation.min.js', + + // Use all of the following for Foundation 6 instead: + //'bower_components/foundation-sites/js/foundation.core.js', + //'bower_components/foundation-sites/js/foundation.util.mediaQuery.js', + //'bower_components/foundation-sites/js/foundation.drilldown.js', + //'bower_components/foundation-sites/js/foundation.dropdown.js', + //'bower_components/foundation-sites/js/foundation.dropdownMenu.js', + //'bower_components/foundation-sites/js/foundation.equalizer.js', + //'bower_components/foundation-sites/js/foundation.orbit.js', + //'bower_components/foundation-sites/js/foundation.responsiveMenu.js', + //'bower_components/foundation-sites/js/foundation.responsiveToggle.js', + //'bower_components/foundation-sites/js/foundation.tabs.js', + //'bower_components/foundation-sites/js/foundation.toggler.js', + //'bower_components/foundation-sites/js/foundation.util.box.js', + //'bower_components/foundation-sites/js/foundation.util.keyboard.js', + //'bower_components/foundation-sites/js/foundation.util.motion.js', + //'bower_components/foundation-sites/js/foundation.util.nest.js', + //'bower_components/foundation-sites/js/foundation.util.timerAndImageLoader.js', + //'bower_components/foundation-sites/js/foundation.util.touch.js', + //'bower_components/foundation-sites/js/foundation.util.triggers.js', 'webroot/js/src/init.js', 'webroot/js/src/app.js', ], @@ -88,7 +94,6 @@ module.exports = function(grunt) { 'webroot/js/vendor/modernizr.js', 'webroot/js/vendor/jquery.js', 'webroot/js/init.js', - 'webroot/js/src/order-balance.js', 'bower_components/qunit/qunit/qunit.js', 'webroot/js/test/*.js', ], @@ -99,7 +104,7 @@ module.exports = function(grunt) { options: { mangle: false }, - my_target: { + all: { files: { 'webroot/js/build/scripts.min.js': ['webroot/js/build/scripts.js'], } @@ -134,13 +139,21 @@ module.exports = function(grunt) { grunt.registerTask('css', [ 'sass:dist', 'postcss:dist' // build css assets (in order) ]); - grunt.registerTask('sasstest', [ - 'sasslint:dist' // lint source scss files + grunt.registerTask('testcss', [ + 'sasslint:dist' // lint source scss files (not enabled in `default` yet) + ]); + + grunt.registerTask('js', [ + 'concat', 'uglify' // build js assets (in order) ]); grunt.registerTask('testjs', [ - 'concat', 'uglify', 'qunit' // build js assets and run tests (in order) + 'js', 'qunit' // build js assets and run tests (in order) + ]); + + grunt.registerTask('build', [ + 'css', 'js' // build everything, skip tests ]); - grunt.registerTask('default', [ + grunt.registerTask('default', [ // build and test everything 'css', 'testjs' ]); }; diff --git a/webroot/css/foundation.min.css b/webroot/css/foundation.min.css deleted file mode 100644 index dc49286..0000000 --- a/webroot/css/foundation.min.css +++ /dev/null @@ -1 +0,0 @@ -meta.foundation-version{font-family:"/5.5.2/"}meta.foundation-mq-small{font-family:"/only screen/";width:0}meta.foundation-mq-small-only{font-family:"/only screen and (max-width: 40em)/";width:0}meta.foundation-mq-medium{font-family:"/only screen and (min-width:40.0625em)/";width:40.0625em}meta.foundation-mq-medium-only{font-family:"/only screen and (min-width:40.0625em) and (max-width:64em)/";width:40.0625em}meta.foundation-mq-large{font-family:"/only screen and (min-width:64.0625em)/";width:64.0625em}meta.foundation-mq-large-only{font-family:"/only screen and (min-width:64.0625em) and (max-width:90em)/";width:64.0625em}meta.foundation-mq-xlarge{font-family:"/only screen and (min-width:90.0625em)/";width:90.0625em}meta.foundation-mq-xlarge-only{font-family:"/only screen and (min-width:90.0625em) and (max-width:120em)/";width:90.0625em}meta.foundation-mq-xxlarge{font-family:"/only screen and (min-width:120.0625em)/";width:120.0625em}meta.foundation-data-attribute-namespace{font-family:false}html,body{height:100%}html{box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit}html,body{font-size:100%}body{background:#fff;color:#222;cursor:auto;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-style:normal;font-weight:normal;line-height:1.5;margin:0;padding:0;position:relative}a:hover{cursor:pointer}img{max-width:100%;height:auto}img{-ms-interpolation-mode:bicubic}#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object,.mqa-display img,.mqa-display embed,.mqa-display object{max-width:none !important}.left{float:left !important}.right{float:right !important}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}.hide{display:none}.invisible{visibility:hidden}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}img{display:inline-block;vertical-align:middle}textarea{height:auto;min-height:50px}select{width:100%}.row{margin:0 auto;max-width:62.5rem;width:100%}.row:before,.row:after{content:" ";display:table}.row:after{clear:both}.row.collapse>.column,.row.collapse>.columns{padding-left:0;padding-right:0}.row.collapse .row{margin-left:0;margin-right:0}.row .row{margin:0 -0.9375rem;max-width:none;width:auto}.row .row:before,.row .row:after{content:" ";display:table}.row .row:after{clear:both}.row .row.collapse{margin:0;max-width:none;width:auto}.row .row.collapse:before,.row .row.collapse:after{content:" ";display:table}.row .row.collapse:after{clear:both}.column,.columns{padding-left:0.9375rem;padding-right:0.9375rem;width:100%;float:left}.column+.column:last-child,.columns+.column:last-child,.column+.columns:last-child,.columns+.columns:last-child{float:right}.column+.column.end,.columns+.column.end,.column+.columns.end,.columns+.columns.end{float:left}@media only screen{.small-push-0{position:relative;left:0;right:auto}.small-pull-0{position:relative;right:0;left:auto}.small-push-1{position:relative;left:8.33333%;right:auto}.small-pull-1{position:relative;right:8.33333%;left:auto}.small-push-2{position:relative;left:16.66667%;right:auto}.small-pull-2{position:relative;right:16.66667%;left:auto}.small-push-3{position:relative;left:25%;right:auto}.small-pull-3{position:relative;right:25%;left:auto}.small-push-4{position:relative;left:33.33333%;right:auto}.small-pull-4{position:relative;right:33.33333%;left:auto}.small-push-5{position:relative;left:41.66667%;right:auto}.small-pull-5{position:relative;right:41.66667%;left:auto}.small-push-6{position:relative;left:50%;right:auto}.small-pull-6{position:relative;right:50%;left:auto}.small-push-7{position:relative;left:58.33333%;right:auto}.small-pull-7{position:relative;right:58.33333%;left:auto}.small-push-8{position:relative;left:66.66667%;right:auto}.small-pull-8{position:relative;right:66.66667%;left:auto}.small-push-9{position:relative;left:75%;right:auto}.small-pull-9{position:relative;right:75%;left:auto}.small-push-10{position:relative;left:83.33333%;right:auto}.small-pull-10{position:relative;right:83.33333%;left:auto}.small-push-11{position:relative;left:91.66667%;right:auto}.small-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.small-1{width:8.33333%}.small-2{width:16.66667%}.small-3{width:25%}.small-4{width:33.33333%}.small-5{width:41.66667%}.small-6{width:50%}.small-7{width:58.33333%}.small-8{width:66.66667%}.small-9{width:75%}.small-10{width:83.33333%}.small-11{width:91.66667%}.small-12{width:100%}.small-offset-0{margin-left:0 !important}.small-offset-1{margin-left:8.33333% !important}.small-offset-2{margin-left:16.66667% !important}.small-offset-3{margin-left:25% !important}.small-offset-4{margin-left:33.33333% !important}.small-offset-5{margin-left:41.66667% !important}.small-offset-6{margin-left:50% !important}.small-offset-7{margin-left:58.33333% !important}.small-offset-8{margin-left:66.66667% !important}.small-offset-9{margin-left:75% !important}.small-offset-10{margin-left:83.33333% !important}.small-offset-11{margin-left:91.66667% !important}.small-reset-order{float:left;left:auto;margin-left:0;margin-right:0;right:auto}.column.small-centered,.columns.small-centered{margin-left:auto;margin-right:auto;float:none}.column.small-uncentered,.columns.small-uncentered{float:left;margin-left:0;margin-right:0}.column.small-centered:last-child,.columns.small-centered:last-child{float:none}.column.small-uncentered:last-child,.columns.small-uncentered:last-child{float:left}.column.small-uncentered.opposite,.columns.small-uncentered.opposite{float:right}.row.small-collapse>.column,.row.small-collapse>.columns{padding-left:0;padding-right:0}.row.small-collapse .row{margin-left:0;margin-right:0}.row.small-uncollapse>.column,.row.small-uncollapse>.columns{padding-left:0.9375rem;padding-right:0.9375rem;float:left}}@media only screen and (min-width: 40.0625em){.medium-push-0{position:relative;left:0;right:auto}.medium-pull-0{position:relative;right:0;left:auto}.medium-push-1{position:relative;left:8.33333%;right:auto}.medium-pull-1{position:relative;right:8.33333%;left:auto}.medium-push-2{position:relative;left:16.66667%;right:auto}.medium-pull-2{position:relative;right:16.66667%;left:auto}.medium-push-3{position:relative;left:25%;right:auto}.medium-pull-3{position:relative;right:25%;left:auto}.medium-push-4{position:relative;left:33.33333%;right:auto}.medium-pull-4{position:relative;right:33.33333%;left:auto}.medium-push-5{position:relative;left:41.66667%;right:auto}.medium-pull-5{position:relative;right:41.66667%;left:auto}.medium-push-6{position:relative;left:50%;right:auto}.medium-pull-6{position:relative;right:50%;left:auto}.medium-push-7{position:relative;left:58.33333%;right:auto}.medium-pull-7{position:relative;right:58.33333%;left:auto}.medium-push-8{position:relative;left:66.66667%;right:auto}.medium-pull-8{position:relative;right:66.66667%;left:auto}.medium-push-9{position:relative;left:75%;right:auto}.medium-pull-9{position:relative;right:75%;left:auto}.medium-push-10{position:relative;left:83.33333%;right:auto}.medium-pull-10{position:relative;right:83.33333%;left:auto}.medium-push-11{position:relative;left:91.66667%;right:auto}.medium-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.medium-1{width:8.33333%}.medium-2{width:16.66667%}.medium-3{width:25%}.medium-4{width:33.33333%}.medium-5{width:41.66667%}.medium-6{width:50%}.medium-7{width:58.33333%}.medium-8{width:66.66667%}.medium-9{width:75%}.medium-10{width:83.33333%}.medium-11{width:91.66667%}.medium-12{width:100%}.medium-offset-0{margin-left:0 !important}.medium-offset-1{margin-left:8.33333% !important}.medium-offset-2{margin-left:16.66667% !important}.medium-offset-3{margin-left:25% !important}.medium-offset-4{margin-left:33.33333% !important}.medium-offset-5{margin-left:41.66667% !important}.medium-offset-6{margin-left:50% !important}.medium-offset-7{margin-left:58.33333% !important}.medium-offset-8{margin-left:66.66667% !important}.medium-offset-9{margin-left:75% !important}.medium-offset-10{margin-left:83.33333% !important}.medium-offset-11{margin-left:91.66667% !important}.medium-reset-order{float:left;left:auto;margin-left:0;margin-right:0;right:auto}.column.medium-centered,.columns.medium-centered{margin-left:auto;margin-right:auto;float:none}.column.medium-uncentered,.columns.medium-uncentered{float:left;margin-left:0;margin-right:0}.column.medium-centered:last-child,.columns.medium-centered:last-child{float:none}.column.medium-uncentered:last-child,.columns.medium-uncentered:last-child{float:left}.column.medium-uncentered.opposite,.columns.medium-uncentered.opposite{float:right}.row.medium-collapse>.column,.row.medium-collapse>.columns{padding-left:0;padding-right:0}.row.medium-collapse .row{margin-left:0;margin-right:0}.row.medium-uncollapse>.column,.row.medium-uncollapse>.columns{padding-left:0.9375rem;padding-right:0.9375rem;float:left}.push-0{position:relative;left:0;right:auto}.pull-0{position:relative;right:0;left:auto}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}}@media only screen and (min-width: 64.0625em){.large-push-0{position:relative;left:0;right:auto}.large-pull-0{position:relative;right:0;left:auto}.large-push-1{position:relative;left:8.33333%;right:auto}.large-pull-1{position:relative;right:8.33333%;left:auto}.large-push-2{position:relative;left:16.66667%;right:auto}.large-pull-2{position:relative;right:16.66667%;left:auto}.large-push-3{position:relative;left:25%;right:auto}.large-pull-3{position:relative;right:25%;left:auto}.large-push-4{position:relative;left:33.33333%;right:auto}.large-pull-4{position:relative;right:33.33333%;left:auto}.large-push-5{position:relative;left:41.66667%;right:auto}.large-pull-5{position:relative;right:41.66667%;left:auto}.large-push-6{position:relative;left:50%;right:auto}.large-pull-6{position:relative;right:50%;left:auto}.large-push-7{position:relative;left:58.33333%;right:auto}.large-pull-7{position:relative;right:58.33333%;left:auto}.large-push-8{position:relative;left:66.66667%;right:auto}.large-pull-8{position:relative;right:66.66667%;left:auto}.large-push-9{position:relative;left:75%;right:auto}.large-pull-9{position:relative;right:75%;left:auto}.large-push-10{position:relative;left:83.33333%;right:auto}.large-pull-10{position:relative;right:83.33333%;left:auto}.large-push-11{position:relative;left:91.66667%;right:auto}.large-pull-11{position:relative;right:91.66667%;left:auto}.column,.columns{position:relative;padding-left:0.9375rem;padding-right:0.9375rem;float:left}.large-1{width:8.33333%}.large-2{width:16.66667%}.large-3{width:25%}.large-4{width:33.33333%}.large-5{width:41.66667%}.large-6{width:50%}.large-7{width:58.33333%}.large-8{width:66.66667%}.large-9{width:75%}.large-10{width:83.33333%}.large-11{width:91.66667%}.large-12{width:100%}.large-offset-0{margin-left:0 !important}.large-offset-1{margin-left:8.33333% !important}.large-offset-2{margin-left:16.66667% !important}.large-offset-3{margin-left:25% !important}.large-offset-4{margin-left:33.33333% !important}.large-offset-5{margin-left:41.66667% !important}.large-offset-6{margin-left:50% !important}.large-offset-7{margin-left:58.33333% !important}.large-offset-8{margin-left:66.66667% !important}.large-offset-9{margin-left:75% !important}.large-offset-10{margin-left:83.33333% !important}.large-offset-11{margin-left:91.66667% !important}.large-reset-order{float:left;left:auto;margin-left:0;margin-right:0;right:auto}.column.large-centered,.columns.large-centered{margin-left:auto;margin-right:auto;float:none}.column.large-uncentered,.columns.large-uncentered{float:left;margin-left:0;margin-right:0}.column.large-centered:last-child,.columns.large-centered:last-child{float:none}.column.large-uncentered:last-child,.columns.large-uncentered:last-child{float:left}.column.large-uncentered.opposite,.columns.large-uncentered.opposite{float:right}.row.large-collapse>.column,.row.large-collapse>.columns{padding-left:0;padding-right:0}.row.large-collapse .row{margin-left:0;margin-right:0}.row.large-uncollapse>.column,.row.large-uncollapse>.columns{padding-left:0.9375rem;padding-right:0.9375rem;float:left}.push-0{position:relative;left:0;right:auto}.pull-0{position:relative;right:0;left:auto}.push-1{position:relative;left:8.33333%;right:auto}.pull-1{position:relative;right:8.33333%;left:auto}.push-2{position:relative;left:16.66667%;right:auto}.pull-2{position:relative;right:16.66667%;left:auto}.push-3{position:relative;left:25%;right:auto}.pull-3{position:relative;right:25%;left:auto}.push-4{position:relative;left:33.33333%;right:auto}.pull-4{position:relative;right:33.33333%;left:auto}.push-5{position:relative;left:41.66667%;right:auto}.pull-5{position:relative;right:41.66667%;left:auto}.push-6{position:relative;left:50%;right:auto}.pull-6{position:relative;right:50%;left:auto}.push-7{position:relative;left:58.33333%;right:auto}.pull-7{position:relative;right:58.33333%;left:auto}.push-8{position:relative;left:66.66667%;right:auto}.pull-8{position:relative;right:66.66667%;left:auto}.push-9{position:relative;left:75%;right:auto}.pull-9{position:relative;right:75%;left:auto}.push-10{position:relative;left:83.33333%;right:auto}.pull-10{position:relative;right:83.33333%;left:auto}.push-11{position:relative;left:91.66667%;right:auto}.pull-11{position:relative;right:91.66667%;left:auto}}button,.button{-webkit-appearance:none;-moz-appearance:none;border-radius:0;border-style:solid;border-width:0;cursor:pointer;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal;line-height:normal;margin:0 0 1.25rem;position:relative;text-align:center;text-decoration:none;display:inline-block;padding:1rem 2rem 1.0625rem 2rem;font-size:1rem;background-color:#008CBA;border-color:#007095;color:#fff;transition:background-color 300ms ease-out}button:hover,button:focus,.button:hover,.button:focus{background-color:#007095}button:hover,button:focus,.button:hover,.button:focus{color:#fff}button.secondary,.button.secondary{background-color:#e7e7e7;border-color:#b9b9b9;color:#333}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{background-color:#b9b9b9}button.secondary:hover,button.secondary:focus,.button.secondary:hover,.button.secondary:focus{color:#333}button.success,.button.success{background-color:#43AC6A;border-color:#368a55;color:#fff}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{background-color:#368a55}button.success:hover,button.success:focus,.button.success:hover,.button.success:focus{color:#fff}button.alert,.button.alert{background-color:#f04124;border-color:#cf2a0e;color:#fff}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{background-color:#cf2a0e}button.alert:hover,button.alert:focus,.button.alert:hover,.button.alert:focus{color:#fff}button.warning,.button.warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff}button.warning:hover,button.warning:focus,.button.warning:hover,.button.warning:focus{background-color:#cf6e0e}button.warning:hover,button.warning:focus,.button.warning:hover,.button.warning:focus{color:#fff}button.info,.button.info{background-color:#a0d3e8;border-color:#61b6d9;color:#333}button.info:hover,button.info:focus,.button.info:hover,.button.info:focus{background-color:#61b6d9}button.info:hover,button.info:focus,.button.info:hover,.button.info:focus{color:#fff}button.large,.button.large{padding:1.125rem 2.25rem 1.1875rem 2.25rem;font-size:1.25rem}button.small,.button.small{padding:0.875rem 1.75rem 0.9375rem 1.75rem;font-size:0.8125rem}button.tiny,.button.tiny{padding:0.625rem 1.25rem 0.6875rem 1.25rem;font-size:0.6875rem}button.expand,.button.expand{padding-left:0;padding-right:0;width:100%}button.left-align,.button.left-align{text-align:left;text-indent:0.75rem}button.right-align,.button.right-align{text-align:right;padding-right:0.75rem}button.radius,.button.radius{border-radius:3px}button.round,.button.round{border-radius:1000px}button.disabled,button[disabled],.button.disabled,.button[disabled]{background-color:#008CBA;border-color:#007095;color:#fff;box-shadow:none;cursor:default;opacity:0.7}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#007095}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{color:#fff}button.disabled:hover,button.disabled:focus,button[disabled]:hover,button[disabled]:focus,.button.disabled:hover,.button.disabled:focus,.button[disabled]:hover,.button[disabled]:focus{background-color:#008CBA}button.disabled.secondary,button[disabled].secondary,.button.disabled.secondary,.button[disabled].secondary{background-color:#e7e7e7;border-color:#b9b9b9;color:#333;box-shadow:none;cursor:default;opacity:0.7}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#b9b9b9}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{color:#333}button.disabled.secondary:hover,button.disabled.secondary:focus,button[disabled].secondary:hover,button[disabled].secondary:focus,.button.disabled.secondary:hover,.button.disabled.secondary:focus,.button[disabled].secondary:hover,.button[disabled].secondary:focus{background-color:#e7e7e7}button.disabled.success,button[disabled].success,.button.disabled.success,.button[disabled].success{background-color:#43AC6A;border-color:#368a55;color:#fff;box-shadow:none;cursor:default;opacity:0.7}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#368a55}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{color:#fff}button.disabled.success:hover,button.disabled.success:focus,button[disabled].success:hover,button[disabled].success:focus,.button.disabled.success:hover,.button.disabled.success:focus,.button[disabled].success:hover,.button[disabled].success:focus{background-color:#43AC6A}button.disabled.alert,button[disabled].alert,.button.disabled.alert,.button[disabled].alert{background-color:#f04124;border-color:#cf2a0e;color:#fff;box-shadow:none;cursor:default;opacity:0.7}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#cf2a0e}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{color:#fff}button.disabled.alert:hover,button.disabled.alert:focus,button[disabled].alert:hover,button[disabled].alert:focus,.button.disabled.alert:hover,.button.disabled.alert:focus,.button[disabled].alert:hover,.button[disabled].alert:focus{background-color:#f04124}button.disabled.warning,button[disabled].warning,.button.disabled.warning,.button[disabled].warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff;box-shadow:none;cursor:default;opacity:0.7}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{background-color:#cf6e0e}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{color:#fff}button.disabled.warning:hover,button.disabled.warning:focus,button[disabled].warning:hover,button[disabled].warning:focus,.button.disabled.warning:hover,.button.disabled.warning:focus,.button[disabled].warning:hover,.button[disabled].warning:focus{background-color:#f08a24}button.disabled.info,button[disabled].info,.button.disabled.info,.button[disabled].info{background-color:#a0d3e8;border-color:#61b6d9;color:#333;box-shadow:none;cursor:default;opacity:0.7}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{background-color:#61b6d9}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{color:#fff}button.disabled.info:hover,button.disabled.info:focus,button[disabled].info:hover,button[disabled].info:focus,.button.disabled.info:hover,.button.disabled.info:focus,.button[disabled].info:hover,.button[disabled].info:focus{background-color:#a0d3e8}button::-moz-focus-inner{border:0;padding:0}@media only screen and (min-width: 40.0625em){button,.button{display:inline-block}}form{margin:0 0 1rem}form .row .row{margin:0 -0.5rem}form .row .row .column,form .row .row .columns{padding:0 0.5rem}form .row .row.collapse{margin:0}form .row .row.collapse .column,form .row .row.collapse .columns{padding:0}form .row .row.collapse input{-webkit-border-bottom-right-radius:0;-webkit-border-top-right-radius:0;border-bottom-right-radius:0;border-top-right-radius:0}form .row input.column,form .row input.columns,form .row textarea.column,form .row textarea.columns{padding-left:0.5rem}label{color:#4d4d4d;cursor:pointer;display:block;font-size:0.875rem;font-weight:normal;line-height:1.5;margin-bottom:0}label.right{float:none !important;text-align:right}label.inline{margin:0 0 1rem 0;padding:0.5625rem 0}label small{text-transform:capitalize;color:#676767}.prefix,.postfix{border-style:solid;border-width:1px;display:block;font-size:0.875rem;height:2.3125rem;line-height:2.3125rem;overflow:visible;padding-bottom:0;padding-top:0;position:relative;text-align:center;width:100%;z-index:2}.postfix.button{border-color:true}.prefix.button{border:none;padding-left:0;padding-right:0;padding-bottom:0;padding-top:0;text-align:center}.prefix.button.radius{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.postfix.button.radius{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}.prefix.button.round{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.postfix.button.round{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}span.prefix,label.prefix{background:#f2f2f2;border-right:none;color:#333;border-color:#ccc}span.postfix,label.postfix{background:#f2f2f2;color:#333;border-color:#ccc}input[type="text"],input[type="password"],input[type="date"],input[type="datetime"],input[type="datetime-local"],input[type="month"],input[type="week"],input[type="email"],input[type="number"],input[type="search"],input[type="tel"],input[type="time"],input[type="url"],input[type="color"],textarea{-webkit-appearance:none;-moz-appearance:none;border-radius:0;background-color:#fff;border-style:solid;border-width:1px;border-color:#ccc;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);color:rgba(0,0,0,0.75);display:block;font-family:inherit;font-size:0.875rem;height:2.3125rem;margin:0 0 1rem 0;padding:0.5rem;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color 0.15s linear,background 0.15s linear;-moz-transition:border-color 0.15s linear,background 0.15s linear;-ms-transition:border-color 0.15s linear,background 0.15s linear;-o-transition:border-color 0.15s linear,background 0.15s linear;transition:border-color 0.15s linear,background 0.15s linear}input[type="text"]:focus,input[type="password"]:focus,input[type="date"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="month"]:focus,input[type="week"]:focus,input[type="email"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="time"]:focus,input[type="url"]:focus,input[type="color"]:focus,textarea:focus{background:#fafafa;border-color:#999;outline:none}input[type="text"]:disabled,input[type="password"]:disabled,input[type="date"]:disabled,input[type="datetime"]:disabled,input[type="datetime-local"]:disabled,input[type="month"]:disabled,input[type="week"]:disabled,input[type="email"]:disabled,input[type="number"]:disabled,input[type="search"]:disabled,input[type="tel"]:disabled,input[type="time"]:disabled,input[type="url"]:disabled,input[type="color"]:disabled,textarea:disabled{background-color:#ddd;cursor:default}input[type="text"][disabled],input[type="text"][readonly],fieldset[disabled] input[type="text"],input[type="password"][disabled],input[type="password"][readonly],fieldset[disabled] input[type="password"],input[type="date"][disabled],input[type="date"][readonly],fieldset[disabled] input[type="date"],input[type="datetime"][disabled],input[type="datetime"][readonly],fieldset[disabled] input[type="datetime"],input[type="datetime-local"][disabled],input[type="datetime-local"][readonly],fieldset[disabled] input[type="datetime-local"],input[type="month"][disabled],input[type="month"][readonly],fieldset[disabled] input[type="month"],input[type="week"][disabled],input[type="week"][readonly],fieldset[disabled] input[type="week"],input[type="email"][disabled],input[type="email"][readonly],fieldset[disabled] input[type="email"],input[type="number"][disabled],input[type="number"][readonly],fieldset[disabled] input[type="number"],input[type="search"][disabled],input[type="search"][readonly],fieldset[disabled] input[type="search"],input[type="tel"][disabled],input[type="tel"][readonly],fieldset[disabled] input[type="tel"],input[type="time"][disabled],input[type="time"][readonly],fieldset[disabled] input[type="time"],input[type="url"][disabled],input[type="url"][readonly],fieldset[disabled] input[type="url"],input[type="color"][disabled],input[type="color"][readonly],fieldset[disabled] input[type="color"],textarea[disabled],textarea[readonly],fieldset[disabled] textarea{background-color:#ddd;cursor:default}input[type="text"].radius,input[type="password"].radius,input[type="date"].radius,input[type="datetime"].radius,input[type="datetime-local"].radius,input[type="month"].radius,input[type="week"].radius,input[type="email"].radius,input[type="number"].radius,input[type="search"].radius,input[type="tel"].radius,input[type="time"].radius,input[type="url"].radius,input[type="color"].radius,textarea.radius{border-radius:3px}form .row .prefix-radius.row.collapse input,form .row .prefix-radius.row.collapse textarea,form .row .prefix-radius.row.collapse select,form .row .prefix-radius.row.collapse button{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}form .row .prefix-radius.row.collapse .prefix{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}form .row .postfix-radius.row.collapse input,form .row .postfix-radius.row.collapse textarea,form .row .postfix-radius.row.collapse select,form .row .postfix-radius.row.collapse button{border-radius:0;-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}form .row .postfix-radius.row.collapse .postfix{border-radius:0;-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}form .row .prefix-round.row.collapse input,form .row .prefix-round.row.collapse textarea,form .row .prefix-round.row.collapse select,form .row .prefix-round.row.collapse button{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}form .row .prefix-round.row.collapse .prefix{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}form .row .postfix-round.row.collapse input,form .row .postfix-round.row.collapse textarea,form .row .postfix-round.row.collapse select,form .row .postfix-round.row.collapse button{border-radius:0;-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}form .row .postfix-round.row.collapse .postfix{border-radius:0;-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}input[type="submit"]{-webkit-appearance:none;-moz-appearance:none;border-radius:0}textarea[rows]{height:auto}textarea{max-width:100%}::-webkit-input-placeholder{color:#ccc}:-moz-placeholder{color:#ccc}::-moz-placeholder{color:#ccc}:-ms-input-placeholder{color:#ccc}select{-webkit-appearance:none !important;-moz-appearance:none !important;background-color:#FAFAFA;border-radius:0;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMTJweCIgeT0iMHB4IiB3aWR0aD0iMjRweCIgaGVpZ2h0PSIzcHgiIHZpZXdCb3g9IjAgMCA2IDMiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDYgMyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBvbHlnb24gcG9pbnRzPSI1Ljk5MiwwIDIuOTkyLDMgLTAuMDA4LDAgIi8+PC9zdmc+);background-position:100% center;background-repeat:no-repeat;border-style:solid;border-width:1px;border-color:#ccc;color:rgba(0,0,0,0.75);font-family:inherit;font-size:0.875rem;line-height:normal;padding:0.5rem;border-radius:0;height:2.3125rem}select::-ms-expand{display:none}select.radius{border-radius:3px}select:hover{background-color:#f3f3f3;border-color:#999}select:disabled{background-color:#ddd;cursor:default}select[multiple]{height:auto}input[type="file"],input[type="checkbox"],input[type="radio"],select{margin:0 0 1rem 0}input[type="checkbox"]+label,input[type="radio"]+label{display:inline-block;margin-left:0.5rem;margin-right:1rem;margin-bottom:0;vertical-align:baseline}input[type="file"]{width:100%}fieldset{border:1px solid #ddd;margin:1.125rem 0;padding:1.25rem}fieldset legend{background:#fff;font-weight:bold;margin-left:-0.1875rem;margin:0;padding:0 0.1875rem}[data-abide] .error small.error,[data-abide] .error span.error,[data-abide] span.error,[data-abide] small.error{display:block;font-size:0.75rem;font-style:italic;font-weight:normal;margin-bottom:1rem;margin-top:-1px;padding:0.375rem 0.5625rem 0.5625rem;background:#f04124;color:#fff}[data-abide] span.error,[data-abide] small.error{display:none}span.error,small.error{display:block;font-size:0.75rem;font-style:italic;font-weight:normal;margin-bottom:1rem;margin-top:-1px;padding:0.375rem 0.5625rem 0.5625rem;background:#f04124;color:#fff}.error input,.error textarea,.error select{margin-bottom:0}.error input[type="checkbox"],.error input[type="radio"]{margin-bottom:1rem}.error label,.error label.error{color:#f04124}.error small.error{display:block;font-size:0.75rem;font-style:italic;font-weight:normal;margin-bottom:1rem;margin-top:-1px;padding:0.375rem 0.5625rem 0.5625rem;background:#f04124;color:#fff}.error>label>small{background:transparent;color:#676767;display:inline;font-size:60%;font-style:normal;margin:0;padding:0;text-transform:capitalize}.error span.error-message{display:block}input.error,textarea.error,select.error{margin-bottom:0}label.error{color:#f04124}meta.foundation-mq-topbar{font-family:"/only screen and (min-width:40.0625em)/";width:40.0625em}.contain-to-grid{width:100%;background:#333}.contain-to-grid .top-bar{margin-bottom:0}.fixed{position:fixed;top:0;width:100%;z-index:99;left:0}.fixed.expanded:not(.top-bar){height:auto;max-height:100%;overflow-y:auto;width:100%}.fixed.expanded:not(.top-bar) .title-area{position:fixed;width:100%;z-index:99}.fixed.expanded:not(.top-bar) .top-bar-section{margin-top:2.8125rem;z-index:98}.top-bar{background:#333;height:2.8125rem;line-height:2.8125rem;margin-bottom:0;overflow:hidden;position:relative}.top-bar ul{list-style:none;margin-bottom:0}.top-bar .row{max-width:none}.top-bar form,.top-bar input,.top-bar select{margin-bottom:0}.top-bar input,.top-bar select{font-size:0.75rem;height:1.75rem;padding-bottom:.35rem;padding-top:.35rem}.top-bar .button,.top-bar button{font-size:0.75rem;margin-bottom:0;padding-bottom:0.4125rem;padding-top:0.4125rem}@media only screen and (max-width: 40em){.top-bar .button,.top-bar button{position:relative;top:-1px}}.top-bar .title-area{margin:0;position:relative}.top-bar .name{font-size:16px;height:2.8125rem;margin:0}.top-bar .name h1,.top-bar .name h2,.top-bar .name h3,.top-bar .name h4,.top-bar .name p,.top-bar .name span{font-size:1.0625rem;line-height:2.8125rem;margin:0}.top-bar .name h1 a,.top-bar .name h2 a,.top-bar .name h3 a,.top-bar .name h4 a,.top-bar .name p a,.top-bar .name span a{color:#fff;display:block;font-weight:normal;padding:0 0.9375rem;width:75%}.top-bar .toggle-topbar{position:absolute;right:0;top:0}.top-bar .toggle-topbar a{color:#fff;display:block;font-size:0.8125rem;font-weight:bold;height:2.8125rem;line-height:2.8125rem;padding:0 0.9375rem;position:relative;text-transform:uppercase}.top-bar .toggle-topbar.menu-icon{margin-top:-16px;top:50%}.top-bar .toggle-topbar.menu-icon a{color:#fff;height:34px;line-height:33px;padding:0 2.5rem 0 0.9375rem;position:relative}.top-bar .toggle-topbar.menu-icon a span::after{content:"";display:block;height:0;position:absolute;margin-top:-8px;top:50%;right:0.9375rem;box-shadow:0 0 0 1px #fff,0 7px 0 1px #fff,0 14px 0 1px #fff;width:16px}.top-bar .toggle-topbar.menu-icon a span:hover:after{box-shadow:0 0 0 1px "",0 7px 0 1px "",0 14px 0 1px ""}.top-bar.expanded{background:transparent;height:auto}.top-bar.expanded .title-area{background:#333}.top-bar.expanded .toggle-topbar a{color:#888}.top-bar.expanded .toggle-topbar a span::after{box-shadow:0 0 0 1px #888,0 7px 0 1px #888,0 14px 0 1px #888}@media screen and (-webkit-min-device-pixel-ratio: 0){.top-bar.expanded .top-bar-section .has-dropdown.moved>.dropdown,.top-bar.expanded .top-bar-section .dropdown{clip:initial}.top-bar.expanded .top-bar-section .has-dropdown:not(.moved)>ul{padding:0}}.top-bar-section{left:0;position:relative;width:auto;transition:left 300ms ease-out}.top-bar-section ul{display:block;font-size:16px;height:auto;margin:0;padding:0;width:100%}.top-bar-section .divider,.top-bar-section [role="separator"]{border-top:solid 1px #1a1a1a;clear:both;height:1px;width:100%}.top-bar-section ul li{background:#333}.top-bar-section ul li>a{color:#fff;display:block;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:0.8125rem;font-weight:normal;padding-left:0.9375rem;padding:12px 0 12px 0.9375rem;text-transform:none;width:100%}.top-bar-section ul li>a.button{font-size:0.8125rem;padding-left:0.9375rem;padding-right:0.9375rem;background-color:#008CBA;border-color:#007095;color:#fff}.top-bar-section ul li>a.button:hover,.top-bar-section ul li>a.button:focus{background-color:#007095}.top-bar-section ul li>a.button:hover,.top-bar-section ul li>a.button:focus{color:#fff}.top-bar-section ul li>a.button.secondary{background-color:#e7e7e7;border-color:#b9b9b9;color:#333}.top-bar-section ul li>a.button.secondary:hover,.top-bar-section ul li>a.button.secondary:focus{background-color:#b9b9b9}.top-bar-section ul li>a.button.secondary:hover,.top-bar-section ul li>a.button.secondary:focus{color:#333}.top-bar-section ul li>a.button.success{background-color:#43AC6A;border-color:#368a55;color:#fff}.top-bar-section ul li>a.button.success:hover,.top-bar-section ul li>a.button.success:focus{background-color:#368a55}.top-bar-section ul li>a.button.success:hover,.top-bar-section ul li>a.button.success:focus{color:#fff}.top-bar-section ul li>a.button.alert{background-color:#f04124;border-color:#cf2a0e;color:#fff}.top-bar-section ul li>a.button.alert:hover,.top-bar-section ul li>a.button.alert:focus{background-color:#cf2a0e}.top-bar-section ul li>a.button.alert:hover,.top-bar-section ul li>a.button.alert:focus{color:#fff}.top-bar-section ul li>a.button.warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff}.top-bar-section ul li>a.button.warning:hover,.top-bar-section ul li>a.button.warning:focus{background-color:#cf6e0e}.top-bar-section ul li>a.button.warning:hover,.top-bar-section ul li>a.button.warning:focus{color:#fff}.top-bar-section ul li>a.button.info{background-color:#a0d3e8;border-color:#61b6d9;color:#333}.top-bar-section ul li>a.button.info:hover,.top-bar-section ul li>a.button.info:focus{background-color:#61b6d9}.top-bar-section ul li>a.button.info:hover,.top-bar-section ul li>a.button.info:focus{color:#fff}.top-bar-section ul li>button{font-size:0.8125rem;padding-left:0.9375rem;padding-right:0.9375rem;background-color:#008CBA;border-color:#007095;color:#fff}.top-bar-section ul li>button:hover,.top-bar-section ul li>button:focus{background-color:#007095}.top-bar-section ul li>button:hover,.top-bar-section ul li>button:focus{color:#fff}.top-bar-section ul li>button.secondary{background-color:#e7e7e7;border-color:#b9b9b9;color:#333}.top-bar-section ul li>button.secondary:hover,.top-bar-section ul li>button.secondary:focus{background-color:#b9b9b9}.top-bar-section ul li>button.secondary:hover,.top-bar-section ul li>button.secondary:focus{color:#333}.top-bar-section ul li>button.success{background-color:#43AC6A;border-color:#368a55;color:#fff}.top-bar-section ul li>button.success:hover,.top-bar-section ul li>button.success:focus{background-color:#368a55}.top-bar-section ul li>button.success:hover,.top-bar-section ul li>button.success:focus{color:#fff}.top-bar-section ul li>button.alert{background-color:#f04124;border-color:#cf2a0e;color:#fff}.top-bar-section ul li>button.alert:hover,.top-bar-section ul li>button.alert:focus{background-color:#cf2a0e}.top-bar-section ul li>button.alert:hover,.top-bar-section ul li>button.alert:focus{color:#fff}.top-bar-section ul li>button.warning{background-color:#f08a24;border-color:#cf6e0e;color:#fff}.top-bar-section ul li>button.warning:hover,.top-bar-section ul li>button.warning:focus{background-color:#cf6e0e}.top-bar-section ul li>button.warning:hover,.top-bar-section ul li>button.warning:focus{color:#fff}.top-bar-section ul li>button.info{background-color:#a0d3e8;border-color:#61b6d9;color:#333}.top-bar-section ul li>button.info:hover,.top-bar-section ul li>button.info:focus{background-color:#61b6d9}.top-bar-section ul li>button.info:hover,.top-bar-section ul li>button.info:focus{color:#fff}.top-bar-section ul li:hover:not(.has-form)>a{background-color:#555;color:#fff;background:#222}.top-bar-section ul li.active>a{background:#008CBA;color:#fff}.top-bar-section ul li.active>a:hover{background:#0078a0;color:#fff}.top-bar-section .has-form{padding:0.9375rem}.top-bar-section .has-dropdown{position:relative}.top-bar-section .has-dropdown>a:after{border:inset 5px;content:"";display:block;height:0;width:0;border-color:transparent transparent transparent rgba(255,255,255,0.4);border-left-style:solid;margin-right:0.9375rem;margin-top:-4.5px;position:absolute;top:50%;right:0}.top-bar-section .has-dropdown.moved{position:static}.top-bar-section .has-dropdown.moved>.dropdown{position:static !important;height:auto;width:auto;overflow:visible;clip:auto;display:block;position:absolute !important;width:100%}.top-bar-section .has-dropdown.moved>a:after{display:none}.top-bar-section .dropdown{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px;display:block;padding:0;position:absolute;top:0;z-index:99;left:100%}.top-bar-section .dropdown li{height:auto;width:100%}.top-bar-section .dropdown li a{font-weight:normal;padding:8px 0.9375rem}.top-bar-section .dropdown li a.parent-link{font-weight:normal}.top-bar-section .dropdown li.title h5,.top-bar-section .dropdown li.parent-link{margin-bottom:0;margin-top:0;font-size:1.125rem}.top-bar-section .dropdown li.title h5 a,.top-bar-section .dropdown li.parent-link a{color:#fff;display:block}.top-bar-section .dropdown li.title h5 a:hover,.top-bar-section .dropdown li.parent-link a:hover{background:none}.top-bar-section .dropdown li.has-form{padding:8px 0.9375rem}.top-bar-section .dropdown li .button,.top-bar-section .dropdown li button{top:auto}.top-bar-section .dropdown label{color:#777;font-size:0.625rem;font-weight:bold;margin-bottom:0;padding:8px 0.9375rem 2px;text-transform:uppercase}.js-generated{display:block}@media only screen and (min-width: 40.0625em){.top-bar{background:#333;overflow:visible}.top-bar:before,.top-bar:after{content:" ";display:table}.top-bar:after{clear:both}.top-bar .toggle-topbar{display:none}.top-bar .title-area{float:left}.top-bar .name h1 a,.top-bar .name h2 a,.top-bar .name h3 a,.top-bar .name h4 a,.top-bar .name h5 a,.top-bar .name h6 a{width:auto}.top-bar input,.top-bar select,.top-bar .button,.top-bar button{font-size:0.875rem;height:1.75rem;position:relative;top:0.53125rem}.top-bar.expanded{background:#333}.contain-to-grid .top-bar{margin-bottom:0;margin:0 auto;max-width:62.5rem}.top-bar-section{transition:none 0 0;left:0 !important}.top-bar-section ul{display:inline;height:auto !important;width:auto}.top-bar-section ul li{float:left}.top-bar-section ul li .js-generated{display:none}.top-bar-section li.hover>a:not(.button){background-color:#555;background:#222;color:#fff}.top-bar-section li:not(.has-form) a:not(.button){background:#333;line-height:2.8125rem;padding:0 0.9375rem}.top-bar-section li:not(.has-form) a:not(.button):hover{background-color:#555;background:#222}.top-bar-section li.active:not(.has-form) a:not(.button){background:#008CBA;color:#fff;line-height:2.8125rem;padding:0 0.9375rem}.top-bar-section li.active:not(.has-form) a:not(.button):hover{background:#0078a0;color:#fff}.top-bar-section .has-dropdown>a{padding-right:2.1875rem !important}.top-bar-section .has-dropdown>a:after{border:inset 5px;content:"";display:block;height:0;width:0;border-color:rgba(255,255,255,0.4) transparent transparent transparent;border-top-style:solid;margin-top:-2.5px;top:1.40625rem}.top-bar-section .has-dropdown.moved{position:relative}.top-bar-section .has-dropdown.moved>.dropdown{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px;display:block}.top-bar-section .has-dropdown.hover>.dropdown,.top-bar-section .has-dropdown.not-click:hover>.dropdown{position:static !important;height:auto;width:auto;overflow:visible;clip:auto;display:block;position:absolute !important}.top-bar-section .has-dropdown>a:focus+.dropdown{position:static !important;height:auto;width:auto;overflow:visible;clip:auto;display:block;position:absolute !important}.top-bar-section .has-dropdown .dropdown li.has-dropdown>a:after{border:none;content:"\00bb";top:0.1875rem;right:5px}.top-bar-section .dropdown{left:0;background:transparent;min-width:100%;top:auto}.top-bar-section .dropdown li a{background:#333;color:#fff;line-height:2.8125rem;padding:12px 0.9375rem;white-space:nowrap}.top-bar-section .dropdown li:not(.has-form):not(.active)>a:not(.button){background:#333;color:#fff}.top-bar-section .dropdown li:not(.has-form):not(.active):hover>a:not(.button){background-color:#555;color:#fff;background:#222}.top-bar-section .dropdown li label{background:#333;white-space:nowrap}.top-bar-section .dropdown li .dropdown{left:100%;top:0}.top-bar-section>ul>.divider,.top-bar-section>ul>[role="separator"]{border-right:solid 1px #4e4e4e;border-bottom:none;border-top:none;clear:none;height:2.8125rem;width:0}.top-bar-section .has-form{background:#333;height:2.8125rem;padding:0 0.9375rem}.top-bar-section .right li .dropdown{left:auto;right:0}.top-bar-section .right li .dropdown li .dropdown{right:100%}.top-bar-section .left li .dropdown{right:auto;left:0}.top-bar-section .left li .dropdown li .dropdown{left:100%}.no-js .top-bar-section ul li:hover>a{background-color:#555;background:#222;color:#fff}.no-js .top-bar-section ul li:active>a{background:#008CBA;color:#fff}.no-js .top-bar-section .has-dropdown:hover>.dropdown{position:static !important;height:auto;width:auto;overflow:visible;clip:auto;display:block;position:absolute !important}.no-js .top-bar-section .has-dropdown>a:focus+.dropdown{position:static !important;height:auto;width:auto;overflow:visible;clip:auto;display:block;position:absolute !important}}.breadcrumbs{border-style:solid;border-width:1px;display:block;list-style:none;margin-left:0;overflow:hidden;padding:0.5625rem 0.875rem 0.5625rem;background-color:#f4f4f4;border-color:#dcdcdc;border-radius:3px}.breadcrumbs>*{color:#008CBA;float:left;font-size:0.6875rem;line-height:0.6875rem;margin:0;text-transform:uppercase}.breadcrumbs>*:hover a,.breadcrumbs>*:focus a{text-decoration:underline}.breadcrumbs>* a{color:#008CBA}.breadcrumbs>*.current{color:#333;cursor:default}.breadcrumbs>*.current a{color:#333;cursor:default}.breadcrumbs>*.current:hover,.breadcrumbs>*.current:hover a,.breadcrumbs>*.current:focus,.breadcrumbs>*.current:focus a{text-decoration:none}.breadcrumbs>*.unavailable{color:#999}.breadcrumbs>*.unavailable a{color:#999}.breadcrumbs>*.unavailable:hover,.breadcrumbs>*.unavailable:hover a,.breadcrumbs>*.unavailable:focus,.breadcrumbs>*.unavailable a:focus{color:#999;cursor:not-allowed;text-decoration:none}.breadcrumbs>*:before{color:#aaa;content:"/";margin:0 0.75rem;position:relative;top:1px}.breadcrumbs>*:first-child:before{content:" ";margin:0}[aria-label="breadcrumbs"] [aria-hidden="true"]:after{content:"/"}.alert-box{border-style:solid;border-width:1px;display:block;font-size:0.8125rem;font-weight:normal;margin-bottom:1.25rem;padding:0.875rem 1.5rem 0.875rem 0.875rem;position:relative;transition:opacity 300ms ease-out;background-color:#008CBA;border-color:#0078a0;color:#fff}.alert-box .close{right:0.25rem;background:inherit;color:#333;font-size:1.375rem;line-height:.9;margin-top:-0.6875rem;opacity:0.3;padding:0 6px 4px;position:absolute;top:50%}.alert-box .close:hover,.alert-box .close:focus{opacity:0.5}.alert-box.radius{border-radius:3px}.alert-box.round{border-radius:1000px}.alert-box.success{background-color:#43AC6A;border-color:#3a945b;color:#fff}.alert-box.alert{background-color:#f04124;border-color:#de2d0f;color:#fff}.alert-box.secondary{background-color:#e7e7e7;border-color:#c7c7c7;color:#4f4f4f}.alert-box.warning{background-color:#f08a24;border-color:#de770f;color:#fff}.alert-box.info{background-color:#a0d3e8;border-color:#74bfdd;color:#4f4f4f}.alert-box.alert-close{opacity:0}.inline-list{list-style:none;margin-left:-1.375rem;margin-right:0;margin:0 auto 1.0625rem auto;overflow:hidden;padding:0}.inline-list>li{display:block;float:left;list-style:none;margin-left:1.375rem}.inline-list>li>*{display:block}.button-group{list-style:none;margin:0;left:0}.button-group:before,.button-group:after{content:" ";display:table}.button-group:after{clear:both}.button-group.even-2 li{display:inline-block;margin:0 -2px;width:50%}.button-group.even-2 li>button,.button-group.even-2 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-2 li:first-child button,.button-group.even-2 li:first-child .button{border-left:0}.button-group.even-2 li button,.button-group.even-2 li .button{width:100%}.button-group.even-3 li{display:inline-block;margin:0 -2px;width:33.33333%}.button-group.even-3 li>button,.button-group.even-3 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-3 li:first-child button,.button-group.even-3 li:first-child .button{border-left:0}.button-group.even-3 li button,.button-group.even-3 li .button{width:100%}.button-group.even-4 li{display:inline-block;margin:0 -2px;width:25%}.button-group.even-4 li>button,.button-group.even-4 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-4 li:first-child button,.button-group.even-4 li:first-child .button{border-left:0}.button-group.even-4 li button,.button-group.even-4 li .button{width:100%}.button-group.even-5 li{display:inline-block;margin:0 -2px;width:20%}.button-group.even-5 li>button,.button-group.even-5 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-5 li:first-child button,.button-group.even-5 li:first-child .button{border-left:0}.button-group.even-5 li button,.button-group.even-5 li .button{width:100%}.button-group.even-6 li{display:inline-block;margin:0 -2px;width:16.66667%}.button-group.even-6 li>button,.button-group.even-6 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-6 li:first-child button,.button-group.even-6 li:first-child .button{border-left:0}.button-group.even-6 li button,.button-group.even-6 li .button{width:100%}.button-group.even-7 li{display:inline-block;margin:0 -2px;width:14.28571%}.button-group.even-7 li>button,.button-group.even-7 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-7 li:first-child button,.button-group.even-7 li:first-child .button{border-left:0}.button-group.even-7 li button,.button-group.even-7 li .button{width:100%}.button-group.even-8 li{display:inline-block;margin:0 -2px;width:12.5%}.button-group.even-8 li>button,.button-group.even-8 li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.even-8 li:first-child button,.button-group.even-8 li:first-child .button{border-left:0}.button-group.even-8 li button,.button-group.even-8 li .button{width:100%}.button-group>li{display:inline-block;margin:0 -2px}.button-group>li>button,.button-group>li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group>li:first-child button,.button-group>li:first-child .button{border-left:0}.button-group.stack>li{display:block;margin:0;float:none}.button-group.stack>li>button,.button-group.stack>li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.stack>li:first-child button,.button-group.stack>li:first-child .button{border-left:0}.button-group.stack>li>button,.button-group.stack>li .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.stack>li>button{width:100%}.button-group.stack>li:first-child button,.button-group.stack>li:first-child .button{border-top:0}.button-group.stack-for-small>li{display:inline-block;margin:0 -2px}.button-group.stack-for-small>li>button,.button-group.stack-for-small>li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.stack-for-small>li:first-child button,.button-group.stack-for-small>li:first-child .button{border-left:0}@media only screen and (max-width: 40em){.button-group.stack-for-small>li{display:block;margin:0}.button-group.stack-for-small>li>button,.button-group.stack-for-small>li .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.stack-for-small>li:first-child button,.button-group.stack-for-small>li:first-child .button{border-left:0}.button-group.stack-for-small>li>button,.button-group.stack-for-small>li .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.stack-for-small>li>button{width:100%}.button-group.stack-for-small>li:first-child button,.button-group.stack-for-small>li:first-child .button{border-top:0}}.button-group.radius>*{display:inline-block;margin:0 -2px}.button-group.radius>*>button,.button-group.radius>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.radius>*:first-child button,.button-group.radius>*:first-child .button{border-left:0}.button-group.radius>*,.button-group.radius>*>a,.button-group.radius>*>button,.button-group.radius>*>.button{border-radius:0}.button-group.radius>*:first-child,.button-group.radius>*:first-child>a,.button-group.radius>*:first-child>button,.button-group.radius>*:first-child>.button{-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.button-group.radius>*:last-child,.button-group.radius>*:last-child>a,.button-group.radius>*:last-child>button,.button-group.radius>*:last-child>.button{-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}.button-group.radius.stack>*{display:block;margin:0}.button-group.radius.stack>*>button,.button-group.radius.stack>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.radius.stack>*:first-child button,.button-group.radius.stack>*:first-child .button{border-left:0}.button-group.radius.stack>*>button,.button-group.radius.stack>* .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.radius.stack>*>button{width:100%}.button-group.radius.stack>*:first-child button,.button-group.radius.stack>*:first-child .button{border-top:0}.button-group.radius.stack>*,.button-group.radius.stack>*>a,.button-group.radius.stack>*>button,.button-group.radius.stack>*>.button{border-radius:0}.button-group.radius.stack>*:first-child,.button-group.radius.stack>*:first-child>a,.button-group.radius.stack>*:first-child>button,.button-group.radius.stack>*:first-child>.button{-webkit-top-left-radius:3px;-webkit-top-right-radius:3px;border-top-left-radius:3px;border-top-right-radius:3px}.button-group.radius.stack>*:last-child,.button-group.radius.stack>*:last-child>a,.button-group.radius.stack>*:last-child>button,.button-group.radius.stack>*:last-child>.button{-webkit-bottom-left-radius:3px;-webkit-bottom-right-radius:3px;border-bottom-left-radius:3px;border-bottom-right-radius:3px}@media only screen and (min-width: 40.0625em){.button-group.radius.stack-for-small>*{display:inline-block;margin:0 -2px}.button-group.radius.stack-for-small>*>button,.button-group.radius.stack-for-small>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.radius.stack-for-small>*:first-child button,.button-group.radius.stack-for-small>*:first-child .button{border-left:0}.button-group.radius.stack-for-small>*,.button-group.radius.stack-for-small>*>a,.button-group.radius.stack-for-small>*>button,.button-group.radius.stack-for-small>*>.button{border-radius:0}.button-group.radius.stack-for-small>*:first-child,.button-group.radius.stack-for-small>*:first-child>a,.button-group.radius.stack-for-small>*:first-child>button,.button-group.radius.stack-for-small>*:first-child>.button{-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.button-group.radius.stack-for-small>*:last-child,.button-group.radius.stack-for-small>*:last-child>a,.button-group.radius.stack-for-small>*:last-child>button,.button-group.radius.stack-for-small>*:last-child>.button{-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}}@media only screen and (max-width: 40em){.button-group.radius.stack-for-small>*{display:block;margin:0}.button-group.radius.stack-for-small>*>button,.button-group.radius.stack-for-small>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.radius.stack-for-small>*:first-child button,.button-group.radius.stack-for-small>*:first-child .button{border-left:0}.button-group.radius.stack-for-small>*>button,.button-group.radius.stack-for-small>* .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.radius.stack-for-small>*>button{width:100%}.button-group.radius.stack-for-small>*:first-child button,.button-group.radius.stack-for-small>*:first-child .button{border-top:0}.button-group.radius.stack-for-small>*,.button-group.radius.stack-for-small>*>a,.button-group.radius.stack-for-small>*>button,.button-group.radius.stack-for-small>*>.button{border-radius:0}.button-group.radius.stack-for-small>*:first-child,.button-group.radius.stack-for-small>*:first-child>a,.button-group.radius.stack-for-small>*:first-child>button,.button-group.radius.stack-for-small>*:first-child>.button{-webkit-top-left-radius:3px;-webkit-top-right-radius:3px;border-top-left-radius:3px;border-top-right-radius:3px}.button-group.radius.stack-for-small>*:last-child,.button-group.radius.stack-for-small>*:last-child>a,.button-group.radius.stack-for-small>*:last-child>button,.button-group.radius.stack-for-small>*:last-child>.button{-webkit-bottom-left-radius:3px;-webkit-bottom-right-radius:3px;border-bottom-left-radius:3px;border-bottom-right-radius:3px}}.button-group.round>*{display:inline-block;margin:0 -2px}.button-group.round>*>button,.button-group.round>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.round>*:first-child button,.button-group.round>*:first-child .button{border-left:0}.button-group.round>*,.button-group.round>*>a,.button-group.round>*>button,.button-group.round>*>.button{border-radius:0}.button-group.round>*:first-child,.button-group.round>*:first-child>a,.button-group.round>*:first-child>button,.button-group.round>*:first-child>.button{-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.button-group.round>*:last-child,.button-group.round>*:last-child>a,.button-group.round>*:last-child>button,.button-group.round>*:last-child>.button{-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}.button-group.round.stack>*{display:block;margin:0}.button-group.round.stack>*>button,.button-group.round.stack>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.round.stack>*:first-child button,.button-group.round.stack>*:first-child .button{border-left:0}.button-group.round.stack>*>button,.button-group.round.stack>* .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.round.stack>*>button{width:100%}.button-group.round.stack>*:first-child button,.button-group.round.stack>*:first-child .button{border-top:0}.button-group.round.stack>*,.button-group.round.stack>*>a,.button-group.round.stack>*>button,.button-group.round.stack>*>.button{border-radius:0}.button-group.round.stack>*:first-child,.button-group.round.stack>*:first-child>a,.button-group.round.stack>*:first-child>button,.button-group.round.stack>*:first-child>.button{-webkit-top-left-radius:1rem;-webkit-top-right-radius:1rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.button-group.round.stack>*:last-child,.button-group.round.stack>*:last-child>a,.button-group.round.stack>*:last-child>button,.button-group.round.stack>*:last-child>.button{-webkit-bottom-left-radius:1rem;-webkit-bottom-right-radius:1rem;border-bottom-left-radius:1rem;border-bottom-right-radius:1rem}@media only screen and (min-width: 40.0625em){.button-group.round.stack-for-small>*{display:inline-block;margin:0 -2px}.button-group.round.stack-for-small>*>button,.button-group.round.stack-for-small>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.round.stack-for-small>*:first-child button,.button-group.round.stack-for-small>*:first-child .button{border-left:0}.button-group.round.stack-for-small>*,.button-group.round.stack-for-small>*>a,.button-group.round.stack-for-small>*>button,.button-group.round.stack-for-small>*>.button{border-radius:0}.button-group.round.stack-for-small>*:first-child,.button-group.round.stack-for-small>*:first-child>a,.button-group.round.stack-for-small>*:first-child>button,.button-group.round.stack-for-small>*:first-child>.button{-webkit-border-bottom-left-radius:1000px;-webkit-border-top-left-radius:1000px;border-bottom-left-radius:1000px;border-top-left-radius:1000px}.button-group.round.stack-for-small>*:last-child,.button-group.round.stack-for-small>*:last-child>a,.button-group.round.stack-for-small>*:last-child>button,.button-group.round.stack-for-small>*:last-child>.button{-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}}@media only screen and (max-width: 40em){.button-group.round.stack-for-small>*{display:block;margin:0}.button-group.round.stack-for-small>*>button,.button-group.round.stack-for-small>* .button{border-left:1px solid;border-color:rgba(255,255,255,0.5)}.button-group.round.stack-for-small>*:first-child button,.button-group.round.stack-for-small>*:first-child .button{border-left:0}.button-group.round.stack-for-small>*>button,.button-group.round.stack-for-small>* .button{border-color:rgba(255,255,255,0.5);border-left-width:0;border-top:1px solid;display:block;margin:0}.button-group.round.stack-for-small>*>button{width:100%}.button-group.round.stack-for-small>*:first-child button,.button-group.round.stack-for-small>*:first-child .button{border-top:0}.button-group.round.stack-for-small>*,.button-group.round.stack-for-small>*>a,.button-group.round.stack-for-small>*>button,.button-group.round.stack-for-small>*>.button{border-radius:0}.button-group.round.stack-for-small>*:first-child,.button-group.round.stack-for-small>*:first-child>a,.button-group.round.stack-for-small>*:first-child>button,.button-group.round.stack-for-small>*:first-child>.button{-webkit-top-left-radius:1rem;-webkit-top-right-radius:1rem;border-top-left-radius:1rem;border-top-right-radius:1rem}.button-group.round.stack-for-small>*:last-child,.button-group.round.stack-for-small>*:last-child>a,.button-group.round.stack-for-small>*:last-child>button,.button-group.round.stack-for-small>*:last-child>.button{-webkit-bottom-left-radius:1rem;-webkit-bottom-right-radius:1rem;border-bottom-left-radius:1rem;border-bottom-right-radius:1rem}}.button-bar:before,.button-bar:after{content:" ";display:table}.button-bar:after{clear:both}.button-bar .button-group{float:left;margin-right:0.625rem}.button-bar .button-group div{overflow:hidden}.panel{border-style:solid;border-width:1px;border-color:#d8d8d8;margin-bottom:1.25rem;padding:1.25rem;background:#f2f2f2;color:#333}.panel>:first-child{margin-top:0}.panel>:last-child{margin-bottom:0}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6,.panel p,.panel li,.panel dl{color:#333}.panel h1,.panel h2,.panel h3,.panel h4,.panel h5,.panel h6{line-height:1;margin-bottom:0.625rem}.panel h1.subheader,.panel h2.subheader,.panel h3.subheader,.panel h4.subheader,.panel h5.subheader,.panel h6.subheader{line-height:1.4}.panel.callout{border-style:solid;border-width:1px;border-color:#d8d8d8;margin-bottom:1.25rem;padding:1.25rem;background:#ecfaff;color:#333}.panel.callout>:first-child{margin-top:0}.panel.callout>:last-child{margin-bottom:0}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6,.panel.callout p,.panel.callout li,.panel.callout dl{color:#333}.panel.callout h1,.panel.callout h2,.panel.callout h3,.panel.callout h4,.panel.callout h5,.panel.callout h6{line-height:1;margin-bottom:0.625rem}.panel.callout h1.subheader,.panel.callout h2.subheader,.panel.callout h3.subheader,.panel.callout h4.subheader,.panel.callout h5.subheader,.panel.callout h6.subheader{line-height:1.4}.panel.callout a:not(.button){color:#008CBA}.panel.callout a:not(.button):hover,.panel.callout a:not(.button):focus{color:#0078a0}.panel.radius{border-radius:3px}.dropdown.button,button.dropdown{position:relative;padding-right:3.5625rem}.dropdown.button::after,button.dropdown::after{border-color:#fff transparent transparent transparent;border-style:solid;content:"";display:block;height:0;position:absolute;top:50%;width:0}.dropdown.button::after,button.dropdown::after{border-width:0.375rem;right:1.40625rem;margin-top:-0.15625rem}.dropdown.button::after,button.dropdown::after{border-color:#fff transparent transparent transparent}.dropdown.button.tiny,button.dropdown.tiny{padding-right:2.625rem}.dropdown.button.tiny:after,button.dropdown.tiny:after{border-width:0.375rem;right:1.125rem;margin-top:-0.125rem}.dropdown.button.tiny::after,button.dropdown.tiny::after{border-color:#fff transparent transparent transparent}.dropdown.button.small,button.dropdown.small{padding-right:3.0625rem}.dropdown.button.small::after,button.dropdown.small::after{border-width:0.4375rem;right:1.3125rem;margin-top:-0.15625rem}.dropdown.button.small::after,button.dropdown.small::after{border-color:#fff transparent transparent transparent}.dropdown.button.large,button.dropdown.large{padding-right:3.625rem}.dropdown.button.large::after,button.dropdown.large::after{border-width:0.3125rem;right:1.71875rem;margin-top:-0.15625rem}.dropdown.button.large::after,button.dropdown.large::after{border-color:#fff transparent transparent transparent}.dropdown.button.secondary:after,button.dropdown.secondary:after{border-color:#333 transparent transparent transparent}.th{border:solid 4px #fff;box-shadow:0 0 0 1px rgba(0,0,0,0.2);display:inline-block;line-height:0;max-width:100%;transition:all 200ms ease-out}.th:hover,.th:focus{box-shadow:0 0 6px 1px rgba(0,140,186,0.5)}.th.radius{border-radius:3px}.pricing-table{border:solid 1px #ddd;margin-left:0;margin-bottom:1.25rem}.pricing-table *{list-style:none;line-height:1}.pricing-table .title{background-color:#333;color:#eee;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:1rem;font-weight:normal;padding:0.9375rem 1.25rem;text-align:center}.pricing-table .price{background-color:#F6F6F6;color:#333;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:2rem;font-weight:normal;padding:0.9375rem 1.25rem;text-align:center}.pricing-table .description{background-color:#fff;border-bottom:dotted 1px #ddd;color:#777;font-size:0.75rem;font-weight:normal;line-height:1.4;padding:0.9375rem;text-align:center}.pricing-table .bullet-item{background-color:#fff;border-bottom:dotted 1px #ddd;color:#333;font-size:0.875rem;font-weight:normal;padding:0.9375rem;text-align:center}.pricing-table .cta-button{background-color:#fff;padding:1.25rem 1.25rem 0;text-align:center}@-webkit-keyframes rotate{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{from{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-ms-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}.slideshow-wrapper{position:relative}.slideshow-wrapper ul{list-style-type:none;margin:0}.slideshow-wrapper ul li,.slideshow-wrapper ul li .orbit-caption{display:none}.slideshow-wrapper ul li:first-child{display:block}.slideshow-wrapper .orbit-container{background-color:transparent}.slideshow-wrapper .orbit-container li{display:block}.slideshow-wrapper .orbit-container li .orbit-caption{display:block}.slideshow-wrapper .orbit-container .orbit-bullets li{display:inline-block}.slideshow-wrapper .preloader{border-radius:1000px;animation-duration:1.5s;animation-iteration-count:infinite;animation-name:rotate;animation-timing-function:linear;border-color:#555 #fff;border:solid 3px;display:block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px}.orbit-container{background:none;overflow:hidden;position:relative;width:100%}.orbit-container .orbit-slides-container{list-style:none;margin:0;padding:0;position:relative;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.orbit-container .orbit-slides-container img{display:block;max-width:100%}.orbit-container .orbit-slides-container>*{position:absolute;top:0;width:100%;margin-left:100%}.orbit-container .orbit-slides-container>*:first-child{margin-left:0}.orbit-container .orbit-slides-container>* .orbit-caption{bottom:0;position:absolute;background-color:rgba(51,51,51,0.8);color:#fff;font-size:0.875rem;padding:0.625rem 0.875rem;width:100%}.orbit-container .orbit-slide-number{left:10px;background:transparent;color:#fff;font-size:12px;position:absolute;top:10px;z-index:10}.orbit-container .orbit-slide-number span{font-weight:700;padding:0.3125rem}.orbit-container .orbit-timer{position:absolute;top:12px;right:10px;height:6px;width:100px;z-index:10}.orbit-container .orbit-timer .orbit-progress{height:3px;background-color:rgba(255,255,255,0.3);display:block;width:0;position:relative;right:20px;top:5px}.orbit-container .orbit-timer>span{border:solid 4px #fff;border-bottom:none;border-top:none;display:none;height:14px;position:absolute;top:0;width:11px;right:0}.orbit-container .orbit-timer.paused>span{top:0;width:11px;height:14px;border:inset 8px;border-left-style:solid;border-color:transparent;border-left-color:#fff;right:-4px}.orbit-container .orbit-timer.paused>span.dark{border-left-color:#333}.orbit-container:hover .orbit-timer>span{display:block}.orbit-container .orbit-prev,.orbit-container .orbit-next{background-color:transparent;color:white;height:60px;line-height:50px;margin-top:-25px;position:absolute;text-indent:-9999px !important;top:45%;width:36px;z-index:10}.orbit-container .orbit-prev:hover,.orbit-container .orbit-next:hover{background-color:rgba(0,0,0,0.3)}.orbit-container .orbit-prev>span,.orbit-container .orbit-next>span{border:inset 10px;display:block;height:0;margin-top:-10px;position:absolute;top:50%;width:0}.orbit-container .orbit-prev{left:0}.orbit-container .orbit-prev>span{border-right-style:solid;border-color:transparent;border-right-color:#fff}.orbit-container .orbit-prev:hover>span{border-right-color:#fff}.orbit-container .orbit-next{right:0}.orbit-container .orbit-next>span{border-color:transparent;border-left-style:solid;border-left-color:#fff;left:50%;margin-left:-4px}.orbit-container .orbit-next:hover>span{border-left-color:#fff}.orbit-bullets-container{text-align:center}.orbit-bullets{display:block;float:none;margin:0 auto 30px auto;overflow:hidden;position:relative;text-align:center;top:10px}.orbit-bullets li{background:#ccc;cursor:pointer;display:inline-block;float:none;height:0.5625rem;margin-right:6px;width:0.5625rem;border-radius:1000px}.orbit-bullets li.active{background:#999}.orbit-bullets li:last-child{margin-right:0}.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:none}.touch .orbit-bullets{display:none}@media only screen and (min-width: 40.0625em){.touch .orbit-container .orbit-prev,.touch .orbit-container .orbit-next{display:inherit}.touch .orbit-bullets{display:block}}@media only screen and (max-width: 40em){.orbit-stack-on-small .orbit-slides-container{height:auto !important}.orbit-stack-on-small .orbit-slides-container>*{margin:0 !important;opacity:1 !important;position:relative}.orbit-stack-on-small .orbit-slide-number{display:none}.orbit-timer{display:none}.orbit-next,.orbit-prev{display:none}.orbit-bullets{display:none}}[data-magellan-expedition],[data-magellan-expedition-clone]{background:#fff;min-width:100%;padding:10px;z-index:50}[data-magellan-expedition] .sub-nav,[data-magellan-expedition-clone] .sub-nav{margin-bottom:0}[data-magellan-expedition] .sub-nav dd,[data-magellan-expedition-clone] .sub-nav dd{margin-bottom:0}[data-magellan-expedition] .sub-nav a,[data-magellan-expedition-clone] .sub-nav a{line-height:1.8em}.icon-bar{display:inline-block;font-size:0;width:100%;background:#333}.icon-bar>*{display:block;float:left;font-size:1rem;margin:0 auto;padding:1.25rem;text-align:center;width:25%}.icon-bar>* i,.icon-bar>* img{display:block;margin:0 auto}.icon-bar>* i+label,.icon-bar>* img+label{margin-top:.0625rem}.icon-bar>* i{font-size:1.875rem;vertical-align:middle}.icon-bar>* img{height:1.875rem;width:1.875rem}.icon-bar.label-right>* i,.icon-bar.label-right>* img{display:inline-block;margin:0 .0625rem 0 0}.icon-bar.label-right>* i+label,.icon-bar.label-right>* img+label{margin-top:0}.icon-bar.label-right>* label{display:inline-block}.icon-bar.vertical.label-right>*{text-align:left}.icon-bar.vertical,.icon-bar.small-vertical{height:100%;width:auto}.icon-bar.vertical .item,.icon-bar.small-vertical .item{float:none;margin:auto;width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.medium-vertical{height:100%;width:auto}.icon-bar.medium-vertical .item{float:none;margin:auto;width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.large-vertical{height:100%;width:auto}.icon-bar.large-vertical .item{float:none;margin:auto;width:auto}}.icon-bar>*{font-size:1rem;padding:1.25rem}.icon-bar>* i+label,.icon-bar>* img+label{margin-top:.0625rem;font-size:1rem}.icon-bar>* i{font-size:1.875rem}.icon-bar>* img{height:1.875rem;width:1.875rem}.icon-bar>* label{color:#fff}.icon-bar>* i{color:#fff}.icon-bar>a:hover{background:#008CBA}.icon-bar>a:hover label{color:#fff}.icon-bar>a:hover i{color:#fff}.icon-bar>a.active{background:#008CBA}.icon-bar>a.active label{color:#fff}.icon-bar>a.active i{color:#fff}.icon-bar .item.disabled{cursor:not-allowed;opacity:0.7;pointer-events:none}.icon-bar .item.disabled>*{opacity:0.7;cursor:not-allowed}.icon-bar.two-up .item{width:50%}.icon-bar.two-up.vertical .item,.icon-bar.two-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.two-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.two-up.large-vertical .item{width:auto}}.icon-bar.three-up .item{width:33.3333%}.icon-bar.three-up.vertical .item,.icon-bar.three-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.three-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.three-up.large-vertical .item{width:auto}}.icon-bar.four-up .item{width:25%}.icon-bar.four-up.vertical .item,.icon-bar.four-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.four-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.four-up.large-vertical .item{width:auto}}.icon-bar.five-up .item{width:20%}.icon-bar.five-up.vertical .item,.icon-bar.five-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.five-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.five-up.large-vertical .item{width:auto}}.icon-bar.six-up .item{width:16.66667%}.icon-bar.six-up.vertical .item,.icon-bar.six-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.six-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.six-up.large-vertical .item{width:auto}}.icon-bar.seven-up .item{width:14.28571%}.icon-bar.seven-up.vertical .item,.icon-bar.seven-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.seven-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.seven-up.large-vertical .item{width:auto}}.icon-bar.eight-up .item{width:12.5%}.icon-bar.eight-up.vertical .item,.icon-bar.eight-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.eight-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.eight-up.large-vertical .item{width:auto}}.icon-bar.two-up .item{width:50%}.icon-bar.two-up.vertical .item,.icon-bar.two-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.two-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.two-up.large-vertical .item{width:auto}}.icon-bar.three-up .item{width:33.3333%}.icon-bar.three-up.vertical .item,.icon-bar.three-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.three-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.three-up.large-vertical .item{width:auto}}.icon-bar.four-up .item{width:25%}.icon-bar.four-up.vertical .item,.icon-bar.four-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.four-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.four-up.large-vertical .item{width:auto}}.icon-bar.five-up .item{width:20%}.icon-bar.five-up.vertical .item,.icon-bar.five-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.five-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.five-up.large-vertical .item{width:auto}}.icon-bar.six-up .item{width:16.66667%}.icon-bar.six-up.vertical .item,.icon-bar.six-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.six-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.six-up.large-vertical .item{width:auto}}.icon-bar.seven-up .item{width:14.28571%}.icon-bar.seven-up.vertical .item,.icon-bar.seven-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.seven-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.seven-up.large-vertical .item{width:auto}}.icon-bar.eight-up .item{width:12.5%}.icon-bar.eight-up.vertical .item,.icon-bar.eight-up.small-vertical .item{width:auto}@media only screen and (min-width: 40.0625em){.icon-bar.eight-up.medium-vertical .item{width:auto}}@media only screen and (min-width: 64.0625em){.icon-bar.eight-up.large-vertical .item{width:auto}}.tabs{margin-bottom:0 !important;margin-left:0}.tabs:before,.tabs:after{content:" ";display:table}.tabs:after{clear:both}.tabs dd,.tabs .tab-title{float:left;list-style:none;margin-bottom:0 !important;position:relative}.tabs dd>a,.tabs .tab-title>a{display:block;background-color:#EFEFEF;color:#222;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:1rem;padding:1rem 2rem}.tabs dd>a:hover,.tabs .tab-title>a:hover{background-color:#e1e1e1}.tabs dd.active a,.tabs .tab-title.active a{background-color:#fff;color:#222}.tabs.radius dd:first-child a,.tabs.radius .tab:first-child a{-webkit-border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-bottom-left-radius:3px;border-top-left-radius:3px}.tabs.radius dd:last-child a,.tabs.radius .tab:last-child a{-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}.tabs.vertical dd,.tabs.vertical .tab-title{position:inherit;float:none;display:block;top:auto}.tabs-content{margin-bottom:1.5rem;width:100%}.tabs-content:before,.tabs-content:after{content:" ";display:table}.tabs-content:after{clear:both}.tabs-content>.content{display:none;float:left;padding:0.9375rem 0;width:100%}.tabs-content>.content.active{display:block;float:none}.tabs-content>.content.contained{padding:0.9375rem}.tabs-content.vertical{display:block}.tabs-content.vertical>.content{padding:0 0.9375rem}@media only screen and (min-width: 40.0625em){.tabs.vertical{float:left;margin:0;margin-bottom:1.25rem !important;max-width:20%;width:20%}.tabs-content.vertical{float:left;margin-left:-1px;max-width:80%;padding-left:1rem;width:80%}}.no-js .tabs-content>.content{display:block;float:none}ul.pagination{display:block;margin-left:-0.3125rem;min-height:1.5rem}ul.pagination li{color:#222;font-size:0.875rem;height:1.5rem;margin-left:0.3125rem}ul.pagination li a,ul.pagination li button{border-radius:3px;transition:background-color 300ms ease-out;background:none;color:#999;display:block;font-size:1em;font-weight:normal;line-height:inherit;padding:0.0625rem 0.625rem 0.0625rem}ul.pagination li:hover a,ul.pagination li a:focus,ul.pagination li:hover button,ul.pagination li button:focus{background:#e6e6e6}ul.pagination li.unavailable a,ul.pagination li.unavailable button{cursor:default;color:#999}ul.pagination li.unavailable:hover a,ul.pagination li.unavailable a:focus,ul.pagination li.unavailable:hover button,ul.pagination li.unavailable button:focus{background:transparent}ul.pagination li.current a,ul.pagination li.current button{background:#008CBA;color:#fff;cursor:default;font-weight:bold}ul.pagination li.current a:hover,ul.pagination li.current a:focus,ul.pagination li.current button:hover,ul.pagination li.current button:focus{background:#008CBA}ul.pagination li{display:block;float:left}.pagination-centered{text-align:center}.pagination-centered ul.pagination li{display:inline-block;float:none}.side-nav{display:block;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;list-style-position:outside;list-style-type:none;margin:0;padding:0.875rem 0}.side-nav li{font-size:0.875rem;font-weight:normal;margin:0 0 0.4375rem 0}.side-nav li a:not(.button){color:#008CBA;display:block;margin:0;padding:0.4375rem 0.875rem}.side-nav li a:not(.button):hover,.side-nav li a:not(.button):focus{background:rgba(0,0,0,0.025);color:#1cc7ff}.side-nav li a:not(.button):active{color:#1cc7ff}.side-nav li.active>a:first-child:not(.button){color:#1cc7ff;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal}.side-nav li.divider{border-top:1px solid;height:0;list-style:none;padding:0;border-top-color:#e6e6e6}.side-nav li.heading{color:#008CBA;font-size:0.875rem;font-weight:bold;text-transform:uppercase}.accordion{margin-bottom:0}.accordion:before,.accordion:after{content:" ";display:table}.accordion:after{clear:both}.accordion .accordion-navigation,.accordion dd{display:block;margin-bottom:0 !important}.accordion .accordion-navigation.active>a,.accordion dd.active>a{background:#e8e8e8}.accordion .accordion-navigation>a,.accordion dd>a{background:#EFEFEF;color:#222;display:block;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:1rem;padding:1rem}.accordion .accordion-navigation>a:hover,.accordion dd>a:hover{background:#e3e3e3}.accordion .accordion-navigation>.content,.accordion dd>.content{display:none;padding:0.9375rem}.accordion .accordion-navigation>.content.active,.accordion dd>.content.active{background:#fff;display:block}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}.text-justify{text-align:justify !important}@media only screen and (max-width: 40em){.small-only-text-left{text-align:left !important}.small-only-text-right{text-align:right !important}.small-only-text-center{text-align:center !important}.small-only-text-justify{text-align:justify !important}}@media only screen{.small-text-left{text-align:left !important}.small-text-right{text-align:right !important}.small-text-center{text-align:center !important}.small-text-justify{text-align:justify !important}}@media only screen and (min-width: 40.0625em) and (max-width: 64em){.medium-only-text-left{text-align:left !important}.medium-only-text-right{text-align:right !important}.medium-only-text-center{text-align:center !important}.medium-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 40.0625em){.medium-text-left{text-align:left !important}.medium-text-right{text-align:right !important}.medium-text-center{text-align:center !important}.medium-text-justify{text-align:justify !important}}@media only screen and (min-width: 64.0625em) and (max-width: 90em){.large-only-text-left{text-align:left !important}.large-only-text-right{text-align:right !important}.large-only-text-center{text-align:center !important}.large-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 64.0625em){.large-text-left{text-align:left !important}.large-text-right{text-align:right !important}.large-text-center{text-align:center !important}.large-text-justify{text-align:justify !important}}@media only screen and (min-width: 90.0625em) and (max-width: 120em){.xlarge-only-text-left{text-align:left !important}.xlarge-only-text-right{text-align:right !important}.xlarge-only-text-center{text-align:center !important}.xlarge-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 90.0625em){.xlarge-text-left{text-align:left !important}.xlarge-text-right{text-align:right !important}.xlarge-text-center{text-align:center !important}.xlarge-text-justify{text-align:justify !important}}@media only screen and (min-width: 120.0625em) and (max-width: 6249999.9375em){.xxlarge-only-text-left{text-align:left !important}.xxlarge-only-text-right{text-align:right !important}.xxlarge-only-text-center{text-align:center !important}.xxlarge-only-text-justify{text-align:justify !important}}@media only screen and (min-width: 120.0625em){.xxlarge-text-left{text-align:left !important}.xxlarge-text-right{text-align:right !important}.xxlarge-text-center{text-align:center !important}.xxlarge-text-justify{text-align:justify !important}}div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}a{color:#008CBA;line-height:inherit;text-decoration:none}a:hover,a:focus{color:#0078a0}a img{border:none}p{font-family:inherit;font-size:1rem;font-weight:normal;line-height:1.6;margin-bottom:1.25rem;text-rendering:optimizeLegibility}p.lead{font-size:1.21875rem;line-height:1.6}p aside{font-size:0.875rem;font-style:italic;line-height:1.35}h1,h2,h3,h4,h5,h6{color:#222;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-style:normal;font-weight:normal;line-height:1.4;margin-bottom:0.5rem;margin-top:0.2rem;text-rendering:optimizeLegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#6f6f6f;font-size:60%;line-height:0}h1{font-size:2.125rem}h2{font-size:1.6875rem}h3{font-size:1.375rem}h4{font-size:1.125rem}h5{font-size:1.125rem}h6{font-size:1rem}.subheader{line-height:1.4;color:#6f6f6f;font-weight:normal;margin-top:0.2rem;margin-bottom:0.5rem}hr{border:solid #ddd;border-width:1px 0 0;clear:both;height:0;margin:1.25rem 0 1.1875rem}em,i{font-style:italic;line-height:inherit}strong,b{font-weight:bold;line-height:inherit}small{font-size:60%;line-height:inherit}code{background-color:#f8f8f8;border-color:#dfdfdf;border-style:solid;border-width:1px;color:#333;font-family:Consolas,"Liberation Mono",Courier,monospace;font-weight:normal;padding:0.125rem 0.3125rem 0.0625rem}ul,ol,dl{font-family:inherit;font-size:1rem;line-height:1.6;list-style-position:outside;margin-bottom:1.25rem}ul{margin-left:1.1rem}ul.no-bullet{margin-left:0}ul.no-bullet li ul,ul.no-bullet li ol{margin-left:1.25rem;margin-bottom:0;list-style:none}ul li ul,ul li ol{margin-left:1.25rem;margin-bottom:0}ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}ul.square{list-style-type:square;margin-left:1.1rem}ul.circle{list-style-type:circle;margin-left:1.1rem}ul.disc{list-style-type:disc;margin-left:1.1rem}ul.no-bullet{list-style:none}ol{margin-left:1.4rem}ol li ul,ol li ol{margin-left:1.25rem;margin-bottom:0}dl dt{margin-bottom:0.3rem;font-weight:bold}dl dd{margin-bottom:0.75rem}abbr,acronym{text-transform:uppercase;font-size:90%;color:#222;cursor:help}abbr{text-transform:none}abbr[title]{border-bottom:1px dotted #ddd}blockquote{margin:0 0 1.25rem;padding:0.5625rem 1.25rem 0 1.1875rem;border-left:1px solid #ddd}blockquote cite{display:block;font-size:0.8125rem;color:#555}blockquote cite:before{content:"\2014 \0020"}blockquote cite a,blockquote cite a:visited{color:#555}blockquote,blockquote p{line-height:1.6;color:#6f6f6f}.vcard{display:inline-block;margin:0 0 1.25rem 0;border:1px solid #ddd;padding:0.625rem 0.75rem}.vcard li{margin:0;display:block}.vcard .fn{font-weight:bold;font-size:0.9375rem}.vevent .summary{font-weight:bold}.vevent abbr{cursor:default;text-decoration:none;font-weight:bold;border:none;padding:0 0.0625rem}@media only screen and (min-width: 40.0625em){h1,h2,h3,h4,h5,h6{line-height:1.4}h1{font-size:2.75rem}h2{font-size:2.3125rem}h3{font-size:1.6875rem}h4{font-size:1.4375rem}h5{font-size:1.125rem}h6{font-size:1rem}}.split.button{position:relative;padding-right:5.0625rem}.split.button span{display:block;height:100%;position:absolute;right:0;top:0;border-left:solid 1px}.split.button span:after{position:absolute;content:"";width:0;height:0;display:block;border-style:inset;top:50%;left:50%}.split.button span:active{background-color:rgba(0,0,0,0.1)}.split.button span{border-left-color:rgba(255,255,255,0.5)}.split.button span{width:3.09375rem}.split.button span:after{border-top-style:solid;border-width:0.375rem;margin-left:-0.375rem;top:48%}.split.button span:after{border-color:#fff transparent transparent transparent}.split.button.secondary span{border-left-color:rgba(255,255,255,0.5)}.split.button.secondary span:after{border-color:#fff transparent transparent transparent}.split.button.alert span{border-left-color:rgba(255,255,255,0.5)}.split.button.success span{border-left-color:rgba(255,255,255,0.5)}.split.button.tiny{padding-right:3.75rem}.split.button.tiny span{width:2.25rem}.split.button.tiny span:after{border-top-style:solid;border-width:0.375rem;margin-left:-0.375rem;top:48%}.split.button.small{padding-right:4.375rem}.split.button.small span{width:2.625rem}.split.button.small span:after{border-top-style:solid;border-width:0.4375rem;margin-left:-0.375rem;top:48%}.split.button.large{padding-right:5.5rem}.split.button.large span{width:3.4375rem}.split.button.large span:after{border-top-style:solid;border-width:0.3125rem;margin-left:-0.375rem;top:48%}.split.button.expand{padding-left:2rem}.split.button.secondary span:after{border-color:#333 transparent transparent transparent}.split.button.radius span{-webkit-border-bottom-right-radius:3px;-webkit-border-top-right-radius:3px;border-bottom-right-radius:3px;border-top-right-radius:3px}.split.button.round span{-webkit-border-bottom-right-radius:1000px;-webkit-border-top-right-radius:1000px;border-bottom-right-radius:1000px;border-top-right-radius:1000px}.split.button.no-pip span:before{border-style:none}.split.button.no-pip span:after{border-style:none}.split.button.no-pip span>i{display:block;left:50%;margin-left:-0.28889em;margin-top:-0.48889em;position:absolute;top:50%}.reveal-modal-bg{background:#000;background:rgba(0,0,0,0.45);bottom:0;display:none;left:0;position:fixed;right:0;top:0;z-index:1004;left:0}.reveal-modal{border-radius:3px;display:none;position:absolute;top:0;visibility:hidden;width:100%;z-index:1005;left:0;background-color:#fff;padding:1.875rem;border:solid 1px #666;box-shadow:0 0 10px rgba(0,0,0,0.4)}@media only screen and (max-width: 40em){.reveal-modal{min-height:100vh}}.reveal-modal .column,.reveal-modal .columns{min-width:0}.reveal-modal>:first-child{margin-top:0}.reveal-modal>:last-child{margin-bottom:0}@media only screen and (min-width: 40.0625em){.reveal-modal{left:0;margin:0 auto;max-width:62.5rem;right:0;width:80%}}@media only screen and (min-width: 40.0625em){.reveal-modal{top:6.25rem}}.reveal-modal.radius{border-radius:3px}.reveal-modal.round{border-radius:1000px}.reveal-modal.collapse{padding:0}@media only screen and (min-width: 40.0625em){.reveal-modal.tiny{left:0;margin:0 auto;max-width:62.5rem;right:0;width:30%}}@media only screen and (min-width: 40.0625em){.reveal-modal.small{left:0;margin:0 auto;max-width:62.5rem;right:0;width:40%}}@media only screen and (min-width: 40.0625em){.reveal-modal.medium{left:0;margin:0 auto;max-width:62.5rem;right:0;width:60%}}@media only screen and (min-width: 40.0625em){.reveal-modal.large{left:0;margin:0 auto;max-width:62.5rem;right:0;width:70%}}@media only screen and (min-width: 40.0625em){.reveal-modal.xlarge{left:0;margin:0 auto;max-width:62.5rem;right:0;width:95%}}.reveal-modal.full{height:100vh;height:100%;left:0;margin-left:0 !important;max-width:none !important;min-height:100vh;top:0}@media only screen and (min-width: 40.0625em){.reveal-modal.full{left:0;margin:0 auto;max-width:62.5rem;right:0;width:100%}}.reveal-modal.toback{z-index:1003}.reveal-modal .close-reveal-modal{color:#aaa;cursor:pointer;font-size:2.5rem;font-weight:bold;line-height:1;position:absolute;top:0.625rem;right:1.375rem}.has-tip{border-bottom:dotted 1px #ccc;color:#333;cursor:help;font-weight:bold}.has-tip:hover,.has-tip:focus{border-bottom:dotted 1px #003f54;color:#008CBA}.has-tip.tip-left,.has-tip.tip-right{float:none !important}.tooltip{background:#333;color:#fff;display:none;font-size:0.875rem;font-weight:normal;line-height:1.3;max-width:300px;padding:0.75rem;position:absolute;width:100%;z-index:1006;left:50%}.tooltip>.nub{border-color:transparent transparent #333 transparent;border:solid 5px;display:block;height:0;pointer-events:none;position:absolute;top:-10px;width:0;left:5px}.tooltip>.nub.rtl{left:auto;right:5px}.tooltip.radius{border-radius:3px}.tooltip.round{border-radius:1000px}.tooltip.round>.nub{left:2rem}.tooltip.opened{border-bottom:dotted 1px #003f54 !important;color:#008CBA !important}.tap-to-close{color:#777;display:block;font-size:0.625rem;font-weight:normal}@media only screen and (min-width: 40.0625em){.tooltip>.nub{border-color:transparent transparent #333 transparent;top:-10px}.tooltip.tip-top>.nub{border-color:#333 transparent transparent transparent;bottom:-10px;top:auto}.tooltip.tip-left,.tooltip.tip-right{float:none !important}.tooltip.tip-left>.nub{border-color:transparent transparent transparent #333;left:auto;margin-top:-5px;right:-10px;top:50%}.tooltip.tip-right>.nub{border-color:transparent #333 transparent transparent;left:-10px;margin-top:-5px;right:auto;top:50%}}.clearing-thumbs,[data-clearing]{list-style:none;margin-left:0;margin-bottom:0}.clearing-thumbs:before,.clearing-thumbs:after,[data-clearing]:before,[data-clearing]:after{content:" ";display:table}.clearing-thumbs:after,[data-clearing]:after{clear:both}.clearing-thumbs li,[data-clearing] li{float:left;margin-right:10px}.clearing-thumbs[class*="block-grid-"] li,[data-clearing][class*="block-grid-"] li{margin-right:0}.clearing-blackout{background:#333;height:100%;position:fixed;top:0;width:100%;z-index:998;left:0}.clearing-blackout .clearing-close{display:block}.clearing-container{height:100%;margin:0;overflow:hidden;position:relative;z-index:998}.clearing-touch-label{color:#aaa;font-size:.6em;left:50%;position:absolute;top:50%}.visible-img{height:95%;position:relative}.visible-img img{position:absolute;left:50%;top:50%;-webkit-transform:translateY(-50%) translateX(-50%);-moz-transform:translateY(-50%) translateX(-50%);-ms-transform:translateY(-50%) translateX(-50%);-o-transform:translateY(-50%) translateX(-50%);transform:translateY(-50%) translateX(-50%);max-height:100%;max-width:100%}.clearing-caption{background:#333;bottom:0;color:#ccc;font-size:0.875em;line-height:1.3;margin-bottom:0;padding:10px 30px 20px;position:absolute;text-align:center;width:100%;left:0}.clearing-close{color:#ccc;display:none;font-size:30px;line-height:1;padding-left:20px;padding-top:10px;z-index:999}.clearing-close:hover,.clearing-close:focus{color:#ccc}.clearing-assembled .clearing-container{height:100%}.clearing-assembled .clearing-container .carousel>ul{display:none}.clearing-feature li{display:none}.clearing-feature li.clearing-featured-img{display:block}@media only screen and (min-width: 40.0625em){.clearing-main-prev,.clearing-main-next{height:100%;position:absolute;top:0;width:40px}.clearing-main-prev>span,.clearing-main-next>span{border:solid 12px;display:block;height:0;position:absolute;top:50%;width:0}.clearing-main-prev>span:hover,.clearing-main-next>span:hover{opacity:.8}.clearing-main-prev{left:0}.clearing-main-prev>span{left:5px;border-color:transparent;border-right-color:#ccc}.clearing-main-next{right:0}.clearing-main-next>span{border-color:transparent;border-left-color:#ccc}.clearing-main-prev.disabled,.clearing-main-next.disabled{opacity:.3}.clearing-assembled .clearing-container .carousel{background:rgba(51,51,51,0.8);height:120px;margin-top:10px;text-align:center}.clearing-assembled .clearing-container .carousel>ul{display:inline-block;z-index:999;height:100%;position:relative;float:none}.clearing-assembled .clearing-container .carousel>ul li{clear:none;cursor:pointer;display:block;float:left;margin-right:0;min-height:inherit;opacity:.4;overflow:hidden;padding:0;position:relative;width:120px}.clearing-assembled .clearing-container .carousel>ul li.fix-height img{height:100%;max-width:none}.clearing-assembled .clearing-container .carousel>ul li a.th{border:none;box-shadow:none;display:block}.clearing-assembled .clearing-container .carousel>ul li img{cursor:pointer !important;width:100% !important}.clearing-assembled .clearing-container .carousel>ul li.visible{opacity:1}.clearing-assembled .clearing-container .carousel>ul li:hover{opacity:.8}.clearing-assembled .clearing-container .visible-img{background:#333;height:85%;overflow:hidden}.clearing-close{padding-left:0;padding-top:0;position:absolute;top:10px;right:20px}}.progress{background-color:#F6F6F6;border:1px solid #fff;height:1.5625rem;margin-bottom:0.625rem;padding:0.125rem}.progress .meter{background:#008CBA;display:block;height:100%}.progress.secondary .meter{background:#e7e7e7;display:block;height:100%}.progress.success .meter{background:#43AC6A;display:block;height:100%}.progress.alert .meter{background:#f04124;display:block;height:100%}.progress.radius{border-radius:3px}.progress.radius .meter{border-radius:2px}.progress.round{border-radius:1000px}.progress.round .meter{border-radius:999px}.sub-nav{display:block;margin:-0.25rem 0 1.125rem;overflow:hidden;padding-top:0.25rem;width:auto}.sub-nav dt{text-transform:uppercase}.sub-nav dt,.sub-nav dd,.sub-nav li{color:#999;float:left;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-size:0.875rem;font-weight:normal;margin-left:1rem;margin-bottom:0}.sub-nav dt a,.sub-nav dd a,.sub-nav li a{color:#999;padding:0.1875rem 1rem;text-decoration:none}.sub-nav dt a:hover,.sub-nav dd a:hover,.sub-nav li a:hover{color:#737373}.sub-nav dt.active a,.sub-nav dd.active a,.sub-nav li.active a{border-radius:3px;background:#008CBA;color:#fff;cursor:default;font-weight:normal;padding:0.1875rem 1rem}.sub-nav dt.active a:hover,.sub-nav dd.active a:hover,.sub-nav li.active a:hover{background:#0078a0}.joyride-list{display:none}.joyride-tip-guide{background:#333;color:#fff;display:none;font-family:inherit;font-weight:normal;position:absolute;top:0;width:95%;z-index:101;left:2.5%}.lt-ie9 .joyride-tip-guide{margin-left:-400px;max-width:800px;left:50%}.joyride-content-wrapper{padding:1.125rem 1.25rem 1.5rem;width:100%}.joyride-content-wrapper .button{margin-bottom:0 !important}.joyride-content-wrapper .joyride-prev-tip{margin-right:10px}.joyride-tip-guide .joyride-nub{border:10px solid #333;display:block;height:0;position:absolute;width:0;left:22px}.joyride-tip-guide .joyride-nub.top{border-color:#333;border-top-color:transparent !important;border-top-style:solid;border-left-color:transparent !important;border-right-color:transparent !important;top:-20px}.joyride-tip-guide .joyride-nub.bottom{border-color:#333 !important;border-bottom-color:transparent !important;border-bottom-style:solid;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-20px}.joyride-tip-guide .joyride-nub.right{right:-20px}.joyride-tip-guide .joyride-nub.left{left:-20px}.joyride-tip-guide h1,.joyride-tip-guide h2,.joyride-tip-guide h3,.joyride-tip-guide h4,.joyride-tip-guide h5,.joyride-tip-guide h6{color:#fff;font-weight:bold;line-height:1.25;margin:0}.joyride-tip-guide p{font-size:0.875rem;line-height:1.3;margin:0 0 1.125rem 0}.joyride-timer-indicator-wrap{border:solid 1px #555;bottom:1rem;height:3px;position:absolute;width:50px;right:1.0625rem}.joyride-timer-indicator{background:#666;display:block;height:inherit;width:0}.joyride-close-tip{color:#777 !important;font-size:24px;font-weight:normal;line-height:.5 !important;position:absolute;text-decoration:none;top:10px;right:12px}.joyride-close-tip:hover,.joyride-close-tip:focus{color:#eee !important}.joyride-modal-bg{background:rgba(0,0,0,0.5);cursor:pointer;display:none;height:100%;position:fixed;top:0;width:100%;z-index:100;left:0}.joyride-expose-wrapper{background-color:#fff;border-radius:3px;box-shadow:0 0 15px #fff;position:absolute;z-index:102}.joyride-expose-cover{background:transparent;border-radius:3px;left:0;position:absolute;top:0;z-index:9999}@media only screen and (min-width: 40.0625em){.joyride-tip-guide{width:300px;left:inherit}.joyride-tip-guide .joyride-nub.bottom{border-color:#333 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-right-color:transparent !important;bottom:-20px}.joyride-tip-guide .joyride-nub.right{border-color:#333 !important;border-right-color:transparent !important;border-bottom-color:transparent !important;border-top-color:transparent !important;left:auto;right:-20px;top:22px}.joyride-tip-guide .joyride-nub.left{border-color:#333 !important;border-bottom-color:transparent !important;border-left-color:transparent !important;border-top-color:transparent !important;left:-20px;right:auto;top:22px}}.label{display:inline-block;font-family:"Helvetica Neue",Helvetica,Roboto,Arial,sans-serif;font-weight:normal;line-height:1;margin-bottom:auto;position:relative;text-align:center;text-decoration:none;white-space:nowrap;padding:0.25rem 0.5rem 0.25rem;font-size:0.6875rem;background-color:#008CBA;color:#fff}.label.radius{border-radius:3px}.label.round{border-radius:1000px}.label.alert{background-color:#f04124;color:#fff}.label.warning{background-color:#f08a24;color:#fff}.label.success{background-color:#43AC6A;color:#fff}.label.secondary{background-color:#e7e7e7;color:#333}.label.info{background-color:#a0d3e8;color:#333}.off-canvas-wrap{-webkit-backface-visibility:hidden;position:relative;width:100%;overflow:hidden}.off-canvas-wrap.move-right,.off-canvas-wrap.move-left{min-height:100%;-webkit-overflow-scrolling:touch}.inner-wrap{position:relative;width:100%;-webkit-transition:-webkit-transform 500ms ease;-moz-transition:-moz-transform 500ms ease;-ms-transition:-ms-transform 500ms ease;-o-transition:-o-transform 500ms ease;transition:transform 500ms ease}.inner-wrap:before,.inner-wrap:after{content:" ";display:table}.inner-wrap:after{clear:both}.tab-bar{-webkit-backface-visibility:hidden;background:#333;color:#fff;height:2.8125rem;line-height:2.8125rem;position:relative}.tab-bar h1,.tab-bar h2,.tab-bar h3,.tab-bar h4,.tab-bar h5,.tab-bar h6{color:#fff;font-weight:bold;line-height:2.8125rem;margin:0}.tab-bar h1,.tab-bar h2,.tab-bar h3,.tab-bar h4{font-size:1.125rem}.left-small{height:2.8125rem;position:absolute;top:0;width:2.8125rem;border-right:solid 1px #1a1a1a;left:0}.right-small{height:2.8125rem;position:absolute;top:0;width:2.8125rem;border-left:solid 1px #1a1a1a;right:0}.tab-bar-section{height:2.8125rem;padding:0 0.625rem;position:absolute;text-align:center;top:0}.tab-bar-section.left{text-align:left}.tab-bar-section.right{text-align:right}.tab-bar-section.left{left:0;right:2.8125rem}.tab-bar-section.right{left:2.8125rem;right:0}.tab-bar-section.middle{left:2.8125rem;right:2.8125rem}.tab-bar .menu-icon{color:#fff;display:block;height:2.8125rem;padding:0;position:relative;text-indent:2.1875rem;transform:translate3d(0, 0, 0);width:2.8125rem}.tab-bar .menu-icon span::after{content:"";display:block;height:0;position:absolute;top:50%;margin-top:-0.5rem;left:0.90625rem;box-shadow:0 0 0 1px #fff,0 7px 0 1px #fff,0 14px 0 1px #fff;width:1rem}.tab-bar .menu-icon span:hover:after{box-shadow:0 0 0 1px #b3b3b3,0 7px 0 1px #b3b3b3,0 14px 0 1px #b3b3b3}.left-off-canvas-menu{-webkit-backface-visibility:hidden;background:#333;bottom:0;box-sizing:content-box;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;overflow-x:hidden;overflow-y:auto;position:absolute;top:0;transition:transform 500ms ease 0s;width:15.625rem;z-index:1001;-webkit-transform:translate3d(-100%, 0, 0);-moz-transform:translate3d(-100%, 0, 0);-ms-transform:translate(-100%, 0);-ms-transform:translate3d(-100%, 0, 0);-o-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.left-off-canvas-menu *{-webkit-backface-visibility:hidden}.right-off-canvas-menu{-webkit-backface-visibility:hidden;background:#333;bottom:0;box-sizing:content-box;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;overflow-x:hidden;overflow-y:auto;position:absolute;top:0;transition:transform 500ms ease 0s;width:15.625rem;z-index:1001;-webkit-transform:translate3d(100%, 0, 0);-moz-transform:translate3d(100%, 0, 0);-ms-transform:translate(100%, 0);-ms-transform:translate3d(100%, 0, 0);-o-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);right:0}.right-off-canvas-menu *{-webkit-backface-visibility:hidden}ul.off-canvas-list{list-style-type:none;margin:0;padding:0}ul.off-canvas-list li label{background:#444;border-bottom:none;border-top:1px solid #5e5e5e;color:#999;display:block;font-size:0.75rem;font-weight:bold;margin:0;padding:0.3rem 0.9375rem;text-transform:uppercase}ul.off-canvas-list li a{border-bottom:1px solid #262626;color:rgba(255,255,255,0.7);display:block;padding:0.66667rem;transition:background 300ms ease}ul.off-canvas-list li a:hover{background:#242424}ul.off-canvas-list li a:active{background:#242424}.move-right>.inner-wrap{-webkit-transform:translate3d(15.625rem, 0, 0);-moz-transform:translate3d(15.625rem, 0, 0);-ms-transform:translate(15.625rem, 0);-ms-transform:translate3d(15.625rem, 0, 0);-o-transform:translate3d(15.625rem, 0, 0);transform:translate3d(15.625rem, 0, 0)}.move-right .exit-off-canvas{-webkit-backface-visibility:hidden;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);cursor:pointer;transition:background 300ms ease;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,0.2);bottom:0;display:block;left:0;position:absolute;right:0;top:0;z-index:1002}@media only screen and (min-width: 40.0625em){.move-right .exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.move-left>.inner-wrap{-webkit-transform:translate3d(-15.625rem, 0, 0);-moz-transform:translate3d(-15.625rem, 0, 0);-ms-transform:translate(-15.625rem, 0);-ms-transform:translate3d(-15.625rem, 0, 0);-o-transform:translate3d(-15.625rem, 0, 0);transform:translate3d(-15.625rem, 0, 0)}.move-left .exit-off-canvas{-webkit-backface-visibility:hidden;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);cursor:pointer;transition:background 300ms ease;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,0.2);bottom:0;display:block;left:0;position:absolute;right:0;top:0;z-index:1002}@media only screen and (min-width: 40.0625em){.move-left .exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.offcanvas-overlap .left-off-canvas-menu,.offcanvas-overlap .right-off-canvas-menu{-ms-transform:none;-webkit-transform:none;-moz-transform:none;-o-transform:none;transform:none;z-index:1003}.offcanvas-overlap .exit-off-canvas{-webkit-backface-visibility:hidden;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);cursor:pointer;transition:background 300ms ease;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,0.2);bottom:0;display:block;left:0;position:absolute;right:0;top:0;z-index:1002}@media only screen and (min-width: 40.0625em){.offcanvas-overlap .exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.offcanvas-overlap-left .right-off-canvas-menu{-ms-transform:none;-webkit-transform:none;-moz-transform:none;-o-transform:none;transform:none;z-index:1003}.offcanvas-overlap-left .exit-off-canvas{-webkit-backface-visibility:hidden;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);cursor:pointer;transition:background 300ms ease;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,0.2);bottom:0;display:block;left:0;position:absolute;right:0;top:0;z-index:1002}@media only screen and (min-width: 40.0625em){.offcanvas-overlap-left .exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.offcanvas-overlap-right .left-off-canvas-menu{-ms-transform:none;-webkit-transform:none;-moz-transform:none;-o-transform:none;transform:none;z-index:1003}.offcanvas-overlap-right .exit-off-canvas{-webkit-backface-visibility:hidden;box-shadow:-4px 0 4px rgba(0,0,0,0.5),4px 0 4px rgba(0,0,0,0.5);cursor:pointer;transition:background 300ms ease;-webkit-tap-highlight-color:transparent;background:rgba(255,255,255,0.2);bottom:0;display:block;left:0;position:absolute;right:0;top:0;z-index:1002}@media only screen and (min-width: 40.0625em){.offcanvas-overlap-right .exit-off-canvas:hover{background:rgba(255,255,255,0.05)}}.no-csstransforms .left-off-canvas-menu{left:-15.625rem}.no-csstransforms .right-off-canvas-menu{right:-15.625rem}.no-csstransforms .move-left>.inner-wrap{right:15.625rem}.no-csstransforms .move-right>.inner-wrap{left:15.625rem}.left-submenu{-webkit-backface-visibility:hidden;-webkit-overflow-scrolling:touch;background:#333;bottom:0;box-sizing:content-box;margin:0;overflow-x:hidden;overflow-y:auto;position:absolute;top:0;width:15.625rem;z-index:1002;-webkit-transform:translate3d(-100%, 0, 0);-moz-transform:translate3d(-100%, 0, 0);-ms-transform:translate(-100%, 0);-ms-transform:translate3d(-100%, 0, 0);-o-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0;-webkit-transition:-webkit-transform 500ms ease;-moz-transition:-moz-transform 500ms ease;-ms-transition:-ms-transform 500ms ease;-o-transition:-o-transform 500ms ease;transition:transform 500ms ease}.left-submenu *{-webkit-backface-visibility:hidden}.left-submenu .back>a{background:#444;border-bottom:none;border-top:1px solid #5e5e5e;color:#999;font-weight:bold;padding:0.3rem 0.9375rem;text-transform:uppercase;margin:0}.left-submenu .back>a:hover{background:#303030;border-bottom:none;border-top:1px solid #5e5e5e}.left-submenu .back>a:before{content:"\AB";margin-right:.5rem;display:inline}.left-submenu.move-right,.left-submenu.offcanvas-overlap-right,.left-submenu.offcanvas-overlap{-webkit-transform:translate3d(0%, 0, 0);-moz-transform:translate3d(0%, 0, 0);-ms-transform:translate(0%, 0);-ms-transform:translate3d(0%, 0, 0);-o-transform:translate3d(0%, 0, 0);transform:translate3d(0%, 0, 0)}.right-submenu{-webkit-backface-visibility:hidden;-webkit-overflow-scrolling:touch;background:#333;bottom:0;box-sizing:content-box;margin:0;overflow-x:hidden;overflow-y:auto;position:absolute;top:0;width:15.625rem;z-index:1002;-webkit-transform:translate3d(100%, 0, 0);-moz-transform:translate3d(100%, 0, 0);-ms-transform:translate(100%, 0);-ms-transform:translate3d(100%, 0, 0);-o-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);right:0;-webkit-transition:-webkit-transform 500ms ease;-moz-transition:-moz-transform 500ms ease;-ms-transition:-ms-transform 500ms ease;-o-transition:-o-transform 500ms ease;transition:transform 500ms ease}.right-submenu *{-webkit-backface-visibility:hidden}.right-submenu .back>a{background:#444;border-bottom:none;border-top:1px solid #5e5e5e;color:#999;font-weight:bold;padding:0.3rem 0.9375rem;text-transform:uppercase;margin:0}.right-submenu .back>a:hover{background:#303030;border-bottom:none;border-top:1px solid #5e5e5e}.right-submenu .back>a:after{content:"\BB";margin-left:.5rem;display:inline}.right-submenu.move-left,.right-submenu.offcanvas-overlap-left,.right-submenu.offcanvas-overlap{-webkit-transform:translate3d(0%, 0, 0);-moz-transform:translate3d(0%, 0, 0);-ms-transform:translate(0%, 0);-ms-transform:translate3d(0%, 0, 0);-o-transform:translate3d(0%, 0, 0);transform:translate3d(0%, 0, 0)}.left-off-canvas-menu ul.off-canvas-list li.has-submenu>a:after{content:"\BB";margin-left:.5rem;display:inline}.right-off-canvas-menu ul.off-canvas-list li.has-submenu>a:before{content:"\AB";margin-right:.5rem;display:inline}.f-dropdown{display:none;left:-9999px;list-style:none;margin-left:0;position:absolute;background:#fff;border:solid 1px #ccc;font-size:0.875rem;height:auto;max-height:none;width:100%;z-index:89;margin-top:2px;max-width:200px}.f-dropdown.open{display:block}.f-dropdown>*:first-child{margin-top:0}.f-dropdown>*:last-child{margin-bottom:0}.f-dropdown:before{border:inset 6px;content:"";display:block;height:0;width:0;border-color:transparent transparent #fff transparent;border-bottom-style:solid;position:absolute;top:-12px;left:10px;z-index:89}.f-dropdown:after{border:inset 7px;content:"";display:block;height:0;width:0;border-color:transparent transparent #ccc transparent;border-bottom-style:solid;position:absolute;top:-14px;left:9px;z-index:88}.f-dropdown.right:before{left:auto;right:10px}.f-dropdown.right:after{left:auto;right:9px}.f-dropdown.drop-right{display:none;left:-9999px;list-style:none;margin-left:0;position:absolute;background:#fff;border:solid 1px #ccc;font-size:0.875rem;height:auto;max-height:none;width:100%;z-index:89;margin-top:0;margin-left:2px;max-width:200px}.f-dropdown.drop-right.open{display:block}.f-dropdown.drop-right>*:first-child{margin-top:0}.f-dropdown.drop-right>*:last-child{margin-bottom:0}.f-dropdown.drop-right:before{border:inset 6px;content:"";display:block;height:0;width:0;border-color:transparent #fff transparent transparent;border-right-style:solid;position:absolute;top:10px;left:-12px;z-index:89}.f-dropdown.drop-right:after{border:inset 7px;content:"";display:block;height:0;width:0;border-color:transparent #ccc transparent transparent;border-right-style:solid;position:absolute;top:9px;left:-14px;z-index:88}.f-dropdown.drop-left{display:none;left:-9999px;list-style:none;margin-left:0;position:absolute;background:#fff;border:solid 1px #ccc;font-size:0.875rem;height:auto;max-height:none;width:100%;z-index:89;margin-top:0;margin-left:-2px;max-width:200px}.f-dropdown.drop-left.open{display:block}.f-dropdown.drop-left>*:first-child{margin-top:0}.f-dropdown.drop-left>*:last-child{margin-bottom:0}.f-dropdown.drop-left:before{border:inset 6px;content:"";display:block;height:0;width:0;border-color:transparent transparent transparent #fff;border-left-style:solid;position:absolute;top:10px;right:-12px;left:auto;z-index:89}.f-dropdown.drop-left:after{border:inset 7px;content:"";display:block;height:0;width:0;border-color:transparent transparent transparent #ccc;border-left-style:solid;position:absolute;top:9px;right:-14px;left:auto;z-index:88}.f-dropdown.drop-top{display:none;left:-9999px;list-style:none;margin-left:0;position:absolute;background:#fff;border:solid 1px #ccc;font-size:0.875rem;height:auto;max-height:none;width:100%;z-index:89;margin-left:0;margin-top:-2px;max-width:200px}.f-dropdown.drop-top.open{display:block}.f-dropdown.drop-top>*:first-child{margin-top:0}.f-dropdown.drop-top>*:last-child{margin-bottom:0}.f-dropdown.drop-top:before{border:inset 6px;content:"";display:block;height:0;width:0;border-color:#fff transparent transparent transparent;border-top-style:solid;bottom:-12px;position:absolute;top:auto;left:10px;right:auto;z-index:89}.f-dropdown.drop-top:after{border:inset 7px;content:"";display:block;height:0;width:0;border-color:#ccc transparent transparent transparent;border-top-style:solid;bottom:-14px;position:absolute;top:auto;left:9px;right:auto;z-index:88}.f-dropdown li{cursor:pointer;font-size:0.875rem;line-height:1.125rem;margin:0}.f-dropdown li:hover,.f-dropdown li:focus{background:#eee}.f-dropdown li.radius{border-radius:3px}.f-dropdown li a{display:block;padding:0.5rem;color:#555}.f-dropdown.content{display:none;left:-9999px;list-style:none;margin-left:0;position:absolute;background:#fff;border:solid 1px #ccc;font-size:0.875rem;height:auto;max-height:none;padding:1.25rem;width:100%;z-index:89;max-width:200px}.f-dropdown.content.open{display:block}.f-dropdown.content>*:first-child{margin-top:0}.f-dropdown.content>*:last-child{margin-bottom:0}.f-dropdown.tiny{max-width:200px}.f-dropdown.small{max-width:300px}.f-dropdown.medium{max-width:500px}.f-dropdown.large{max-width:800px}.f-dropdown.mega{width:100% !important;max-width:100% !important}.f-dropdown.mega.open{left:0 !important}table{background:#fff;border:solid 1px #ddd;margin-bottom:1.25rem;table-layout:auto}table caption{background:transparent;color:#222;font-size:1rem;font-weight:bold}table thead{background:#F5F5F5}table thead tr th,table thead tr td{color:#222;font-size:0.875rem;font-weight:bold;padding:0.5rem 0.625rem 0.625rem}table tfoot{background:#F5F5F5}table tfoot tr th,table tfoot tr td{color:#222;font-size:0.875rem;font-weight:bold;padding:0.5rem 0.625rem 0.625rem}table tr th,table tr td{color:#222;font-size:0.875rem;padding:0.5625rem 0.625rem;text-align:left}table tr.even,table tr.alt,table tr:nth-of-type(even){background:#F9F9F9}table thead tr th,table tfoot tr th,table tfoot tr td,table tbody tr th,table tbody tr td,table tr td{display:table-cell;line-height:1.125rem}.range-slider{border:1px solid #ddd;margin:1.25rem 0;position:relative;-ms-touch-action:none;touch-action:none;display:block;height:1rem;width:100%;background:#FAFAFA}.range-slider.vertical-range{border:1px solid #ddd;margin:1.25rem 0;position:relative;-ms-touch-action:none;touch-action:none;display:inline-block;height:12.5rem;width:1rem}.range-slider.vertical-range .range-slider-handle{bottom:-10.5rem;margin-left:-0.5rem;margin-top:0;position:absolute}.range-slider.vertical-range .range-slider-active-segment{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;border-top-left-radius:initial;bottom:0;height:auto;width:0.875rem}.range-slider.radius{background:#FAFAFA;border-radius:3px}.range-slider.radius .range-slider-handle{background:#008CBA;border-radius:3px}.range-slider.radius .range-slider-handle:hover{background:#007ba4}.range-slider.round{background:#FAFAFA;border-radius:1000px}.range-slider.round .range-slider-handle{background:#008CBA;border-radius:1000px}.range-slider.round .range-slider-handle:hover{background:#007ba4}.range-slider.disabled,.range-slider[disabled]{background:#FAFAFA;cursor:not-allowed;opacity:0.7}.range-slider.disabled .range-slider-handle,.range-slider[disabled] .range-slider-handle{background:#008CBA;cursor:default;opacity:0.7}.range-slider.disabled .range-slider-handle:hover,.range-slider[disabled] .range-slider-handle:hover{background:#007ba4}.range-slider-active-segment{background:#e5e5e5;border-bottom-left-radius:inherit;border-top-left-radius:inherit;display:inline-block;height:0.875rem;position:absolute}.range-slider-handle{border:1px solid none;cursor:pointer;display:inline-block;height:1.375rem;position:absolute;top:-0.3125rem;width:2rem;z-index:1;-ms-touch-action:manipulation;touch-action:manipulation;background:#008CBA}.range-slider-handle:hover{background:#007ba4}[class*="block-grid-"]{display:block;padding:0;margin:0 -0.625rem}[class*="block-grid-"]:before,[class*="block-grid-"]:after{content:" ";display:table}[class*="block-grid-"]:after{clear:both}[class*="block-grid-"]>li{display:block;float:left;height:auto;padding:0 0.625rem 1.25rem}@media only screen{.small-block-grid-1>li{list-style:none;width:100%}.small-block-grid-1>li:nth-of-type(1n){clear:none}.small-block-grid-1>li:nth-of-type(1n+1){clear:both}.small-block-grid-2>li{list-style:none;width:50%}.small-block-grid-2>li:nth-of-type(1n){clear:none}.small-block-grid-2>li:nth-of-type(2n+1){clear:both}.small-block-grid-3>li{list-style:none;width:33.33333%}.small-block-grid-3>li:nth-of-type(1n){clear:none}.small-block-grid-3>li:nth-of-type(3n+1){clear:both}.small-block-grid-4>li{list-style:none;width:25%}.small-block-grid-4>li:nth-of-type(1n){clear:none}.small-block-grid-4>li:nth-of-type(4n+1){clear:both}.small-block-grid-5>li{list-style:none;width:20%}.small-block-grid-5>li:nth-of-type(1n){clear:none}.small-block-grid-5>li:nth-of-type(5n+1){clear:both}.small-block-grid-6>li{list-style:none;width:16.66667%}.small-block-grid-6>li:nth-of-type(1n){clear:none}.small-block-grid-6>li:nth-of-type(6n+1){clear:both}.small-block-grid-7>li{list-style:none;width:14.28571%}.small-block-grid-7>li:nth-of-type(1n){clear:none}.small-block-grid-7>li:nth-of-type(7n+1){clear:both}.small-block-grid-8>li{list-style:none;width:12.5%}.small-block-grid-8>li:nth-of-type(1n){clear:none}.small-block-grid-8>li:nth-of-type(8n+1){clear:both}.small-block-grid-9>li{list-style:none;width:11.11111%}.small-block-grid-9>li:nth-of-type(1n){clear:none}.small-block-grid-9>li:nth-of-type(9n+1){clear:both}.small-block-grid-10>li{list-style:none;width:10%}.small-block-grid-10>li:nth-of-type(1n){clear:none}.small-block-grid-10>li:nth-of-type(10n+1){clear:both}.small-block-grid-11>li{list-style:none;width:9.09091%}.small-block-grid-11>li:nth-of-type(1n){clear:none}.small-block-grid-11>li:nth-of-type(11n+1){clear:both}.small-block-grid-12>li{list-style:none;width:8.33333%}.small-block-grid-12>li:nth-of-type(1n){clear:none}.small-block-grid-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width: 40.0625em){.medium-block-grid-1>li{list-style:none;width:100%}.medium-block-grid-1>li:nth-of-type(1n){clear:none}.medium-block-grid-1>li:nth-of-type(1n+1){clear:both}.medium-block-grid-2>li{list-style:none;width:50%}.medium-block-grid-2>li:nth-of-type(1n){clear:none}.medium-block-grid-2>li:nth-of-type(2n+1){clear:both}.medium-block-grid-3>li{list-style:none;width:33.33333%}.medium-block-grid-3>li:nth-of-type(1n){clear:none}.medium-block-grid-3>li:nth-of-type(3n+1){clear:both}.medium-block-grid-4>li{list-style:none;width:25%}.medium-block-grid-4>li:nth-of-type(1n){clear:none}.medium-block-grid-4>li:nth-of-type(4n+1){clear:both}.medium-block-grid-5>li{list-style:none;width:20%}.medium-block-grid-5>li:nth-of-type(1n){clear:none}.medium-block-grid-5>li:nth-of-type(5n+1){clear:both}.medium-block-grid-6>li{list-style:none;width:16.66667%}.medium-block-grid-6>li:nth-of-type(1n){clear:none}.medium-block-grid-6>li:nth-of-type(6n+1){clear:both}.medium-block-grid-7>li{list-style:none;width:14.28571%}.medium-block-grid-7>li:nth-of-type(1n){clear:none}.medium-block-grid-7>li:nth-of-type(7n+1){clear:both}.medium-block-grid-8>li{list-style:none;width:12.5%}.medium-block-grid-8>li:nth-of-type(1n){clear:none}.medium-block-grid-8>li:nth-of-type(8n+1){clear:both}.medium-block-grid-9>li{list-style:none;width:11.11111%}.medium-block-grid-9>li:nth-of-type(1n){clear:none}.medium-block-grid-9>li:nth-of-type(9n+1){clear:both}.medium-block-grid-10>li{list-style:none;width:10%}.medium-block-grid-10>li:nth-of-type(1n){clear:none}.medium-block-grid-10>li:nth-of-type(10n+1){clear:both}.medium-block-grid-11>li{list-style:none;width:9.09091%}.medium-block-grid-11>li:nth-of-type(1n){clear:none}.medium-block-grid-11>li:nth-of-type(11n+1){clear:both}.medium-block-grid-12>li{list-style:none;width:8.33333%}.medium-block-grid-12>li:nth-of-type(1n){clear:none}.medium-block-grid-12>li:nth-of-type(12n+1){clear:both}}@media only screen and (min-width: 64.0625em){.large-block-grid-1>li{list-style:none;width:100%}.large-block-grid-1>li:nth-of-type(1n){clear:none}.large-block-grid-1>li:nth-of-type(1n+1){clear:both}.large-block-grid-2>li{list-style:none;width:50%}.large-block-grid-2>li:nth-of-type(1n){clear:none}.large-block-grid-2>li:nth-of-type(2n+1){clear:both}.large-block-grid-3>li{list-style:none;width:33.33333%}.large-block-grid-3>li:nth-of-type(1n){clear:none}.large-block-grid-3>li:nth-of-type(3n+1){clear:both}.large-block-grid-4>li{list-style:none;width:25%}.large-block-grid-4>li:nth-of-type(1n){clear:none}.large-block-grid-4>li:nth-of-type(4n+1){clear:both}.large-block-grid-5>li{list-style:none;width:20%}.large-block-grid-5>li:nth-of-type(1n){clear:none}.large-block-grid-5>li:nth-of-type(5n+1){clear:both}.large-block-grid-6>li{list-style:none;width:16.66667%}.large-block-grid-6>li:nth-of-type(1n){clear:none}.large-block-grid-6>li:nth-of-type(6n+1){clear:both}.large-block-grid-7>li{list-style:none;width:14.28571%}.large-block-grid-7>li:nth-of-type(1n){clear:none}.large-block-grid-7>li:nth-of-type(7n+1){clear:both}.large-block-grid-8>li{list-style:none;width:12.5%}.large-block-grid-8>li:nth-of-type(1n){clear:none}.large-block-grid-8>li:nth-of-type(8n+1){clear:both}.large-block-grid-9>li{list-style:none;width:11.11111%}.large-block-grid-9>li:nth-of-type(1n){clear:none}.large-block-grid-9>li:nth-of-type(9n+1){clear:both}.large-block-grid-10>li{list-style:none;width:10%}.large-block-grid-10>li:nth-of-type(1n){clear:none}.large-block-grid-10>li:nth-of-type(10n+1){clear:both}.large-block-grid-11>li{list-style:none;width:9.09091%}.large-block-grid-11>li:nth-of-type(1n){clear:none}.large-block-grid-11>li:nth-of-type(11n+1){clear:both}.large-block-grid-12>li{list-style:none;width:8.33333%}.large-block-grid-12>li:nth-of-type(1n){clear:none}.large-block-grid-12>li:nth-of-type(12n+1){clear:both}}.flex-video{height:0;margin-bottom:1rem;overflow:hidden;padding-bottom:67.5%;padding-top:1.5625rem;position:relative}.flex-video.widescreen{padding-bottom:56.34%}.flex-video.vimeo{padding-top:0}.flex-video iframe,.flex-video object,.flex-video embed,.flex-video video{height:100%;position:absolute;top:0;width:100%;left:0}.keystroke,kbd{background-color:#ededed;border-color:#ddd;color:#222;border-style:solid;border-width:1px;font-family:"Consolas","Menlo","Courier",monospace;font-size:inherit;margin:0;padding:0.125rem 0.25rem 0;border-radius:3px}.switch{border:none;margin-bottom:1.5rem;outline:0;padding:0;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{background:#ddd;color:transparent;cursor:pointer;display:block;margin-bottom:1rem;position:relative;text-indent:100%;width:4rem;height:2rem;transition:left 0.15s ease-out}.switch input{left:10px;opacity:0;padding:0;position:absolute;top:9px}.switch input+label{margin-left:0;margin-right:0}.switch label:after{background:#fff;content:"";display:block;height:1.5rem;left:.25rem;position:absolute;top:.25rem;width:1.5rem;-webkit-transition:left 0.15s ease-out;-moz-transition:left 0.15s ease-out;-o-transition:translate3d(0, 0, 0);transition:left 0.15s ease-out;-webkit-transform:translate3d(0, 0, 0);-moz-transform:translate3d(0, 0, 0);-ms-transform:translate3d(0, 0, 0);-o-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.switch input:checked+label{background:#008CBA}.switch input:checked+label:after{left:2.25rem}.switch label{height:2rem;width:4rem}.switch label:after{height:1.5rem;width:1.5rem}.switch input:checked+label:after{left:2.25rem}.switch label{color:transparent;background:#ddd}.switch label:after{background:#fff}.switch input:checked+label{background:#008CBA}.switch.large label{height:2.5rem;width:5rem}.switch.large label:after{height:2rem;width:2rem}.switch.large input:checked+label:after{left:2.75rem}.switch.small label{height:1.75rem;width:3.5rem}.switch.small label:after{height:1.25rem;width:1.25rem}.switch.small input:checked+label:after{left:2rem}.switch.tiny label{height:1.5rem;width:3rem}.switch.tiny label:after{height:1rem;width:1rem}.switch.tiny input:checked+label:after{left:1.75rem}.switch.radius label{border-radius:4px}.switch.radius label:after{border-radius:3px}.switch.round{border-radius:1000px}.switch.round label{border-radius:2rem}.switch.round label:after{border-radius:2rem}@media only screen{.show-for-small-only,.show-for-small-up,.show-for-small,.show-for-small-down,.hide-for-medium-only,.hide-for-medium-up,.hide-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.hide-for-small-only,.hide-for-small-up,.hide-for-small,.hide-for-small-down,.show-for-medium-only,.show-for-medium-up,.show-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.visible-for-small-only,.visible-for-small-up,.visible-for-small,.visible-for-small-down,.hidden-for-medium-only,.hidden-for-medium-up,.hidden-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.hidden-for-small-only,.hidden-for-small-up,.hidden-for-small,.hidden-for-small-down,.visible-for-medium-only,.visible-for-medium-up,.visible-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}table.show-for-small-only,table.show-for-small-up,table.show-for-small,table.show-for-small-down,table.hide-for-medium-only,table.hide-for-medium-up,table.hide-for-medium,table.show-for-medium-down,table.hide-for-large-only,table.hide-for-large-up,table.hide-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.show-for-small-only,thead.show-for-small-up,thead.show-for-small,thead.show-for-small-down,thead.hide-for-medium-only,thead.hide-for-medium-up,thead.hide-for-medium,thead.show-for-medium-down,thead.hide-for-large-only,thead.hide-for-large-up,thead.hide-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.show-for-small-only,tbody.show-for-small-up,tbody.show-for-small,tbody.show-for-small-down,tbody.hide-for-medium-only,tbody.hide-for-medium-up,tbody.hide-for-medium,tbody.show-for-medium-down,tbody.hide-for-large-only,tbody.hide-for-large-up,tbody.hide-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.show-for-small-only,tr.show-for-small-up,tr.show-for-small,tr.show-for-small-down,tr.hide-for-medium-only,tr.hide-for-medium-up,tr.hide-for-medium,tr.show-for-medium-down,tr.hide-for-large-only,tr.hide-for-large-up,tr.hide-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.show-for-small-only,td.show-for-small-only,th.show-for-small-up,td.show-for-small-up,th.show-for-small,td.show-for-small,th.show-for-small-down,td.show-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.hide-for-medium-up,td.hide-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.show-for-medium-down,td.show-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.hide-for-large-up,td.hide-for-large-up,th.hide-for-large,td.hide-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 40.0625em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.show-for-medium-only,.show-for-medium-up,.show-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.hide-for-medium-only,.hide-for-medium-up,.hide-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.visible-for-medium-only,.visible-for-medium-up,.visible-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.hidden-for-medium-only,.hidden-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.show-for-medium-only,table.show-for-medium-up,table.show-for-medium,table.show-for-medium-down,table.hide-for-large-only,table.hide-for-large-up,table.hide-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.show-for-medium-only,thead.show-for-medium-up,thead.show-for-medium,thead.show-for-medium-down,thead.hide-for-large-only,thead.hide-for-large-up,thead.hide-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.show-for-medium-only,tbody.show-for-medium-up,tbody.show-for-medium,tbody.show-for-medium-down,tbody.hide-for-large-only,tbody.hide-for-large-up,tbody.hide-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.show-for-medium-only,tr.show-for-medium-up,tr.show-for-medium,tr.show-for-medium-down,tr.hide-for-large-only,tr.hide-for-large-up,tr.hide-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.show-for-medium-only,td.show-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.show-for-medium,td.show-for-medium,th.show-for-medium-down,td.show-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.hide-for-large-up,td.hide-for-large-up,th.hide-for-large,td.hide-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 64.0625em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.show-for-large-only,.show-for-large-up,.show-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.hide-for-large-only,.hide-for-large-up,.hide-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.visible-for-large-only,.visible-for-large-up,.visible-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.hidden-for-large-only,.hidden-for-large-up,.hidden-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.show-for-large-only,table.show-for-large-up,table.show-for-large,table.show-for-large-down,table.hide-for-xlarge-only,table.hide-for-xlarge-up,table.hide-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.show-for-large-only,thead.show-for-large-up,thead.show-for-large,thead.show-for-large-down,thead.hide-for-xlarge-only,thead.hide-for-xlarge-up,thead.hide-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.show-for-large-only,tbody.show-for-large-up,tbody.show-for-large,tbody.show-for-large-down,tbody.hide-for-xlarge-only,tbody.hide-for-xlarge-up,tbody.hide-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.show-for-large-only,tr.show-for-large-up,tr.show-for-large,tr.show-for-large-down,tr.hide-for-xlarge-only,tr.hide-for-xlarge-up,tr.hide-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.show-for-large-only,td.show-for-large-only,th.show-for-large-up,td.show-for-large-up,th.show-for-large,td.show-for-large,th.show-for-large-down,td.show-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.hide-for-xlarge-up,td.hide-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 90.0625em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.hide-for-large-only,.show-for-large-up,.hide-for-large,.hide-for-large-down,.show-for-xlarge-only,.show-for-xlarge-up,.show-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.show-for-large-only,.hide-for-large-up,.show-for-large,.show-for-large-down,.hide-for-xlarge-only,.hide-for-xlarge-up,.hide-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.hidden-for-large-only,.visible-for-large-up,.hidden-for-large,.hidden-for-large-down,.visible-for-xlarge-only,.visible-for-xlarge-up,.visible-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.visible-for-large-only,.hidden-for-large-up,.visible-for-large,.visible-for-large-down,.hidden-for-xlarge-only,.hidden-for-xlarge-up,.hidden-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.hidden-for-xxlarge-down{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-large-only,table.show-for-large-up,table.hide-for-large,table.hide-for-large-down,table.show-for-xlarge-only,table.show-for-xlarge-up,table.show-for-xlarge,table.show-for-xlarge-down,table.hide-for-xxlarge-only,table.hide-for-xxlarge-up,table.hide-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-large-only,thead.show-for-large-up,thead.hide-for-large,thead.hide-for-large-down,thead.show-for-xlarge-only,thead.show-for-xlarge-up,thead.show-for-xlarge,thead.show-for-xlarge-down,thead.hide-for-xxlarge-only,thead.hide-for-xxlarge-up,thead.hide-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-large-only,tbody.show-for-large-up,tbody.hide-for-large,tbody.hide-for-large-down,tbody.show-for-xlarge-only,tbody.show-for-xlarge-up,tbody.show-for-xlarge,tbody.show-for-xlarge-down,tbody.hide-for-xxlarge-only,tbody.hide-for-xxlarge-up,tbody.hide-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-large-only,tr.show-for-large-up,tr.hide-for-large,tr.hide-for-large-down,tr.show-for-xlarge-only,tr.show-for-xlarge-up,tr.show-for-xlarge,tr.show-for-xlarge-down,tr.hide-for-xxlarge-only,tr.hide-for-xxlarge-up,tr.hide-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.show-for-large-up,td.show-for-large-up,th.hide-for-large,td.hide-for-large,th.hide-for-large-down,td.hide-for-large-down,th.show-for-xlarge-only,td.show-for-xlarge-only,th.show-for-xlarge-up,td.show-for-xlarge-up,th.show-for-xlarge,td.show-for-xlarge,th.show-for-xlarge-down,td.show-for-xlarge-down,th.hide-for-xxlarge-only,td.hide-for-xxlarge-only,th.hide-for-xxlarge-up,td.hide-for-xxlarge-up,th.hide-for-xxlarge,td.hide-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}@media only screen and (min-width: 120.0625em){.hide-for-small-only,.show-for-small-up,.hide-for-small,.hide-for-small-down,.hide-for-medium-only,.show-for-medium-up,.hide-for-medium,.hide-for-medium-down,.hide-for-large-only,.show-for-large-up,.hide-for-large,.hide-for-large-down,.hide-for-xlarge-only,.show-for-xlarge-up,.hide-for-xlarge,.hide-for-xlarge-down,.show-for-xxlarge-only,.show-for-xxlarge-up,.show-for-xxlarge,.show-for-xxlarge-down{display:inherit !important}.show-for-small-only,.hide-for-small-up,.show-for-small,.show-for-small-down,.show-for-medium-only,.hide-for-medium-up,.show-for-medium,.show-for-medium-down,.show-for-large-only,.hide-for-large-up,.show-for-large,.show-for-large-down,.show-for-xlarge-only,.hide-for-xlarge-up,.show-for-xlarge,.show-for-xlarge-down,.hide-for-xxlarge-only,.hide-for-xxlarge-up,.hide-for-xxlarge,.hide-for-xxlarge-down{display:none !important}.hidden-for-small-only,.visible-for-small-up,.hidden-for-small,.hidden-for-small-down,.hidden-for-medium-only,.visible-for-medium-up,.hidden-for-medium,.hidden-for-medium-down,.hidden-for-large-only,.visible-for-large-up,.hidden-for-large,.hidden-for-large-down,.hidden-for-xlarge-only,.visible-for-xlarge-up,.hidden-for-xlarge,.hidden-for-xlarge-down,.visible-for-xxlarge-only,.visible-for-xxlarge-up,.visible-for-xxlarge,.visible-for-xxlarge-down{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.visible-for-small-only,.hidden-for-small-up,.visible-for-small,.visible-for-small-down,.visible-for-medium-only,.hidden-for-medium-up,.visible-for-medium,.visible-for-medium-down,.visible-for-large-only,.hidden-for-large-up,.visible-for-large,.visible-for-large-down,.visible-for-xlarge-only,.hidden-for-xlarge-up,.visible-for-xlarge,.visible-for-xlarge-down,.hidden-for-xxlarge-only,.hidden-for-xxlarge-up,.hidden-for-xxlarge,.hidden-for-xxlarge-down{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}table.hide-for-small-only,table.show-for-small-up,table.hide-for-small,table.hide-for-small-down,table.hide-for-medium-only,table.show-for-medium-up,table.hide-for-medium,table.hide-for-medium-down,table.hide-for-large-only,table.show-for-large-up,table.hide-for-large,table.hide-for-large-down,table.hide-for-xlarge-only,table.show-for-xlarge-up,table.hide-for-xlarge,table.hide-for-xlarge-down,table.show-for-xxlarge-only,table.show-for-xxlarge-up,table.show-for-xxlarge,table.show-for-xxlarge-down{display:table !important}thead.hide-for-small-only,thead.show-for-small-up,thead.hide-for-small,thead.hide-for-small-down,thead.hide-for-medium-only,thead.show-for-medium-up,thead.hide-for-medium,thead.hide-for-medium-down,thead.hide-for-large-only,thead.show-for-large-up,thead.hide-for-large,thead.hide-for-large-down,thead.hide-for-xlarge-only,thead.show-for-xlarge-up,thead.hide-for-xlarge,thead.hide-for-xlarge-down,thead.show-for-xxlarge-only,thead.show-for-xxlarge-up,thead.show-for-xxlarge,thead.show-for-xxlarge-down{display:table-header-group !important}tbody.hide-for-small-only,tbody.show-for-small-up,tbody.hide-for-small,tbody.hide-for-small-down,tbody.hide-for-medium-only,tbody.show-for-medium-up,tbody.hide-for-medium,tbody.hide-for-medium-down,tbody.hide-for-large-only,tbody.show-for-large-up,tbody.hide-for-large,tbody.hide-for-large-down,tbody.hide-for-xlarge-only,tbody.show-for-xlarge-up,tbody.hide-for-xlarge,tbody.hide-for-xlarge-down,tbody.show-for-xxlarge-only,tbody.show-for-xxlarge-up,tbody.show-for-xxlarge,tbody.show-for-xxlarge-down{display:table-row-group !important}tr.hide-for-small-only,tr.show-for-small-up,tr.hide-for-small,tr.hide-for-small-down,tr.hide-for-medium-only,tr.show-for-medium-up,tr.hide-for-medium,tr.hide-for-medium-down,tr.hide-for-large-only,tr.show-for-large-up,tr.hide-for-large,tr.hide-for-large-down,tr.hide-for-xlarge-only,tr.show-for-xlarge-up,tr.hide-for-xlarge,tr.hide-for-xlarge-down,tr.show-for-xxlarge-only,tr.show-for-xxlarge-up,tr.show-for-xxlarge,tr.show-for-xxlarge-down{display:table-row}th.hide-for-small-only,td.hide-for-small-only,th.show-for-small-up,td.show-for-small-up,th.hide-for-small,td.hide-for-small,th.hide-for-small-down,td.hide-for-small-down,th.hide-for-medium-only,td.hide-for-medium-only,th.show-for-medium-up,td.show-for-medium-up,th.hide-for-medium,td.hide-for-medium,th.hide-for-medium-down,td.hide-for-medium-down,th.hide-for-large-only,td.hide-for-large-only,th.show-for-large-up,td.show-for-large-up,th.hide-for-large,td.hide-for-large,th.hide-for-large-down,td.hide-for-large-down,th.hide-for-xlarge-only,td.hide-for-xlarge-only,th.show-for-xlarge-up,td.show-for-xlarge-up,th.hide-for-xlarge,td.hide-for-xlarge,th.hide-for-xlarge-down,td.hide-for-xlarge-down,th.show-for-xxlarge-only,td.show-for-xxlarge-only,th.show-for-xxlarge-up,td.show-for-xxlarge-up,th.show-for-xxlarge,td.show-for-xxlarge,th.show-for-xxlarge-down,td.show-for-xxlarge-down{display:table-cell !important}}.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.hide-for-landscape,table.show-for-portrait{display:table !important}thead.hide-for-landscape,thead.show-for-portrait{display:table-header-group !important}tbody.hide-for-landscape,tbody.show-for-portrait{display:table-row-group !important}tr.hide-for-landscape,tr.show-for-portrait{display:table-row !important}td.hide-for-landscape,td.show-for-portrait,th.hide-for-landscape,th.show-for-portrait{display:table-cell !important}@media only screen and (orientation: landscape){.show-for-landscape,.hide-for-portrait{display:inherit !important}.hide-for-landscape,.show-for-portrait{display:none !important}table.show-for-landscape,table.hide-for-portrait{display:table !important}thead.show-for-landscape,thead.hide-for-portrait{display:table-header-group !important}tbody.show-for-landscape,tbody.hide-for-portrait{display:table-row-group !important}tr.show-for-landscape,tr.hide-for-portrait{display:table-row !important}td.show-for-landscape,td.hide-for-portrait,th.show-for-landscape,th.hide-for-portrait{display:table-cell !important}}@media only screen and (orientation: portrait){.show-for-portrait,.hide-for-landscape{display:inherit !important}.hide-for-portrait,.show-for-landscape{display:none !important}table.show-for-portrait,table.hide-for-landscape{display:table !important}thead.show-for-portrait,thead.hide-for-landscape{display:table-header-group !important}tbody.show-for-portrait,tbody.hide-for-landscape{display:table-row-group !important}tr.show-for-portrait,tr.hide-for-landscape{display:table-row !important}td.show-for-portrait,td.hide-for-landscape,th.show-for-portrait,th.hide-for-landscape{display:table-cell !important}}.show-for-touch{display:none !important}.hide-for-touch{display:inherit !important}.touch .show-for-touch{display:inherit !important}.touch .hide-for-touch{display:none !important}table.hide-for-touch{display:table !important}.touch table.show-for-touch{display:table !important}thead.hide-for-touch{display:table-header-group !important}.touch thead.show-for-touch{display:table-header-group !important}tbody.hide-for-touch{display:table-row-group !important}.touch tbody.show-for-touch{display:table-row-group !important}tr.hide-for-touch{display:table-row !important}.touch tr.show-for-touch{display:table-row !important}td.hide-for-touch{display:table-cell !important}.touch td.show-for-touch{display:table-cell !important}th.hide-for-touch{display:table-cell !important}.touch th.show-for-touch{display:table-cell !important}.show-for-sr{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}.show-on-focus{clip:rect(1px, 1px, 1px, 1px);height:1px;overflow:hidden;position:absolute !important;width:1px}.show-on-focus:focus,.show-on-focus:active{position:static !important;height:auto;width:auto;overflow:visible;clip:auto}.print-only{display:none !important}@media print{*{background:transparent !important;box-shadow:none !important;color:#000 !important;text-shadow:none !important}.show-for-print{display:block}.hide-for-print{display:none}table.show-for-print{display:table !important}thead.show-for-print{display:table-header-group !important}tbody.show-for-print{display:table-row-group !important}tr.show-for-print{display:table-row !important}td.show-for-print{display:table-cell !important}th.show-for-print{display:table-cell !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.hide-on-print{display:none !important}.print-only{display:block !important}.hide-for-print{display:none !important}.show-for-print{display:inherit !important}}@media print{.show-for-print{display:block}.hide-for-print{display:none}table.show-for-print{display:table !important}thead.show-for-print{display:table-header-group !important}tbody.show-for-print{display:table-row-group !important}tr.show-for-print{display:table-row !important}td.show-for-print{display:table-cell !important}th.show-for-print{display:table-cell !important}}@media not print{.show-for-print{display:none !important}} diff --git a/webroot/css/src/_variables.scss b/webroot/css/src/_variables.scss new file mode 100644 index 0000000..11cfec7 --- /dev/null +++ b/webroot/css/src/_variables.scss @@ -0,0 +1,3 @@ +/** + * Define CSS variables for use in the rest of the style here + */ diff --git a/webroot/css/src/app.scss b/webroot/css/src/app.scss index 0fc6e56..791a3e3 100644 --- a/webroot/css/src/app.scss +++ b/webroot/css/src/app.scss @@ -2,12 +2,19 @@ * Main application styles should be defined here. */ -@import "../cake.css"; // @TODO: Remove this when no longer required. +@import "../vendor/normalize.css"; // @TODO: Remove this when no longer required. +@import "../vendor/cake.css"; // @TODO: Remove this when no longer required. +// Foundation 5. +@import '../vendor/foundation.css'; + +// For Foundation 6, use the following instead: //@import 'settings'; -@import 'foundation'; -@include foundation-everything; -//@import 'variables'; +//@import 'foundation'; +//@include foundation-everything; + +// Load our own components. +@import 'variables'; //@import 'tables'; //@import 'forms'; //@import 'footer'; diff --git a/webroot/css/cake.css b/webroot/css/vendor/cake.css similarity index 100% rename from webroot/css/cake.css rename to webroot/css/vendor/cake.css diff --git a/webroot/css/foundation.css b/webroot/css/vendor/foundation.css similarity index 100% rename from webroot/css/foundation.css rename to webroot/css/vendor/foundation.css diff --git a/webroot/css/normalize.css b/webroot/css/vendor/normalize.css similarity index 100% rename from webroot/css/normalize.css rename to webroot/css/vendor/normalize.css diff --git a/webroot/js/foundation.min.js b/webroot/js/vendor/foundation.min.js similarity index 100% rename from webroot/js/foundation.min.js rename to webroot/js/vendor/foundation.min.js diff --git a/webroot/js/foundation/foundation.abide.js b/webroot/js/vendor/foundation/foundation.abide.js similarity index 100% rename from webroot/js/foundation/foundation.abide.js rename to webroot/js/vendor/foundation/foundation.abide.js diff --git a/webroot/js/foundation/foundation.accordion.js b/webroot/js/vendor/foundation/foundation.accordion.js similarity index 100% rename from webroot/js/foundation/foundation.accordion.js rename to webroot/js/vendor/foundation/foundation.accordion.js diff --git a/webroot/js/foundation/foundation.alert.js b/webroot/js/vendor/foundation/foundation.alert.js similarity index 100% rename from webroot/js/foundation/foundation.alert.js rename to webroot/js/vendor/foundation/foundation.alert.js diff --git a/webroot/js/foundation/foundation.clearing.js b/webroot/js/vendor/foundation/foundation.clearing.js similarity index 100% rename from webroot/js/foundation/foundation.clearing.js rename to webroot/js/vendor/foundation/foundation.clearing.js diff --git a/webroot/js/foundation/foundation.dropdown.js b/webroot/js/vendor/foundation/foundation.dropdown.js similarity index 100% rename from webroot/js/foundation/foundation.dropdown.js rename to webroot/js/vendor/foundation/foundation.dropdown.js diff --git a/webroot/js/foundation/foundation.equalizer.js b/webroot/js/vendor/foundation/foundation.equalizer.js similarity index 100% rename from webroot/js/foundation/foundation.equalizer.js rename to webroot/js/vendor/foundation/foundation.equalizer.js diff --git a/webroot/js/foundation/foundation.interchange.js b/webroot/js/vendor/foundation/foundation.interchange.js similarity index 100% rename from webroot/js/foundation/foundation.interchange.js rename to webroot/js/vendor/foundation/foundation.interchange.js diff --git a/webroot/js/foundation/foundation.joyride.js b/webroot/js/vendor/foundation/foundation.joyride.js similarity index 100% rename from webroot/js/foundation/foundation.joyride.js rename to webroot/js/vendor/foundation/foundation.joyride.js diff --git a/webroot/js/foundation/foundation.js b/webroot/js/vendor/foundation/foundation.js similarity index 100% rename from webroot/js/foundation/foundation.js rename to webroot/js/vendor/foundation/foundation.js diff --git a/webroot/js/foundation/foundation.magellan.js b/webroot/js/vendor/foundation/foundation.magellan.js similarity index 100% rename from webroot/js/foundation/foundation.magellan.js rename to webroot/js/vendor/foundation/foundation.magellan.js diff --git a/webroot/js/foundation/foundation.offcanvas.js b/webroot/js/vendor/foundation/foundation.offcanvas.js similarity index 100% rename from webroot/js/foundation/foundation.offcanvas.js rename to webroot/js/vendor/foundation/foundation.offcanvas.js diff --git a/webroot/js/foundation/foundation.orbit.js b/webroot/js/vendor/foundation/foundation.orbit.js similarity index 100% rename from webroot/js/foundation/foundation.orbit.js rename to webroot/js/vendor/foundation/foundation.orbit.js diff --git a/webroot/js/foundation/foundation.reveal.js b/webroot/js/vendor/foundation/foundation.reveal.js similarity index 100% rename from webroot/js/foundation/foundation.reveal.js rename to webroot/js/vendor/foundation/foundation.reveal.js diff --git a/webroot/js/foundation/foundation.slider.js b/webroot/js/vendor/foundation/foundation.slider.js similarity index 100% rename from webroot/js/foundation/foundation.slider.js rename to webroot/js/vendor/foundation/foundation.slider.js diff --git a/webroot/js/foundation/foundation.tab.js b/webroot/js/vendor/foundation/foundation.tab.js similarity index 100% rename from webroot/js/foundation/foundation.tab.js rename to webroot/js/vendor/foundation/foundation.tab.js diff --git a/webroot/js/foundation/foundation.tooltip.js b/webroot/js/vendor/foundation/foundation.tooltip.js similarity index 100% rename from webroot/js/foundation/foundation.tooltip.js rename to webroot/js/vendor/foundation/foundation.tooltip.js diff --git a/webroot/js/foundation/foundation.topbar.js b/webroot/js/vendor/foundation/foundation.topbar.js similarity index 100% rename from webroot/js/foundation/foundation.topbar.js rename to webroot/js/vendor/foundation/foundation.topbar.js From f2b65fdffd8c00c41651b4236e5e8633c1a495df Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 14 Jun 2016 13:45:09 -0500 Subject: [PATCH 07/15] Fix code sniffs. --- config/app.php.template | 3 +-- src/Controller/AppController.php | 1 - src/View/AppView.php | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/config/app.php.template b/config/app.php.template index 40ee923..a61c69d 100755 --- a/config/app.php.template +++ b/config/app.php.template @@ -373,6 +373,5 @@ return [ * Top-level keys should match the Entity/Table, second-level keys * should match `*_slug` field names. */ - 'Lists' => [ - ], + 'Lists' => [], ]; diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 0335ee5..3f9b202 100755 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -23,7 +23,6 @@ use Cake\Routing\Router; use LibRegistry\LibRegistryTrait; - /** * Application Controller * diff --git a/src/View/AppView.php b/src/View/AppView.php index a10d2a5..2afff7f 100755 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -19,8 +19,7 @@ class AppView extends View { * @return void */ public function initialize() { - $this->loadHelper('Html', [ - ]); + $this->loadHelper('Html', []); $this->loadHelper('Form', [ 'errorClass' => 'error', 'templates' => [ From 0f09a0d92c72eb1a53cbbe5cdfb2f0f35561a1a2 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 14 Jun 2016 16:08:59 -0500 Subject: [PATCH 08/15] fixup stuff --- config/bootstrap.php | 2 +- .../TestCase/Controller/AppControllerTest.php | 84 ++++++++++--------- .../Controller/PagesControllerTest.php | 30 ++++++- tests/TestCase/Lib/ConfigClosuresTest.php | 1 + 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/config/bootstrap.php b/config/bootstrap.php index 24fb813..36a5254 100755 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -194,7 +194,7 @@ Plugin::load('BasicSeed', ['bootstrap' => false, 'routes' => false]); Plugin::load('CreatorModifier', ['bootstrap' => false, 'routes' => false]); Plugin::load('ConfigRead', ['bootstrap' => false, 'routes' => false]); -Plugin::load('LoadsysTheme', ['bootstrap' => false, 'routes' => false]); +Plugin::load('LoadsysTheme', ['bootstrap' => true, 'routes' => false]); Plugin::load('Migrations'); Plugin::load('Uuid', ['bootstrap' => false, 'routes' => false]); diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php index 22d5108..af0146b 100644 --- a/tests/TestCase/Controller/AppControllerTest.php +++ b/tests/TestCase/Controller/AppControllerTest.php @@ -5,6 +5,9 @@ namespace App\Test\TestCase\Controller; use App\Controller\AppController; +use Cake\Core\Configure; +use Cake\Event\Event; +use Cake\ORM\Entity as User; use Cake\TestSuite\IntegrationTestCase; /** @@ -35,6 +38,21 @@ class AppControllerTest extends IntegrationTestCase { */ public $fixtures = []; + /** + * setUp method + * + * @return void + */ + public function setUp() { + parent::tearDown(); + + $this->user = new User([ + 'id' => 1, + 'role' => 'admin', + 'email' => 'admin@localhost', + ]); + } + /** * tearDown method * @@ -42,6 +60,7 @@ class AppControllerTest extends IntegrationTestCase { */ public function tearDown() { Configure::write('Defaults.ssl_force', false); + unset($this->user); parent::tearDown(); } @@ -91,7 +110,6 @@ public function testInitialize() { */ public function testIsAuthorized($expected, $msg = '') { $request = $this->getMock('Cake\Network\Request'); - $request->params['prefix'] = $prefix; $controller = new AppController($request); $this->assertEquals( @@ -120,50 +138,19 @@ public function provideIsAuthorizedArgs() { * @covers \App\Controller\AppController::auth */ public function testBeforeFilter() { - $request = $this->getMock('Cake\Network\Request'); - $request->params['prefix'] = 'admin'; - - // Set up auth data, AuthComponent and Controller. - $user = [ - 'id' => 1, - 'role' => 'admin', - 'email' => 'admin@localhost', - ]; - $userEntity = new User($user); - $this->session($user); - - $authComponent = $this->getMock('AuthComponent', ['userEntity']); - $authComponent->expects($this->once()) - ->method('userEntity') - ->with() - ->willReturn($userEntity); - $controller = $this->getMock( 'App\Controller\AppController', ['viewBuilder', 'layout'], - [$request] + [$this->getMock('Cake\Network\Request')] ); - $controller->expects($this->once()) - ->method('viewBuilder') - ->will($this->returnSelf()); - $controller->expects($this->once()) - ->method('layout') - ->with('admin'); - $controller->Auth = $authComponent; + //$controller->expects($this->once()) + // ->method('viewBuilder') + // ->will($this->returnSelf()); + //$controller->expects($this->once()) + // ->method('layout') + // ->with('admin'); $controller->beforeFilter(new Event([])); - - // Assert view vars are present. - $this->assertEquals( - $userEntity, - $controller->viewVars['u'], - 'The `u` global view var should be set.' - ); - $this->assertEquals( - $user['role'], - $controller->viewVars['uRole'], - 'The `uRole` global view var should be set.' - ); } /** @@ -301,7 +288,24 @@ public function providerBlackHole() { * @covers \App\Controller\AppController::beforeRender */ public function testBeforeRender() { - $this->markTestSkipped('No method to test.'); + + // Set up auth data, AuthComponent and Controller. + $controller = $this->getMock( + 'App\Controller\AppController', + ['set'], + [$this->getMock('Cake\Network\Request')] + ); + $controller->expects($this->once()) + ->method('set') + ->with(['u' => $this->user]) + ->willreturn('canary'); + $controller->Auth = $this->getMock('AuthComponent', ['userEntity']); + $controller->Auth->expects($this->once()) + ->method('userEntity') + ->with() + ->willReturn($this->user); + + $controller->beforeRender(new Event([])); } /** diff --git a/tests/TestCase/Controller/PagesControllerTest.php b/tests/TestCase/Controller/PagesControllerTest.php index 3fd172b..e15f1bb 100644 --- a/tests/TestCase/Controller/PagesControllerTest.php +++ b/tests/TestCase/Controller/PagesControllerTest.php @@ -5,6 +5,7 @@ namespace App\Test\TestCase\Controller; use App\Controller\PagesController; +use Cake\Core\Configure; use Cake\ORM\TableRegistry; use Cake\TestSuite\IntegrationTestCase; @@ -26,6 +27,7 @@ class PagesControllerTest extends IntegrationTestCase { */ public function setUp() { parent::setUp(); + $this->debug = Configure::read('debug'); } /** @@ -34,16 +36,20 @@ public function setUp() { * @return void */ public function tearDown() { + Configure::write('debug', $this->debug); parent::tearDown(); } /** - * Test the home page display method for the pages controller + * Test the home page display method for the pages controller, but + * only when debug is on. * * @return void * @covers App\Controller\PagesController::display */ - public function testDisplayHome() { + public function testDisplayHomeDevelopment() { + Configure::write('debug', true); + $this->get("/"); $this->assertResponseOk(); @@ -53,6 +59,22 @@ public function testDisplayHome() { ); } + /** + * Verify that the default home page is suppressed when debug is off. + * + * @return void + * @covers App\Controller\PagesController::display + */ + public function testDisplayHomeProduction() { + $this->markTestIncomplete('@TODO: Can not get this working properly.'); + Configure::write('debug', false); + + $r = $this->get('/'); + + $this->assertResponseError(); + $this->assertResponseContains('Default Cake homepage is suppressed when debug is off.'); + } + /** * Test a sub page that does not exist. * @@ -66,7 +88,7 @@ public function testDisplayInvalid() { $this->assertRedirect('/', 'Attempting to access the PagesController::display() method directly should force a redirect to the homepage.'); $this->get('/pages/this/page/does/not/exist'); - $this->assertResponseFailure(); - $this->assertResponseContains('this/page/does/not/exist.ctp'); + $this->assertResponseError(); + $this->assertResponseContains('this/page/does/not/exist'); } } diff --git a/tests/TestCase/Lib/ConfigClosuresTest.php b/tests/TestCase/Lib/ConfigClosuresTest.php index 965525d..e4a7f0a 100644 --- a/tests/TestCase/Lib/ConfigClosuresTest.php +++ b/tests/TestCase/Lib/ConfigClosuresTest.php @@ -187,6 +187,7 @@ public function testStyleForEnv() { 'Format' => '', 'Snippet' => 'background: red !important;', ]; + Configure::write('debug', true); Configure::write('Defaults.Env.Hint', $expected); $result = ConfigClosures::styleForEnv(); From 9e6d1cf4521de783ea28f05b8b5c7de553cc84f6 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Tue, 14 Jun 2016 16:13:45 -0500 Subject: [PATCH 09/15] fixup homepage css --- src/Template/Pages/home.ctp | 2 +- webroot/css/src/app.scss | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Template/Pages/home.ctp b/src/Template/Pages/home.ctp index f5d3063..dddbf2a 100755 --- a/src/Template/Pages/home.ctp +++ b/src/Template/Pages/home.ctp @@ -25,7 +25,7 @@ endif; ?> -Html->css('cake.css') ?> +Html->css(['vendor/normalize', 'vendor/cake']) ?>
diff --git a/webroot/css/src/app.scss b/webroot/css/src/app.scss index 791a3e3..348d924 100644 --- a/webroot/css/src/app.scss +++ b/webroot/css/src/app.scss @@ -2,9 +2,6 @@ * Main application styles should be defined here. */ -@import "../vendor/normalize.css"; // @TODO: Remove this when no longer required. -@import "../vendor/cake.css"; // @TODO: Remove this when no longer required. - // Foundation 5. @import '../vendor/foundation.css'; From 16b1253a3898db2dd4ccedb1e49d3602855178a0 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 08:29:50 -0500 Subject: [PATCH 10/15] Add InListRule. --- src/Model/Rule/InListRule.php | 109 ++++++++ tests/TestCase/Model/Rule/InListRuleTest.php | 253 +++++++++++++++++++ 2 files changed, 362 insertions(+) create mode 100644 src/Model/Rule/InListRule.php create mode 100644 tests/TestCase/Model/Rule/InListRuleTest.php diff --git a/src/Model/Rule/InListRule.php b/src/Model/Rule/InListRule.php new file mode 100644 index 0000000..e0ebb33 --- /dev/null +++ b/src/Model/Rule/InListRule.php @@ -0,0 +1,109 @@ + null, + 'allowNulls' => false, + 'checkOnlyIfDirty' => false, + ]; + + /** + * Construct a new instance of the class. + * + * @param string $field The field to determine if a matching value has a + * matching key in the Configure array at "Lists.{`TableNames`}.{$this->field}". + * @param array $config Additional options for the class: + * - string [configPath] - When present, overrides the Configure path to search. + * - bool [allowNulls] - When true, allows fields that are unset or null to pass. + * - bool [checkOnlyIfDirty] - When true, fields marked as clean will be skipped and always pass. + */ + public function __construct($field, array $config = []) { + $this->field = $field; + $this->config($config); + } + + /** + * Performs the check that ensures this field matches an array_key provided + * at either the passed configPath in the constructor or matching the path + * `Lists.{$options['repository']->registryAlias()}.{$this->field}` + * + * @param \Cake\Datasource\EntityInterface $entity The entity from which to extract the fields + * @param array $options Options passed to the check, where the `repository` key is required. + * @return bool Returns true on the field being found as an array key in the + * Configure value at configPath. False if it does not match or repository + * not passed. If nulls are permitted, and the value is null, short circuits + * to return true. On the field not being dirty returns true. + * @throws \InvalidArgumentException On options not including a repository value + */ + public function __invoke(EntityInterface $entity, array $options) { + // if we don't have a repository passed as an option, must return false + if (empty($options['repository'])) { + throw new InvalidArgumentException( + 'Options requires a repository key to be passed to run this rule.' + ); + } + + // if we want to check the field only if it is dirty + // and the entity reports the field as being dirty + if ((bool)$this->_config['checkOnlyIfDirty'] + && !$entity->dirty($this->field) + ) { + return true; + } + + // build the path to the config value if not passed + if (is_null($this->_config['configPath'])) { + $configPath = "Lists.{$options['repository']->registryAlias()}.{$this->field}"; + } else { + $configPath = $this->_config['configPath']; + } + + // if we allow nulls for the field value and the value is null, return true + if ((bool)$this->_config['allowNulls'] + && is_null($entity->{$this->field}) + ) { + return true; + } + + // ensure the value in Configure is available + if (Configure::check($configPath)) { + return in_array( + $entity->{$this->field}, + array_keys(Configure::read($configPath) ?: []) + ); + } + + return false; + } +} diff --git a/tests/TestCase/Model/Rule/InListRuleTest.php b/tests/TestCase/Model/Rule/InListRuleTest.php new file mode 100644 index 0000000..a3a6958 --- /dev/null +++ b/tests/TestCase/Model/Rule/InListRuleTest.php @@ -0,0 +1,253 @@ +Rule = new TestInListRule($this->field); + $this->User = new User(); + } + + /** + * tearDown method + * + * @return void + */ + public function tearDown() { + unset($this->Rule); + unset($this->User); + + parent::tearDown(); + } + + /** + * test the __construct method + * + * @return void + */ + public function testConstruct() { + $this->assertEquals( + $this->field, + $Rule->field, + 'The field value of the property should be equal to the passed in construct argument.' + ); + } + + /** + * test the __invoke method on no repository set. + * + * @return void + */ + public function testInvokeOnNoRepositoryPassed() { + $this->setExpectedException( + 'InvalidArgumentException', + 'Options requires a repository key to be passed to run this rule.' + ); + $this->Rule($this->User, []); + } + + /** + * test the __invoke method + * + * @param string $field The field name to test. + * @param array $config The config params for InConfigureListRule. + * @param string $value The value of the field to check against. + * @param bool $expected The expected output from __invoke. + * @param string $msg The PHPUnit error message. + * @return void + * @dataProvider providerInvoke + */ + public function testInvoke($field, $config, $value, $expected, $msg = '') { + $options = []; + $options['repository'] = $this->getMock('Cake\ORM\Table'); + + $this->Rule = new InListRule($field, $config); + $this->User->{$this->field} = $value; + + $this->assertEquals( + $expected, + $InConfigureListRule($this->User, $options), + $msg + ); + } + + /** + * DataProvider for testInvoke. + * + * @return array Data inputs for testInvoke. + */ + public function providerInvoke() { + return [ + 'Role that is available in Config' => [ + $this->field, + [], + 'admin', + true, + 'On a valid role, should return true.', + ], + 'Empty String role value' => [ + $this->field, + [], + '', + false, + 'On an empty role value, should return false.', + ], + 'Role that is not available in Config' => [ + $this->field, + [], + 'not-real-value', + false, + 'One a role that is not available in Config, should return false.', + ], + 'Field that is not available in Config' => [ + 'not-real-field', + [], + 'not-real-value', + false, + 'One a field that is not available in Config, should return false.', + ], + 'Custom Path that is not available in Config' => [ + $this->field, + ['configPath' => 'Custom.path.something'], + 'admin', + false, + 'On an invalid path, should return false.', + ], + 'Allow null and pass null' => [ + $this->field, + ['allowNulls' => true], + null, + true, + 'On allowing nulls and setting null, should return true.', + ], + 'Disallow null and pass null' => [ + $this->field, + [], + null, + false, + 'On disallowing nulls and setting null, should return false.', + ], + ]; + } + + /** + * test the __invoke method when dealing with the checkOnlyIfDirty config + * option. + * + * @return void + */ + public function testInvokeOnIgnoreIfDirty() { + $config = [ + 'checkOnlyIfDirty' => true, + ]; + $options = []; + + $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; + $this->Users = TableRegistry::get('Users', $tableConfig); + $options['repository'] = $this->Users; + + $InConfigureListRule = new InConfigureListRule('role', $config); + + $this->User = new User(); + $this->User->role = 'not-real-value'; + $this->User->dirty('role', false); + + $output = $InConfigureListRule($this->User, $options); + $this->assertEquals( + true, + $output, + 'On the Rule set to checkOnlyIfDirty and the field set to not be dirty, should return true, regardless of the fields value.' + ); + unset($InConfigureListRule); + unset($this->Users); + + $config = [ + 'checkOnlyIfDirty' => true, + ]; + $options = []; + + $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; + $this->Users = TableRegistry::get('Users', $tableConfig); + $options['repository'] = $this->Users; + + $InConfigureListRule = new InConfigureListRule('role', $config); + + $this->User = new User(); + $this->User->role = 'admin'; + $this->User->dirty('role', true); + + $output = $InConfigureListRule($this->User, $options); + $this->assertEquals( + true, + $output, + 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return true on a real value.' + ); + unset($InConfigureListRule); + unset($this->Users); + + $config = [ + 'checkOnlyIfDirty' => true, + ]; + $options = []; + + $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; + $this->Users = TableRegistry::get('Users', $tableConfig); + $options['repository'] = $this->Users; + + $InConfigureListRule = new InConfigureListRule('role', $config); + + $this->User = new User(); + $this->User->role = 'not-real-value'; + $this->User->dirty('role', true); + + $output = $InConfigureListRule($this->User, $options); + $this->assertEquals( + false, + $output, + 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return false on a not real value.' + ); + } +} From 279be208bf7e888b90fcab54687d7164ef5eb101 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 08:30:02 -0500 Subject: [PATCH 11/15] Document vagrant box default passwords. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 251a1d5..2b38011 100755 --- a/README.md +++ b/README.md @@ -41,6 +41,11 @@ Alternatively, you can edit your system's `hosts` file to include the VM's IP an You should be able to visit the homepage of the new app and see the setup "traffic lights" as green. +The [bundled vagrant VM](https://github.com/puppetlabs/puppetlabs-packer) starts with two user accounts: + +* `root` / `puppet` # Default for the box. +* `vagrant` / `vagrant` # Customized by our provisioning. Useful for Sequel Pro MySQL access. + ## Contributing From 453bf7cebbccbf1fcc06afaae9922c38b0eb0393 Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 08:57:13 -0500 Subject: [PATCH 12/15] Add readme notes about grunt tooling and skel testing. --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++- README.md.template | 26 +++++++++++++++----- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2b38011..c43810d 100755 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Most of the magic behind the `create-project` command lies in composer's ability Additional first-time setup should be added as a post-install script. If a process should be repeatable, consider making it part of the [loadsys/CakePHP-Shell-Scripts](https://github.com/loadsys/CakePHP-Shell-Scripts) repo instead, and calling that script from a post-install hook. -#### Running Tests +### Running Tests The composer scripts have their own unit tests. To execute them, run: @@ -106,6 +106,63 @@ $ bin/phpunit --configuration skel/tests/phpunit.xml.dist $ open tmp/coverage/skel/html/index.html ``` +### Testing the Spawn Process + +(This is customized for Brian's preferred editor and git UI- adjust as necessary.) + +* Open 2 terminal Windows: + +* T1 (host): `$ cd CakePHP-Skeleton/` + +* (Make your changes to the skeleton files, and **commit them to a branch!**) + +* T1 (host): `$ ./skel/test-project.sh MY_BRANCH_NAME ~/Desktop/skelbuild-check` + + * (Answer all of the prompts, dummy data is mostly fine.) + +* T2 (host): `$ cd ~/Desktop/skelbuild-check/ ; bb . ; ./bootstrap.sh vagrant ; gitx ; vagrant ssh` + + * (If you're also testing changes to the LoadsysTheme): + + * Add this to the `skelbuild-check/composer.json`: + + ```json + "repositories": [ + { + "type": "path", + "url" : "~/path/to/CakePHP-LoadsysTheme/", + "options": { + "symlink": false + } + }, + { + "packagist": false + } + ], + ``` + + * T2 (host!): `$ composer update loadsys/cakephp-loadsys-theme` + + * T2 (vagrant): `$ bin/cake bake migration CreateUsers email:string password:string` # This should use the LoadsysTheme **automatically**! + + * T2 (vagrant): `$ bin/cake migrations migrate` # Make sure the generated migration file executes correctly and populate the DB with a `users` table to test bake with. + + * T2 (vagrant): `$bin/cake bake --all Users` # Should use LoadsysTheme automatically! + + * T2 (vagrant): `$ bin/codesniffer && bin/phpunit` # Should come back (mostly) clean. + + * (Make corrections in the `skelbuild-check/` project.) + + * (Re-run sniffs/tests.) + + * (Use the git diff to copy them back into the `CakePHP-Skeleton/` working copy.) + + * T2 (vagrant): `$ exit` + +* T2 (host): `$ vagrant destroy -f ; cd ../ ; rm -rf skelbuild-check` + +* (Repeat from the top.) + ### Keeping in sync with CakePHP App diff --git a/README.md.template b/README.md.template index 7e5ac23..60b2728 100755 --- a/README.md.template +++ b/README.md.template @@ -247,7 +247,7 @@ This setup is handy for backing up your data if you're about to destroy the box, * Both test data and starting production data is maintained by the Loadsys Basic Seeds plugin. * The default seed files are environment aware, making it possible to run the same command regardless of environment. * To populate (or reset) your local dev data back to the contents of the seed: - * `bin/cake BasicSeed.basic_seed` + * `bin/cake basic_seed -v` * There is not currently any way to automatically generate seed data-- it must be entered into the related files (`config/seed.php` for production, `config/seed_staging.php` and `config/seed_vagrant.php` for development) by hand. * It's possible for the seeds to refer to each other. In the default setup, staging and vagrant envs load **both** the production data, and the vagrant data. * :warning: Seeds are capable of `TRUNCATE`ing data during their execution-- be careful to have backups of any data you want to keep by using `bin/db-backup`. @@ -299,18 +299,32 @@ There are three testsuites setup as part of the phpunit.xml.dist file. ### Javascript Unit Testing -* Tests should also be written for the browser JavaScript code. -* Javascript should be written in individual "class" files (they will be merged by asset compilation) in `webroot/js/src/`. +* Javascript is managed via JS source files in the `webroot/js/src/` folder. +* Separate files should be used for each specific class or each specific page. +* Vendor libraries live in `webroot/js/vendor/`. +* Files need to be added to the `Gruntfile.js` for both the `concat` and `qunit` steps to ensure they are bunlded correctly. +* Tests must be written for all JavaScript code and live in `webroot/js/test/`. + * Anything you would normally put in a `document.ready(...)` call should be placed in `webroot/js/init.js`. -* Matching test files should be created in `webroot/js/test/`. * Tests are run with phantomjs via grunt: + * Run `grunt testjs` to run the qunit test suite. * Run `grunt watch` to run the test suite on js test or source file change. - * Run `grunt qunit` to run the qunit test suite. ### CSS Changes -* CSS is managed via Standard CSS files in the webroot directory +* CSS is managed via SASS source files in the `webroot/css/src/` dir. +* Vendor libraries live in `webroot/css/vendor/`. +* All CSS is compiled together using `grunt css`. +* SASS linting should be performed using `grunt testcss`. +* Use `grunt watch` to compile SASS changes on file save. +* The "main" SASS file is `webroot/css/src/app.scss`. Add your includes here and the build process will take care of the rest. + + +### Other grunt commands + +* `grunt build` - Compiles all CSS and JS assets _without running tests_. +* `grunt` - Compiles all CSS/JS assets and executes all tests. ### Command Line Scripts From a15bcaf7700734dae409b8242f4850d510975b3d Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 12:15:30 -0500 Subject: [PATCH 13/15] More fixup. --- README.md | 4 +- src/Model/Rule/InListRule.php | 14 +- .../TestCase/Controller/AppControllerTest.php | 1 - tests/TestCase/Model/Rule/InListRuleTest.php | 156 ++++++++---------- 4 files changed, 72 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index c43810d..9d80e31 100755 --- a/README.md +++ b/README.md @@ -147,9 +147,9 @@ $ open tmp/coverage/skel/html/index.html * T2 (vagrant): `$ bin/cake migrations migrate` # Make sure the generated migration file executes correctly and populate the DB with a `users` table to test bake with. - * T2 (vagrant): `$bin/cake bake --all Users` # Should use LoadsysTheme automatically! + * T2 (vagrant): `$ bin/cake bake all Users` # Should use LoadsysTheme automatically! - * T2 (vagrant): `$ bin/codesniffer && bin/phpunit` # Should come back (mostly) clean. + * T2 (vagrant): `$ bin/codesniffer-run && bin/phpunit` # Should come back (mostly) clean. * (Make corrections in the `skelbuild-check/` project.) diff --git a/src/Model/Rule/InListRule.php b/src/Model/Rule/InListRule.php index e0ebb33..06d6456 100644 --- a/src/Model/Rule/InListRule.php +++ b/src/Model/Rule/InListRule.php @@ -56,24 +56,16 @@ public function __construct($field, array $config = []) { /** * Performs the check that ensures this field matches an array_key provided * at either the passed configPath in the constructor or matching the path - * `Lists.{$options['repository']->registryAlias()}.{$this->field}` + * `Lists.{$entity->source()}.{$this->field}` * * @param \Cake\Datasource\EntityInterface $entity The entity from which to extract the fields - * @param array $options Options passed to the check, where the `repository` key is required. + * @param array $options Options passed to the check. * @return bool Returns true on the field being found as an array key in the * Configure value at configPath. False if it does not match or repository * not passed. If nulls are permitted, and the value is null, short circuits * to return true. On the field not being dirty returns true. - * @throws \InvalidArgumentException On options not including a repository value */ public function __invoke(EntityInterface $entity, array $options) { - // if we don't have a repository passed as an option, must return false - if (empty($options['repository'])) { - throw new InvalidArgumentException( - 'Options requires a repository key to be passed to run this rule.' - ); - } - // if we want to check the field only if it is dirty // and the entity reports the field as being dirty if ((bool)$this->_config['checkOnlyIfDirty'] @@ -84,7 +76,7 @@ public function __invoke(EntityInterface $entity, array $options) { // build the path to the config value if not passed if (is_null($this->_config['configPath'])) { - $configPath = "Lists.{$options['repository']->registryAlias()}.{$this->field}"; + $configPath = "Lists.{$entity->source()}.{$this->field}"; } else { $configPath = $this->_config['configPath']; } diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php index af0146b..9ecf435 100644 --- a/tests/TestCase/Controller/AppControllerTest.php +++ b/tests/TestCase/Controller/AppControllerTest.php @@ -288,7 +288,6 @@ public function providerBlackHole() { * @covers \App\Controller\AppController::beforeRender */ public function testBeforeRender() { - // Set up auth data, AuthComponent and Controller. $controller = $this->getMock( 'App\Controller\AppController', diff --git a/tests/TestCase/Model/Rule/InListRuleTest.php b/tests/TestCase/Model/Rule/InListRuleTest.php index a3a6958..c229ad9 100644 --- a/tests/TestCase/Model/Rule/InListRuleTest.php +++ b/tests/TestCase/Model/Rule/InListRuleTest.php @@ -4,8 +4,9 @@ */ namespace App\Test\TestCase\Model\Rule; -use App\Model\Rule\InConfigureListRule; +use App\Model\Rule\InListRule; use App\Model\Table\UsersTable; +use Cake\Core\Configure; use Cake\ORM\Entity as User; use Cake\ORM\TableRegistry; use Cake\TestSuite\TestCase; @@ -39,6 +40,21 @@ class InListRuleTest extends TestCase { */ public $field = 'role'; + /** + * Data to load into Configure for testing. + * + * @var string + */ + public $configData = [ + 'Users' => [ + 'role' => [ + 'student' => 'Student', + 'parent' => 'Guardian', + 'admin' => 'Administrator', + ], + ], + ]; + /** * setUp method * @@ -48,7 +64,8 @@ public function setUp() { parent::setUp(); $this->Rule = new TestInListRule($this->field); - $this->User = new User(); + $this->User = new User([], ['source' => 'Users']); + Configure::write('Lists', $this->configData); } /** @@ -57,6 +74,7 @@ public function setUp() { * @return void */ public function tearDown() { + Configure::delete('Lists'); unset($this->Rule); unset($this->User); @@ -71,7 +89,7 @@ public function tearDown() { public function testConstruct() { $this->assertEquals( $this->field, - $Rule->field, + $this->Rule->field, 'The field value of the property should be equal to the passed in construct argument.' ); } @@ -82,11 +100,10 @@ public function testConstruct() { * @return void */ public function testInvokeOnNoRepositoryPassed() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Options requires a repository key to be passed to run this rule.' + $this->assertFalse( + $this->Rule->__invoke(new User(), []), + 'When no Entity::source() is available, return false' ); - $this->Rule($this->User, []); } /** @@ -94,22 +111,30 @@ public function testInvokeOnNoRepositoryPassed() { * * @param string $field The field name to test. * @param array $config The config params for InConfigureListRule. + * @param bool $dirty Whether the field should be marked as dirty or not. * @param string $value The value of the field to check against. * @param bool $expected The expected output from __invoke. * @param string $msg The PHPUnit error message. * @return void * @dataProvider providerInvoke */ - public function testInvoke($field, $config, $value, $expected, $msg = '') { - $options = []; - $options['repository'] = $this->getMock('Cake\ORM\Table'); + public function testInvoke($field, $config, $dirty, $value, $expected, $msg = '') { + $options = [ + 'repository' => $this->getMockBuilder('Cake\ORM\Table') + ->setMethods([]) + ->setConstructorArgs([ + ['alias' => 'Users'], // 1st constructor arg + ]) + ->getMock(), + ]; $this->Rule = new InListRule($field, $config); $this->User->{$this->field} = $value; + $this->User->dirty($this->field, $dirty); $this->assertEquals( $expected, - $InConfigureListRule($this->User, $options), + $this->Rule->__invoke($this->User, $options), $msg ); } @@ -124,6 +149,7 @@ public function providerInvoke() { 'Role that is available in Config' => [ $this->field, [], + false, 'admin', true, 'On a valid role, should return true.', @@ -131,6 +157,7 @@ public function providerInvoke() { 'Empty String role value' => [ $this->field, [], + false, '', false, 'On an empty role value, should return false.', @@ -138,6 +165,7 @@ public function providerInvoke() { 'Role that is not available in Config' => [ $this->field, [], + false, 'not-real-value', false, 'One a role that is not available in Config, should return false.', @@ -145,6 +173,7 @@ public function providerInvoke() { 'Field that is not available in Config' => [ 'not-real-field', [], + false, 'not-real-value', false, 'One a field that is not available in Config, should return false.', @@ -152,6 +181,7 @@ public function providerInvoke() { 'Custom Path that is not available in Config' => [ $this->field, ['configPath' => 'Custom.path.something'], + false, 'admin', false, 'On an invalid path, should return false.', @@ -159,6 +189,7 @@ public function providerInvoke() { 'Allow null and pass null' => [ $this->field, ['allowNulls' => true], + false, null, true, 'On allowing nulls and setting null, should return true.', @@ -166,88 +197,35 @@ public function providerInvoke() { 'Disallow null and pass null' => [ $this->field, [], + false, null, false, 'On disallowing nulls and setting null, should return false.', ], + 'Check only dirty enabled, a valid & clean field/value should return true.' => [ + $this->field, + ['checkOnlyIfDirty' => true], + false, + 'admin', + true, + 'When checkOnlyIfDirty is enabled, a valid field+value that is marked dirty should return true.', + ], + 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return true on a real value.' => [ + $this->field, + ['checkOnlyIfDirty' => true], + true, + 'admin', + true, + 'When checkOnlyIfDirty is enabled, a valid field+value that is marked dirty should return true.', + ], + 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return false on a not real value.' => [ + $this->field, + ['checkOnlyIfDirty' => true], + true, + 'not-valid-value', + false, + 'When checkOnlyIfDirty is enabled, an invalid field+value that is marked dirty should return false.', + ], ]; } - - /** - * test the __invoke method when dealing with the checkOnlyIfDirty config - * option. - * - * @return void - */ - public function testInvokeOnIgnoreIfDirty() { - $config = [ - 'checkOnlyIfDirty' => true, - ]; - $options = []; - - $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; - $this->Users = TableRegistry::get('Users', $tableConfig); - $options['repository'] = $this->Users; - - $InConfigureListRule = new InConfigureListRule('role', $config); - - $this->User = new User(); - $this->User->role = 'not-real-value'; - $this->User->dirty('role', false); - - $output = $InConfigureListRule($this->User, $options); - $this->assertEquals( - true, - $output, - 'On the Rule set to checkOnlyIfDirty and the field set to not be dirty, should return true, regardless of the fields value.' - ); - unset($InConfigureListRule); - unset($this->Users); - - $config = [ - 'checkOnlyIfDirty' => true, - ]; - $options = []; - - $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; - $this->Users = TableRegistry::get('Users', $tableConfig); - $options['repository'] = $this->Users; - - $InConfigureListRule = new InConfigureListRule('role', $config); - - $this->User = new User(); - $this->User->role = 'admin'; - $this->User->dirty('role', true); - - $output = $InConfigureListRule($this->User, $options); - $this->assertEquals( - true, - $output, - 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return true on a real value.' - ); - unset($InConfigureListRule); - unset($this->Users); - - $config = [ - 'checkOnlyIfDirty' => true, - ]; - $options = []; - - $tableConfig = TableRegistry::exists('Users') ? [] : ['className' => 'App\Model\Table\UsersTable']; - $this->Users = TableRegistry::get('Users', $tableConfig); - $options['repository'] = $this->Users; - - $InConfigureListRule = new InConfigureListRule('role', $config); - - $this->User = new User(); - $this->User->role = 'not-real-value'; - $this->User->dirty('role', true); - - $output = $InConfigureListRule($this->User, $options); - $this->assertEquals( - false, - $output, - 'On the Rule set to checkOnlyIfDirty and the field set to be dirty, should return false on a not real value.' - ); - } } From 0eb925927129103a5eb6ae43033a6f6d9b412f1c Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 12:16:14 -0500 Subject: [PATCH 14/15] Update LoadsysTheme requirement to ensure new features are available. --- composer.json.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json.template b/composer.json.template index f24b027..099f626 100644 --- a/composer.json.template +++ b/composer.json.template @@ -26,7 +26,7 @@ "loadsys/loadsys_codesniffer": "~3.0", "phpmd/phpmd": "^2.3", "loadsys/cakephp-uuid-shell": "~1.0", - "loadsys/cakephp-loadsys-theme": "^1.0", + "loadsys/cakephp-loadsys-theme": "^1.1", "behat/behat": "~3.0", "behat/mink": "^1.7", "behat/mink-extension": "^2.2", From a814340c65c07796bf3976af9a48f2824853848f Mon Sep 17 00:00:00 2001 From: Brian Porter Date: Wed, 15 Jun 2016 12:30:33 -0500 Subject: [PATCH 15/15] Correct LogTraitTest to no longer require a Users fixture. --- tests/TestCase/Lib/Log/LogTraitTest.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/TestCase/Lib/Log/LogTraitTest.php b/tests/TestCase/Lib/Log/LogTraitTest.php index 3293e45..23e0a5f 100644 --- a/tests/TestCase/Lib/Log/LogTraitTest.php +++ b/tests/TestCase/Lib/Log/LogTraitTest.php @@ -31,9 +31,7 @@ class LogTraitTest extends TestCase { * * @var array */ - public $fixtures = [ - 'app.users', - ]; + public $fixtures = []; /** * setUp method @@ -44,9 +42,12 @@ public function setUp() { parent::setUp(); $this->TestLogTrait = new TestLogTrait(); - - $config = TableRegistry::exists('Users') ? [] : ['className' => '\App\Model\Table\UsersTable']; - $this->Users = TableRegistry::get('Users', $config); + $this->Users = $this->getMockBuilder('Cake\ORM\Table') + ->setMethods([]) + ->setConstructorArgs([ + ['alias' => 'Users'], // 1st constructor arg + ]) + ->getMock(); } /**